Merge branch 'main' into instruction_patch_clean

This commit is contained in:
Silvris
2024-02-15 00:29:16 -06:00
committed by GitHub
260 changed files with 9844 additions and 3758 deletions
+5 -1
View File
@@ -77,6 +77,10 @@ class AutoWorldRegister(type):
# create missing options_dataclass from legacy option_definitions
# TODO - remove this once all worlds use options dataclasses
if "options_dataclass" not in dct and "option_definitions" in dct:
# TODO - switch to deprecate after a version
if __debug__:
logging.warning(f"{name} Assigned options through option_definitions which is now deprecated. "
"Please use options_dataclass instead.")
dct["options_dataclass"] = make_dataclass(f"{name}Options", dct["option_definitions"].items(),
bases=(PerGameCommonOptions,))
@@ -324,7 +328,7 @@ class World(metaclass=AutoWorldRegister):
def create_items(self) -> None:
"""
Method for creating and submitting items to the itempool. Items and Regions should *not* be created and submitted
Method for creating and submitting items to the itempool. Items and Regions must *not* be created and submitted
to the MultiWorld after this step. If items need to be placed during pre_fill use `get_prefill_items`.
"""
pass
+8 -2
View File
@@ -3,7 +3,9 @@ import os
import sys
import warnings
import zipimport
from typing import Dict, List, NamedTuple, TypedDict
import time
import dataclasses
from typing import Dict, List, TypedDict, Optional
from Utils import local_path, user_path
@@ -34,10 +36,12 @@ class DataPackage(TypedDict):
games: Dict[str, GamesPackage]
class WorldSource(NamedTuple):
@dataclasses.dataclass(order=True)
class WorldSource:
path: str # typically relative path from this module
is_zip: bool = False
relative: bool = True # relative to regular world import folder
time_taken: Optional[float] = None
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
@@ -50,6 +54,7 @@ class WorldSource(NamedTuple):
def load(self) -> bool:
try:
start = time.perf_counter()
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10
@@ -69,6 +74,7 @@ class WorldSource(NamedTuple):
importer.exec_module(mod)
else:
importlib.import_module(f".{self.path}", "worlds")
self.time_taken = time.perf_counter()-start
return True
except Exception:
+1 -1
View File
@@ -271,7 +271,7 @@ class AdventureWorld(World):
overworld_locations_copy = overworld.locations.copy()
all_locations = self.multiworld.get_locations(self.player)
locations_copy = all_locations.copy()
locations_copy = list(all_locations)
for loc in all_locations:
if loc.item is not None or loc.progress_type != LocationProgressType.DEFAULT:
locations_copy.remove(loc)
+1 -2
View File
@@ -10,8 +10,7 @@ As we are using BizHawk, this guide is only applicable to Windows and Linux syst
- Version 2.3.1 and later are supported. Version 2.7 is recommended for stability.
- Detailed installation instructions for BizHawk can be found at the above link.
- Windows users must run the prereq installer first, which can also be found at the above link.
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
(select `Adventure Client` during installation).
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases).
- An Adventure NTSC ROM file. The Archipelago community cannot provide these.
## Configuring BizHawk
+1 -1
View File
@@ -264,7 +264,7 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
if loc in all_state_base.events:
all_state_base.events.remove(loc)
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True,
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True, allow_excluded=True,
name="LttP Dungeon Items")
-2
View File
@@ -682,8 +682,6 @@ def get_pool_core(world, player: int):
key_location = world.random.choice(key_locations)
place_item(key_location, "Small Key (Universal)")
pool = pool[:-3]
if world.key_drop_shuffle[player]:
pass # pool.extend([item_to_place] * (len(key_drop_data) - 1))
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon,
additional_pieces_to_place)
+5 -1
View File
@@ -136,7 +136,8 @@ def mirrorless_path_to_castle_courtyard(world, player):
def set_defeat_dungeon_boss_rule(location):
# Lambda required to defer evaluation of dungeon.boss since it will change later if boss shuffle is used
set_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state))
add_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state))
def set_always_allow(spot, rule):
spot.always_allow = rule
@@ -967,6 +968,9 @@ def standard_rules(world, player):
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3))
else:
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state.has('Big Key (Hyrule Castle)', player))
def toss_junk_item(world, player):
items = ['Rupees (20)', 'Bombs (3)', 'Arrows (10)', 'Rupees (5)', 'Rupee (1)', 'Bombs (10)',
+7
View File
@@ -26,6 +26,13 @@ class ALttPLocation(Location):
self.player_address = player_address
self._hint_text = hint_text
@property
def hint_text(self) -> str:
hint_text = getattr(self, "_hint_text", None)
if hint_text:
return hint_text
return "at " + self.name.replace("_", " ").replace("-", " ")
class ALttPItem(Item):
game: str = "A Link to the Past"
+11 -6
View File
@@ -289,12 +289,17 @@ class ALTTPWorld(World):
self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options)
self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options)
if multiworld.mode[player] == 'standard' \
and multiworld.smallkey_shuffle[player] \
and multiworld.smallkey_shuffle[player] != smallkey_shuffle.option_universal \
and multiworld.smallkey_shuffle[player] != smallkey_shuffle.option_own_dungeons \
and multiworld.smallkey_shuffle[player] != smallkey_shuffle.option_start_with:
self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1
if multiworld.mode[player] == 'standard':
if multiworld.smallkey_shuffle[player]:
if (multiworld.smallkey_shuffle[player] not in
(smallkey_shuffle.option_universal, smallkey_shuffle.option_own_dungeons,
smallkey_shuffle.option_start_with)):
self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1
self.multiworld.local_items[self.player].value.add("Small Key (Hyrule Castle)")
self.multiworld.non_local_items[self.player].value.discard("Small Key (Hyrule Castle)")
if multiworld.bigkey_shuffle[player]:
self.multiworld.local_items[self.player].value.add("Big Key (Hyrule Castle)")
self.multiworld.non_local_items[self.player].value.discard("Big Key (Hyrule Castle)")
# system for sharing ER layouts
self.er_seed = str(multiworld.random.randint(0, 2 ** 64))
+6 -6
View File
@@ -2,8 +2,7 @@
## Required Software
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for
`SNI Client - A Link to the Past Patch Setup`
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- [SNI](https://github.com/alttpo/sni/releases). This is automatically included with your Archipelago installation above.
- SNI is not compatible with (Q)Usb2Snes.
- Hardware or software capable of loading and playing SNES ROM files
@@ -18,11 +17,12 @@ but it is not supported.**
## Installation Procedures
1. Download and install SNIClient from the link above, making sure to install the most recent version.
**The installer file is located in the assets section at the bottom of the version information**.
- During setup, you will be asked to locate your base ROM file. This is your Japanese Link to the Past ROM file.
1. Download and install [Archipelago](<https://github.com/ArchipelagoMW/Archipelago/releases/latest>). **The installer
file is located in the assets section at the bottom of the version information.**
2. The first time you do local generation or patch your game, you will be asked to locate your base ROM file.
This is your Japanese Link to the Past ROM file. This only needs to be done once.
2. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
files.
1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
2. Right-click on a ROM file and select **Open with...**
+19 -10
View File
@@ -7,16 +7,25 @@ from ..AutoWorld import WebWorld, World
class Bk_SudokuWebWorld(WebWorld):
options_page = "games/Sudoku/info/en"
theme = 'partyTime'
tutorials = [
Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing BK Sudoku',
language='English',
file_name='setup_en.md',
link='setup/en',
authors=['Jarno']
)
]
setup_en = Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing BK Sudoku',
language='English',
file_name='setup_en.md',
link='setup/en',
authors=['Jarno']
)
setup_de = Tutorial(
tutorial_name='Setup Anleitung',
description='Eine Anleitung um BK-Sudoku zu spielen',
language='Deutsch',
file_name='setup_de.md',
link='setup/de',
authors=['Held_der_Zeit']
)
tutorials = [setup_en, setup_de]
class Bk_SudokuWorld(World):
+21
View File
@@ -0,0 +1,21 @@
# BK-Sudoku
## Was ist das für ein Spiel?
BK-Sudoku ist kein typisches Archipelago-Spiel; stattdessen ist es ein gewöhnlicher Sudoku-Client der sich zu jeder
beliebigen Multiworld verbinden kann. Einmal verbunden kannst du ein 9x9 Sudoku spielen um einen zufälligen Hinweis
für dein Spiel zu erhalten. Es ist zwar langsam, aber es gibt dir etwas zu tun, solltest du mal nicht in der Lage sein
weitere „Checks” zu erreichen.
(Wer mag kann auch einfach so Sudoku spielen. Man muss nicht mit einer Multiworld verbunden sein, um ein Sudoku zu
spielen/generieren.)
## Wie werden Hinweise freigeschalten?
Nach dem Lösen eines Sudokus wird für den verbundenen Slot ein zufällig ausgewählter Hinweis freigegeben, für einen
Gegenstand der noch nicht gefunden wurde.
## Wo ist die Seite für die Einstellungen?
Es gibt keine Seite für die Einstellungen. Dieses Spiel kann nicht in deinen YAML-Dateien benutzt werden. Stattdessen
kann sich der Client mit einem beliebigen Slot einer Multiworld verbinden. In dem Client selbst kann aber der
Schwierigkeitsgrad des Sudoku ausgewählt werden.
+27
View File
@@ -0,0 +1,27 @@
# BK-Sudoku Setup Anleitung
## Benötigte Software
- [Bk-Sudoku](https://github.com/Jarno458/sudoku)
- Windows 8 oder höher
## Generelles Konzept
Dies ist ein Client, der sich mit jedem beliebigen Slot einer Multiworld verbinden kann. Er lässt dich ein (9x9) Sudoku
spielen, um zufällige Hinweise für den verbundenen Slot freizuschalten.
Aufgrund des Fakts, dass der Sudoku-Client sich zu jedem beliebigen Slot verbinden kann, ist es daher nicht notwendig
eine YAML für dieses Spiel zu generieren, da es keinen neuen Slot zur Multiworld-Session hinzufügt.
## Installationsprozess
Gehe zu der aktuellsten (latest) Veröffentlichung der [BK-Sudoku Releases](https://github.com/Jarno458/sudoku/releases).
Downloade und extrahiere/entpacke die `Bk_Sudoku.zip`-Datei.
## Verbinden mit einer Multiworld
1. Starte `Bk_Sudoku.exe`
2. Trage den Namen des Slots ein, mit dem du dich verbinden möchtest
3. Trage die Server-URL und den Port ein
4. Drücke auf Verbinden (connect)
5. Wähle deinen Schwierigkeitsgrad
6. Versuche das Sudoku zu Lösen
-1
View File
@@ -5,7 +5,6 @@
- ChecksFinder from
the [Github releases Page for the game](https://github.com/jonloveslegos/ChecksFinder/releases) (latest version)
- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases)
- (select `ChecksFinder Client` during installation.)
## Configuring your YAML file
+20 -10
View File
@@ -11,16 +11,26 @@ from .Rules import get_button_rule
class CliqueWebWorld(WebWorld):
theme = "partyTime"
tutorials = [
Tutorial(
tutorial_name="Start Guide",
description="A guide to playing Clique.",
language="English",
file_name="guide_en.md",
link="guide/en",
authors=["Phar"]
)
]
setup_en = Tutorial(
tutorial_name="Start Guide",
description="A guide to playing Clique.",
language="English",
file_name="guide_en.md",
link="guide/en",
authors=["Phar"]
)
setup_de = Tutorial(
tutorial_name="Anleitung zum Anfangen",
description="Eine Anleitung um Clique zu spielen.",
language="Deutsch",
file_name="guide_de.md",
link="guide/de",
authors=["Held_der_Zeit"]
)
tutorials = [setup_en, setup_de]
class CliqueWorld(World):
+18
View File
@@ -0,0 +1,18 @@
# Clique
## Was ist das für ein Spiel?
~~Clique ist ein psychologisches Überlebens-Horror Spiel, in dem der Spieler der Versuchung wiederstehen muss große~~
~~(rote) Knöpfe zu drücken.~~
Clique ist ein scherzhaftes Spiel, welches für Archipelago im März 2023 entwickelt wurde, um zu zeigen, wie einfach
es sein kann eine Welt für Archipelago zu entwicklen. Das Ziel des Spiels ist es den großen (standardmäßig) roten
Knopf zu drücken. Wenn ein Spieler auf dem `hard_mode` (schwieriger Modus) spielt, muss dieser warten bis jemand
anderes in der Multiworld den Knopf aktiviert, damit er gedrückt werden kann.
Clique kann auf den meisten modernen, HTML5-fähigen Browsern gespielt werden.
## Wo ist die Seite für die Einstellungen?
Die [Seite für die Spielereinstellungen dieses Spiels](../player-options) enthält alle Optionen die man benötigt um
eine YAML-Datei zu konfigurieren und zu exportieren.
+25
View File
@@ -0,0 +1,25 @@
# Clique Anleitung
Nachdem dein Seed generiert wurde, gehe auf die Website von [Clique dem Spiel](http://clique.pharware.com/) und gib
Server-Daten, deinen Slot-Namen und ein Passwort (falls vorhanden) ein. Klicke dann auf "Connect" (Verbinden).
Wenn du auf "Einfach" spielst, kannst du unbedenklich den Knopf drücken und deine "Befriedigung" erhalten.
Wenn du auf "Schwer" spielst, ist es sehr wahrscheinlich, dass du warten musst bevor du dein Ziel erreichen kannst.
Glücklicherweise läuft Click auf den meißten großen Browsern, die HTML5 unterstützen. Das heißt du kannst Clique auf
deinem Handy starten und produktiv sein während du wartest!
Falls du einige Ideen brauchst was du tun kannst, während du wartest bis der Knopf aktiviert wurde, versuche
(mindestens) eins der Folgenden:
- Dein Zimmer aufräumen.
- Die Wäsche machen.
- Etwas Essen von einem X-Belieben Fast Food Restaruant holen.
- Das tägliche Wordle machen.
- ~~Deine Seele an **Phar** verkaufen.~~
- Deine Hausaufgaben erledigen.
- Deine Post abholen.
~~Solltest du auf irgendwelche Probleme in diesem Spiel stoßen, solltest du keinesfalls nicht **thephar** auf~~
~~Discord kontaktieren. *zwinker* *zwinker*~~
+5 -4
View File
@@ -2,7 +2,7 @@
## Required Software
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `SNI Client - Donkey Kong Country 3 Patch Setup`
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- Hardware or software capable of loading and playing SNES ROM files
@@ -23,9 +23,10 @@
### Windows Setup
1. During the installation of Archipelago, you will have been asked to install the SNI Client. If you did not do this,
or you are on an older version, you may run the installer again to install the SNI Client.
2. During setup, you will be asked to locate your base ROM file. This is your Donkey Kong Country 3 ROM file.
1. Download and install [Archipelago](<https://github.com/ArchipelagoMW/Archipelago/releases/latest>). **The installer
file is located in the assets section at the bottom of the version information.**
2. The first time you do local generation or patch your game, you will be asked to locate your base ROM file.
This is your Donkey Kong Country 3 ROM file. This only needs to be done once.
3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
files.
1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
+11 -2
View File
@@ -13,14 +13,23 @@ client_version = 0
class DLCqwebworld(WebWorld):
tutorials = [Tutorial(
setup_en = Tutorial(
"Multiworld Setup Tutorial",
"A guide to setting up the Archipelago DLCQuest game on your computer.",
"English",
"setup_en.md",
"setup/en",
["axe_y"]
)]
)
setup_fr = Tutorial(
"Guide de configuration MultiWorld",
"Un guide pour configurer DLCQuest sur votre PC.",
"Français",
"setup_fr.md",
"setup/fr",
["Deoxis"]
)
tutorials = [setup_en, setup_fr]
class DLCqworld(World):
+49
View File
@@ -0,0 +1,49 @@
# DLC Quest
## 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.
## Quel est l'effet de la randomisation sur ce jeu ?
Les DLC seront obtenus en tant que check pour le multiworld. Il existe également d'autres checks optionnels dans DLC Quest.
## Quel est le but de DLC Quest ?
DLC Quest a deux campagnes, et le joueur peut choisir celle qu'il veut jouer pour sa partie.
Il peut également choisir de faire les deux campagnes.
## Quels sont les emplacements dans DLC quest ?
Les emplacements dans DLC Quest comprennent toujours
- les achats de DLC auprès du commerçant
- Les objectifs liés aux récompenses
- Tuer des moutons dans DLC Quest
- Objectifs spécifiques de l'attribution dans Live Freemium or Die
Il existe également un certain nombres de critères de localisation qui sont optionnels et que les joueurs peuvent choisir d'inclure ou non dans leur sélection :
- Objets que votre personnage peut obtenir de différentes manières
- Swords
- Gun
- Box of Various Supplies
- Humble Indie Bindle
- Pickaxe
- Coinsanity : Pièces de monnaie, soit individuellement, soit sous forme de lots personnalisés
## Quels objets peuvent se trouver dans le monde d'un autre joueur ?
Tous les DLC du jeu sont mélangés dans le stock d'objets. Les objets liés aux contrôles optionnels décrits ci-dessus sont également dans le stock
Il y a aussi de nouveaux objets pièges, utilisés comme substituts, basés sur les désagréments du jeu vanille.
- Zombie Sheep
- Loading Screens
- Temporary Spikes
## Que se passe-t-il lorsque le joueur reçoit un objet ?
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.
+55
View File
@@ -0,0 +1,55 @@
# # Guide de configuration MultiWorld de DLCQuest
## Logiciels requis
- DLC Quest sur PC (Recommandé: [Version Steam](https://store.steampowered.com/app/230050/DLC_Quest/))
- [DLCQuestipelago](https://github.com/agilbert1412/DLCQuestipelago/releases)
- BepinEx (utilisé comme un modloader pour DLCQuest. La version du mod ci-dessus inclut BepInEx si vous choisissez la version d'installation complète)
## Logiciels optionnels
- [Archipelago] (https://github.com/ArchipelagoMW/Archipelago/releases)
- (Uniquement pour le TextClient)
## Créer un fichier de configuration (.yaml)
### Qu'est-ce qu'un fichier YAML et pourquoi en ai-je besoin ?
Voir le guide d'Archipelago sur la mise en place d'un YAML de base : [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en)
### 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).
## Rejoindre une partie multi-monde
### Installer le mod
- Télécharger le [DLCQuestipelago mod release](https://github.com/agilbert1412/DLCQuestipelago/releases). Si c'est la première fois que vous installez le mod, ou si vous n'êtes pas à l'aise avec l'édition manuelle de fichiers, vous devriez choisir l'Installateur. Il se chargera de la plus grande partie du travail pour vous
- Extraire l'archive .zip à l'emplacement de votre choix
- Exécutez "DLCQuestipelagoInstaller.exe".
![image](https://i.imgur.com/2sPhMgs.png)
- Le programme d'installation devrait décrire ce qu'il fait à chaque étape, et vous demandera votre avis si nécessaire.
- Il vous permettra de choisir l'emplacement d'installation de votre jeu moddé et vous proposera un emplacement par défaut
- Il **essayera** de trouver votre jeu DLCQuest sur votre ordinateur et, en cas d'échec, vous demandera d'indiquer le chemin d'accès.
- Il vous offrira la possibilité de créer un raccourci sur le bureau pour le lanceur moddé.
### Se connecter au MultiServer
- Localisez le fichier "ArchipelagoConnectionInfo.json", qui se situe dans le même emplacement que votre installation moddée. Vous pouvez éditer ce fichier avec n'importe quel éditeur de texte, et vous devez entrer l'adresse IP du serveur, le port et votre nom de joueur dans les champs appropriés.
- Exécutez BepInEx.NET.Framework.Launcher.exe. Si vous avez opté pour un raccourci sur le bureau, vous le trouverez avec une icône et un nom plus reconnaissable.
![image](https://i.imgur.com/ZUiFrhf.png)
- Votre jeu devrait se lancer en même temps qu'une console de modloader, qui contiendra des informations de débogage importantes si vous rencontrez des problèmes.
- Le jeu devrait se connecter automatiquement, et tenter de se reconnecter si votre internet ou le serveur se déconnecte, pendant que vous jouez.
### Interagir avec le MultiWorld depuis le jeu
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.
+10 -8
View File
@@ -5,7 +5,7 @@ import os
import shutil
import threading
import zipfile
from typing import Optional, TYPE_CHECKING, Any, List, Callable, Tuple
from typing import Optional, TYPE_CHECKING, Any, List, Callable, Tuple, Union
import jinja2
@@ -63,7 +63,7 @@ recipe_time_ranges = {
class FactorioModFile(worlds.Files.APContainer):
game = "Factorio"
compression_method = zipfile.ZIP_DEFLATED # Factorio can't load LZMA archives
writing_tasks: List[Callable[[], Tuple[str, str]]]
writing_tasks: List[Callable[[], Tuple[str, Union[str, bytes]]]]
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
@@ -164,9 +164,7 @@ def generate_mod(world: "Factorio", output_directory: str):
template_data["free_sample_blacklist"].update({item: 1 for item in multiworld.free_sample_blacklist[player].value})
template_data["free_sample_blacklist"].update({item: 0 for item in multiworld.free_sample_whitelist[player].value})
mod_dir = os.path.join(output_directory, versioned_mod_name)
zf_path = os.path.join(mod_dir + ".zip")
zf_path = os.path.join(output_directory, versioned_mod_name + ".zip")
mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player])
if world.zip_path:
@@ -177,7 +175,13 @@ def generate_mod(world: "Factorio", output_directory: str):
mod.writing_tasks.append(lambda arcpath=versioned_mod_name+"/"+path_part, content=zf.read(file):
(arcpath, content))
else:
shutil.copytree(os.path.join(os.path.dirname(__file__), "data", "mod"), mod_dir, dirs_exist_ok=True)
basepath = os.path.join(os.path.dirname(__file__), "data", "mod")
for dirpath, dirnames, filenames in os.walk(basepath):
base_arc_path = (versioned_mod_name+"/"+os.path.relpath(dirpath, basepath)).rstrip("/.\\")
for filename in filenames:
mod.writing_tasks.append(lambda arcpath=base_arc_path+"/"+filename,
file_path=os.path.join(dirpath, filename):
(arcpath, open(file_path, "rb").read()))
mod.writing_tasks.append(lambda: (versioned_mod_name + "/data.lua",
data_template.render(**template_data)))
@@ -197,5 +201,3 @@ def generate_mod(world: "Factorio", output_directory: str):
# write the mod file
mod.write()
# clean up
shutil.rmtree(mod_dir)
+3 -4
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import typing
import datetime
from Options import Choice, OptionDict, OptionSet, ItemDict, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
StartInventoryPool
from schema import Schema, Optional, And, Or
@@ -207,11 +207,10 @@ class RecipeIngredientsOffset(Range):
range_end = 5
class FactorioStartItems(ItemDict):
class FactorioStartItems(OptionDict):
"""Mapping of Factorio internal item-name to amount granted on start."""
display_name = "Starting Items"
verify_item_name = False
default = {"burner-mining-drill": 19, "stone-furnace": 19}
default = {"burner-mining-drill": 4, "stone-furnace": 4, "raw-fish": 50}
class FactorioFreeSampleBlacklist(OptionSet):
+2 -1
View File
@@ -246,7 +246,8 @@ class Factorio(World):
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
(ingredient not in technology_table or state.has(ingredient, player)) and \
all(state.has(technology.name, player) for sub_ingredient in custom_recipe.ingredients
for technology in required_technologies[sub_ingredient])
for technology in required_technologies[sub_ingredient]) and \
all(state.has(technology.name, player) for technology in required_technologies[custom_recipe.crafting_machine])
else:
location.access_rule = lambda state, ingredient=ingredient: \
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
+1
View File
@@ -74,6 +74,7 @@ class FF1World(World):
items = get_options(self.multiworld, 'items', self.player)
goal_rule = generate_rule([[name for name in items.keys() if name in FF1_PROGRESSION_LIST and name != "Shard"]],
self.player)
terminated_event.access_rule = goal_rule
if "Shard" in items.keys():
def goal_rule_and_shards(state):
return goal_rule(state) and state.has("Shard", self.player, 32)
+1 -1
View File
@@ -71,7 +71,7 @@ class FFMQClient(SNIClient):
received = await snes_read(ctx, RECEIVED_DATA[0], RECEIVED_DATA[1])
data = await snes_read(ctx, READ_DATA_START, READ_DATA_END - READ_DATA_START)
check_2 = await snes_read(ctx, 0xF53749, 1)
if check_1 == b'\x00' or check_2 == b'\x00':
if check_1 in (b'\x00', b'\x55') or check_2 in (b'\x00', b'\x55'):
return
def get_range(data_range):
+1 -5
View File
@@ -187,6 +187,7 @@ item_table = {
"Pazuzu 5F": ItemData(None, ItemClassification.progression),
"Pazuzu 6F": ItemData(None, ItemClassification.progression),
"Dark King": ItemData(None, ItemClassification.progression),
"Tristam Bone Item Given": ItemData(None, ItemClassification.progression),
#"Barred": ItemData(None, ItemClassification.progression),
}
@@ -222,11 +223,6 @@ for item, data in item_table.items():
def create_items(self) -> None:
items = []
starting_weapon = self.multiworld.starting_weapon[self.player].current_key.title().replace("_", " ")
if self.multiworld.progressive_gear[self.player]:
for item_group in prog_map:
if starting_weapon in self.item_name_groups[item_group]:
starting_weapon = prog_map[item_group]
break
self.multiworld.push_precollected(self.create_item(starting_weapon))
self.multiworld.push_precollected(self.create_item("Steel Armor"))
if self.multiworld.sky_coin_mode[self.player] == "start_with":
+100 -2
View File
@@ -1,4 +1,4 @@
from Options import Choice, FreeText, Toggle
from Options import Choice, FreeText, Toggle, Range
class Logic(Choice):
@@ -131,6 +131,21 @@ class EnemizerAttacks(Choice):
default = 0
class EnemizerGroups(Choice):
"""Set which enemy groups will be affected by Enemizer."""
display_name = "Enemizer Groups"
option_mobs_only = 0
option_mobs_and_bosses = 1
option_mobs_bosses_and_dark_king = 2
default = 1
class ShuffleResWeakType(Toggle):
"""Resistance and Weakness types are shuffled for all enemies."""
display_name = "Shuffle Resistance/Weakness Types"
default = 0
class ShuffleEnemiesPositions(Toggle):
"""Instead of their original position in a given map, enemies are randomly placed."""
display_name = "Shuffle Enemies' Positions"
@@ -231,6 +246,81 @@ class BattlefieldsBattlesQuantities(Choice):
option_random_one_through_ten = 6
class CompanionLevelingType(Choice):
"""Set how companions gain levels.
Quests: Complete each companion's individual quest for them to promote to their second version.
Quests Extended: Each companion has four exclusive quests, leveling each time a quest is completed.
Save the Crystals (All): Each time a Crystal is saved, all companions gain levels.
Save the Crystals (Individual): Each companion will level to their second version when a specific Crystal is saved.
Benjamin Level: Companions' level tracks Benjamin's."""
option_quests = 0
option_quests_extended = 1
option_save_crystals_individual = 2
option_save_crystals_all = 3
option_benjamin_level = 4
option_benjamin_level_plus_5 = 5
option_benjamin_level_plus_10 = 6
default = 0
display_name = "Companion Leveling Type"
class CompanionSpellbookType(Choice):
"""Update companions' spellbook.
Standard: Original game spellbooks.
Extended: Add some extra spells. Tristam gains Exit and Quake and Reuben gets Blizzard.
Random Balanced: Randomize the spellbooks with an appropriate mix of spells.
Random Chaos: Randomize the spellbooks in total free-for-all."""
option_standard = 0
option_extended = 1
option_random_balanced = 2
option_random_chaos = 3
default = 0
display_name = "Companion Spellbook Type"
class StartingCompanion(Choice):
"""Set a companion to start with.
Random Companion: Randomly select one companion.
Random Plus None: Randomly select a companion, with the possibility of none selected."""
display_name = "Starting Companion"
default = 0
option_none = 0
option_kaeli = 1
option_tristam = 2
option_phoebe = 3
option_reuben = 4
option_random_companion = 5
option_random_plus_none = 6
class AvailableCompanions(Range):
"""Select randomly which companions will join your party. Unavailable companions can still be reached to get their items and complete their quests if needed.
Note: If a Starting Companion is selected, it will always be available, regardless of this setting."""
display_name = "Available Companions"
default = 4
range_start = 0
range_end = 4
class CompanionsLocations(Choice):
"""Set the primary location of companions. Their secondary location is always the same.
Standard: Companions will be at the same locations as in the original game.
Shuffled: Companions' locations are shuffled amongst themselves.
Shuffled Extended: Add all the Temples, as well as Phoebe's House and the Rope Bridge as possible locations."""
display_name = "Companions' Locations"
default = 0
option_standard = 0
option_shuffled = 1
option_shuffled_extended = 2
class KaelisMomFightsMinotaur(Toggle):
"""Transfer Kaeli's requirements (Tree Wither, Elixir) and the two items she's giving to her mom.
Kaeli will be available to join the party right away without the Tree Wither."""
display_name = "Kaeli's Mom Fights Minotaur"
default = 0
option_definitions = {
"logic": Logic,
"brown_boxes": BrownBoxes,
@@ -238,12 +328,21 @@ option_definitions = {
"shattered_sky_coin_quantity": ShatteredSkyCoinQuantity,
"starting_weapon": StartingWeapon,
"progressive_gear": ProgressiveGear,
"leveling_curve": LevelingCurve,
"starting_companion": StartingCompanion,
"available_companions": AvailableCompanions,
"companions_locations": CompanionsLocations,
"kaelis_mom_fight_minotaur": KaelisMomFightsMinotaur,
"companion_leveling_type": CompanionLevelingType,
"companion_spellbook_type": CompanionSpellbookType,
"enemies_density": EnemiesDensity,
"enemies_scaling_lower": EnemiesScalingLower,
"enemies_scaling_upper": EnemiesScalingUpper,
"bosses_scaling_lower": BossesScalingLower,
"bosses_scaling_upper": BossesScalingUpper,
"enemizer_attacks": EnemizerAttacks,
"enemizer_groups": EnemizerGroups,
"shuffle_res_weak_types": ShuffleResWeakType,
"shuffle_enemies_position": ShuffleEnemiesPositions,
"progressive_formations": ProgressiveFormations,
"doom_castle_mode": DoomCastle,
@@ -253,6 +352,5 @@ option_definitions = {
"crest_shuffle": CrestShuffle,
"shuffle_battlefield_rewards": ShuffleBattlefieldRewards,
"map_shuffle_seed": MapShuffleSeed,
"leveling_curve": LevelingCurve,
"battlefields_battles_quantities": BattlefieldsBattlesQuantities,
}
+47 -35
View File
@@ -3,7 +3,7 @@ import os
import zipfile
from copy import deepcopy
from .Regions import object_id_table
from Main import __version__
from Utils import __version__
from worlds.Files import APContainer
import pkgutil
@@ -35,46 +35,58 @@ def generate_output(self, output_directory):
"item_name": location.item.name})
def cc(option):
return option.current_key.title().replace("_", "").replace("OverworldAndDungeons", "OverworldDungeons")
return option.current_key.title().replace("_", "").replace("OverworldAndDungeons",
"OverworldDungeons").replace("MobsAndBosses", "MobsBosses").replace("MobsBossesAndDarkKing",
"MobsBossesDK").replace("BenjaminLevelPlus", "BenPlus").replace("BenjaminLevel", "BenPlus0").replace(
"RandomCompanion", "Random")
def tf(option):
return True if option else False
options = deepcopy(settings_template)
options["name"] = self.multiworld.player_name[self.player]
option_writes = {
"enemies_density": cc(self.multiworld.enemies_density[self.player]),
"chests_shuffle": "Include",
"shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle",
"npcs_shuffle": "Include",
"battlefields_shuffle": "Include",
"logic_options": cc(self.multiworld.logic[self.player]),
"shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]),
"enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]),
"enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]),
"bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]),
"bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]),
"enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]),
"leveling_curve": cc(self.multiworld.leveling_curve[self.player]),
"battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if
self.multiworld.battlefields_battles_quantities[self.player].value < 5 else
"RandomLow" if
self.multiworld.battlefields_battles_quantities[self.player].value == 5 else
"RandomHigh",
"shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]),
"random_starting_weapon": True,
"progressive_gear": tf(self.multiworld.progressive_gear[self.player]),
"tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]),
"doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]),
"doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]),
"sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]),
"sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]),
"enable_spoilers": False,
"progressive_formations": cc(self.multiworld.progressive_formations[self.player]),
"map_shuffling": cc(self.multiworld.map_shuffle[self.player]),
"crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]),
}
"enemies_density": cc(self.multiworld.enemies_density[self.player]),
"chests_shuffle": "Include",
"shuffle_boxes_content": self.multiworld.brown_boxes[self.player] == "shuffle",
"npcs_shuffle": "Include",
"battlefields_shuffle": "Include",
"logic_options": cc(self.multiworld.logic[self.player]),
"shuffle_enemies_position": tf(self.multiworld.shuffle_enemies_position[self.player]),
"enemies_scaling_lower": cc(self.multiworld.enemies_scaling_lower[self.player]),
"enemies_scaling_upper": cc(self.multiworld.enemies_scaling_upper[self.player]),
"bosses_scaling_lower": cc(self.multiworld.bosses_scaling_lower[self.player]),
"bosses_scaling_upper": cc(self.multiworld.bosses_scaling_upper[self.player]),
"enemizer_attacks": cc(self.multiworld.enemizer_attacks[self.player]),
"leveling_curve": cc(self.multiworld.leveling_curve[self.player]),
"battles_quantity": cc(self.multiworld.battlefields_battles_quantities[self.player]) if
self.multiworld.battlefields_battles_quantities[self.player].value < 5 else
"RandomLow" if
self.multiworld.battlefields_battles_quantities[self.player].value == 5 else
"RandomHigh",
"shuffle_battlefield_rewards": tf(self.multiworld.shuffle_battlefield_rewards[self.player]),
"random_starting_weapon": True,
"progressive_gear": tf(self.multiworld.progressive_gear[self.player]),
"tweaked_dungeons": tf(self.multiworld.tweak_frustrating_dungeons[self.player]),
"doom_castle_mode": cc(self.multiworld.doom_castle_mode[self.player]),
"doom_castle_shortcut": tf(self.multiworld.doom_castle_shortcut[self.player]),
"sky_coin_mode": cc(self.multiworld.sky_coin_mode[self.player]),
"sky_coin_fragments_qty": cc(self.multiworld.shattered_sky_coin_quantity[self.player]),
"enable_spoilers": False,
"progressive_formations": cc(self.multiworld.progressive_formations[self.player]),
"map_shuffling": cc(self.multiworld.map_shuffle[self.player]),
"crest_shuffle": tf(self.multiworld.crest_shuffle[self.player]),
"enemizer_groups": cc(self.multiworld.enemizer_groups[self.player]),
"shuffle_res_weak_type": tf(self.multiworld.shuffle_res_weak_types[self.player]),
"companion_leveling_type": cc(self.multiworld.companion_leveling_type[self.player]),
"companion_spellbook_type": cc(self.multiworld.companion_spellbook_type[self.player]),
"starting_companion": cc(self.multiworld.starting_companion[self.player]),
"available_companions": ["Zero", "One", "Two",
"Three", "Four"][self.multiworld.available_companions[self.player].value],
"companions_locations": cc(self.multiworld.companions_locations[self.player]),
"kaelis_mom_fight_minotaur": tf(self.multiworld.kaelis_mom_fight_minotaur[self.player]),
}
for option, data in option_writes.items():
options["Final Fantasy Mystic Quest"][option][data] = 1
@@ -83,7 +95,7 @@ def generate_output(self, output_directory):
'utf8')
self.rom_name_available_event.set()
setup = {"version": "1.4", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed":
setup = {"version": "1.5", "name": self.multiworld.player_name[self.player], "romname": rom_name, "seed":
hex(self.multiworld.per_slot_randoms[self.player].randint(0, 0xFFFFFFFF)).split("0x")[1].upper()}
starting_items = [output_item_name(item) for item in self.multiworld.precollected_items[self.player]]
+4 -4
View File
@@ -67,10 +67,10 @@ def create_regions(self):
self.multiworld.regions.append(create_region(self.multiworld, self.player, room["name"], room["id"],
[FFMQLocation(self.player, object["name"], location_table[object["name"]] if object["name"] in
location_table else None, object["type"], object["access"],
self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for
object in room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in
("BattlefieldGp", "BattlefieldXp") and (object["type"] != "Box" or
self.multiworld.brown_boxes[self.player] == "include")], room["links"]))
self.create_item(yaml_item(object["on_trigger"][0])) if object["type"] == "Trigger" else None) for object in
room["game_objects"] if "Hero Chest" not in object["name"] and object["type"] not in ("BattlefieldGp",
"BattlefieldXp") and (object["type"] != "Box" or self.multiworld.brown_boxes[self.player] == "include") and
not (object["name"] == "Kaeli Companion" and not object["on_trigger"])], room["links"]))
dark_king_room = self.multiworld.get_region("Doom Castle Dark King Room", self.player)
dark_king = FFMQLocation(self.player, "Dark King", None, "Trigger", [])
+3 -1
View File
@@ -108,8 +108,10 @@ class FFMQWorld(World):
map_shuffle = multiworld.map_shuffle[world.player].value
crest_shuffle = multiworld.crest_shuffle[world.player].current_key
battlefield_shuffle = multiworld.shuffle_battlefield_rewards[world.player].current_key
companion_shuffle = multiworld.companions_locations[world.player].value
kaeli_mom = multiworld.kaelis_mom_fight_minotaur[world.player].current_key
query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}"
query = f"s={seed}&m={map_shuffle}&c={crest_shuffle}&b={battlefield_shuffle}&cs={companion_shuffle}&km={kaeli_mom}"
if query in rooms_data:
world.rooms = rooms_data[query]
+30 -5
View File
@@ -827,12 +827,12 @@
id: 164
area: 47
coordinates: [14, 6]
teleporter: [16, 2]
teleporter: [98, 8] # Script for reuben, original value [16, 2]
- name: Fireburg - Hotel
id: 165
area: 47
coordinates: [20, 8]
teleporter: [17, 2]
teleporter: [96, 8] # It's a script now for tristam, original value [17, 2]
- name: Fireburg - GrenadeMan House Script
id: 166
area: 47
@@ -1178,6 +1178,16 @@
area: 60
coordinates: [2, 7]
teleporter: [123, 0]
- name: Lava Dome Pointless Room - Visit Quest Script 1
id: 490
area: 60
coordinates: [4, 4]
teleporter: [99, 8]
- name: Lava Dome Pointless Room - Visit Quest Script 2
id: 491
area: 60
coordinates: [4, 5]
teleporter: [99, 8]
- name: Lava Dome Lower Moon Helm Room - Left Entrance
id: 235
area: 60
@@ -1568,6 +1578,11 @@
area: 79
coordinates: [2, 45]
teleporter: [174, 0]
- name: Mount Gale - Visit Quest
id: 494
area: 79
coordinates: [44, 7]
teleporter: [101, 8]
- name: Windia - Main Entrance 1
id: 312
area: 80
@@ -1613,11 +1628,11 @@
area: 80
coordinates: [21, 39]
teleporter: [30, 5]
- name: Windia - INN's Script # Change to teleporter
- name: Windia - INN's Script # Change to teleporter / Change back to script!
id: 321
area: 80
coordinates: [18, 34]
teleporter: [31, 2] # Original value [79, 8]
teleporter: [97, 8] # Original value [79, 8] > [31, 2]
- name: Windia - Vendor House
id: 322
area: 80
@@ -1697,7 +1712,7 @@
id: 337
area: 82
coordinates: [45, 24]
teleporter: [215, 0]
teleporter: [102, 8] # Changed to script, original value [215, 0]
- name: Windia Inn Lobby - Exit
id: 338
area: 82
@@ -1998,6 +2013,16 @@
area: 95
coordinates: [29, 37]
teleporter: [70, 8]
- name: Light Temple - Visit Quest Script 1
id: 492
area: 95
coordinates: [34, 39]
teleporter: [100, 8]
- name: Light Temple - Visit Quest Script 2
id: 493
area: 95
coordinates: [35, 39]
teleporter: [100, 8]
- name: Ship Dock - Mobius Teleporter Script
id: 397
area: 96
+16 -16
View File
@@ -309,13 +309,13 @@
location: "WindiaBattlefield01"
location_slot: "WindiaBattlefield01"
type: "BattlefieldXp"
access: []
access: ["SandCoin", "RiverCoin"]
- name: "South of Windia Battlefield"
object_id: 0x14
location: "WindiaBattlefield02"
location_slot: "WindiaBattlefield02"
type: "BattlefieldXp"
access: []
access: ["SandCoin", "RiverCoin"]
links:
- target_room: 9 # Focus Tower Windia
location: "FocusTowerWindia"
@@ -739,7 +739,7 @@
object_id: 0x2E
type: "Box"
access: []
- name: "Kaeli 1"
- name: "Kaeli Companion"
object_id: 0
type: "Trigger"
on_trigger: ["Kaeli1"]
@@ -838,7 +838,7 @@
- name: Sand Temple
id: 24
game_objects:
- name: "Tristam Sand Temple"
- name: "Tristam Companion"
object_id: 0
type: "Trigger"
on_trigger: ["Tristam"]
@@ -883,6 +883,11 @@
object_id: 2
type: "NPC"
access: ["Tristam"]
- name: "Tristam Bone Dungeon Item Given"
object_id: 0
type: "Trigger"
on_trigger: ["TristamBoneItemGiven"]
access: ["Tristam"]
links:
- target_room: 25
entrance: 59
@@ -1080,7 +1085,7 @@
object_id: 0x40
type: "Box"
access: []
- name: "Phoebe"
- name: "Phoebe Companion"
object_id: 0
type: "Trigger"
on_trigger: ["Phoebe1"]
@@ -1846,11 +1851,11 @@
access: []
- target_room: 77
entrance: 164
teleporter: [16, 2]
teleporter: [98, 8] # original value [16, 2]
access: []
- target_room: 82
entrance: 165
teleporter: [17, 2]
teleporter: [96, 8] # original value [17, 2]
access: []
- target_room: 208
access: ["Claw"]
@@ -1875,7 +1880,7 @@
object_id: 14
type: "NPC"
access: ["ReubenDadSaved"]
- name: "Reuben"
- name: "Reuben Companion"
object_id: 0
type: "Trigger"
on_trigger: ["Reuben1"]
@@ -1951,12 +1956,7 @@
- name: "Fireburg - Tristam"
object_id: 10
type: "NPC"
access: []
- name: "Tristam Fireburg"
object_id: 0
type: "Trigger"
on_trigger: ["Tristam"]
access: []
access: ["Tristam", "TristamBoneItemGiven"]
links:
- target_room: 76
entrance: 177
@@ -3183,7 +3183,7 @@
access: []
- target_room: 163
entrance: 321
teleporter: [31, 2]
teleporter: [97, 8]
access: []
- target_room: 165
entrance: 322
@@ -3292,7 +3292,7 @@
access: []
- target_room: 164
entrance: 337
teleporter: [215, 0]
teleporter: [102, 8]
access: []
- name: Windia Inn Beds
id: 164
+44 -1
View File
@@ -73,14 +73,57 @@ Final Fantasy Mystic Quest:
Chaos: 0
SelfDestruct: 0
SimpleShuffle: 0
enemizer_groups:
MobsOnly: 0
MobsBosses: 0
MobsBossesDK: 0
shuffle_res_weak_type:
true: 0
false: 0
leveling_curve:
Half: 0
Normal: 0
OneAndHalf: 0
Double: 0
DoubleHalf: 0
DoubleAndHalf: 0
Triple: 0
Quadruple: 0
companion_leveling_type:
Quests: 0
QuestsExtended: 0
SaveCrystalsIndividual: 0
SaveCrystalsAll: 0
BenPlus0: 0
BenPlus5: 0
BenPlus10: 0
companion_spellbook_type:
Standard: 0
Extended: 0
RandomBalanced: 0
RandomChaos: 0
starting_companion:
None: 0
Kaeli: 0
Tristam: 0
Phoebe: 0
Reuben: 0
Random: 0
RandomPlusNone: 0
available_companions:
Zero: 0
One: 0
Two: 0
Three: 0
Four: 0
Random14: 0
Random04: 0
companions_locations:
Standard: 0
Shuffled: 0
ShuffledExtended: 0
kaelis_mom_fight_minotaur:
true: 0
false: 0
battles_quantity:
Ten: 0
Seven: 0
+3 -3
View File
@@ -2,7 +2,7 @@
## Required Software
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `SNI Client`
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- Hardware or software capable of loading and playing SNES ROM files
- An emulator capable of connecting to SNI such as:
@@ -19,8 +19,8 @@ The Archipelago community cannot supply you with this.
### Windows Setup
1. During the installation of Archipelago, you will have been asked to install the SNI Client. If you did not do this,
or you are on an older version, you may run the installer again to install the SNI Client.
1. Download and install [Archipelago](<https://github.com/ArchipelagoMW/Archipelago/releases/latest>). **The installer
file is located in the assets section at the bottom of the version information.**
2. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
files.
1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
+8 -4
View File
@@ -1,4 +1,5 @@
import collections
import logging
import typing
from BaseClasses import LocationProgressType, MultiWorld, Location, Region, Entrance
@@ -81,15 +82,18 @@ def locality_rules(world: MultiWorld):
i.name not in sending_blockers[i.player] and old_rule(i)
def exclusion_rules(world: MultiWorld, player: int, exclude_locations: typing.Set[str]) -> None:
def exclusion_rules(multiworld: MultiWorld, player: int, exclude_locations: typing.Set[str]) -> None:
for loc_name in exclude_locations:
try:
location = world.get_location(loc_name, player)
location = multiworld.get_location(loc_name, player)
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
if loc_name not in world.worlds[player].location_name_to_id:
if loc_name not in multiworld.worlds[player].location_name_to_id:
raise Exception(f"Unable to exclude location {loc_name} in player {player}'s world.") from e
else:
location.progress_type = LocationProgressType.EXCLUDED
if not location.event:
location.progress_type = LocationProgressType.EXCLUDED
else:
logging.warning(f"Unable to exclude location {loc_name} in player {player}'s world.")
def set_rule(spot: typing.Union["BaseClasses.Location", "BaseClasses.Entrance"], rule: CollectionRule):
+35 -26
View File
@@ -17,19 +17,22 @@ The most recent public release of Archipelago can be found on the GitHub Release
Run the exe file, and after accepting the license agreement you will be asked which components you would like to
install.
The generator allows you to generate multiworld games on your computer. The ROM setups are required if anyone in the
game that you generate wants to play any of those games as they are needed to generate the relevant patch files. If you
do not own the game, uncheck the relevant box. If you gain the game later, the installer can be run again to install and
set up new components.
Archipelago installations are automatically bundled with some programs. These include a launcher, a generator, a
server and some clients.
The server will allow you to host the multiworld on your machine. Hosting on your machine requires forwarding the port
- The launcher lets you quickly access Archipelago's different components and programs. It is found under the name
`ArchipelagoLauncher` and can be found in the main directory of your Archipelago installation.
- The generator allows you to generate multiworld games on your computer. Please refer to the 'Generating a game'
section of this guide for more information about it.
- The server will allow you to host the multiworld on your machine. Hosting on your machine requires forwarding the port
you are hosting on. The default port for Archipelago is `38281`. If you are unsure how to do this there are plenty of
other guides on the internet that will be more suited to your hardware.
The `Clients` are what are used to connect your game to the multiworld. If the game you plan to play is available
here, go ahead and install its client as well. If the game you choose to play is supported by Archipelago but not listed
in the installation, check the setup guide for that game. Installing a client for a ROM based game requires you to have
a legally obtained ROM for that game as well.
- The clients are what are used to connect your game to the multiworld. Some games use a client that is automatically
installed with an Archipelago installation. You can access those clients via the launcher or by navigating
to your Archipelago installation.
## Generating a game
@@ -72,14 +75,18 @@ If you have downloaded the settings, or have created a settings file manually, t
#### On your local installation
To generate a game on your local machine, make sure to install the Archipelago software, and ensure to select the
`Generator` component, as well as the `ROM setup` for any games you will want to play. Navigate to your Archipelago
To generate a game on your local machine, make sure to install the Archipelago software. Navigate to your Archipelago
installation (usually C:\ProgramData\Archipelago), and place the settings file you have either created or downloaded
from the website in the `Players` folder.
Run `ArchipelagoGenerate.exe`, and it will inform you whether the generation was successful or not. If successful, there
will be an output zip in the `output` folder (usually named something like `AP_XXXXX.zip`). This will contain all
relevant information to the session, including the spoiler log, if one was generated.
Run `ArchipelagoGenerate.exe`, or click on `Generate` in the launcher, and it will inform you whether the generation
was successful or not. If successful, there will be an output zip in the `output` folder
(usually named something like `AP_XXXXX.zip`). This will contain all relevant information to the session, including the
spoiler log, if one was generated.
Please note that some games require you to own their ROM files to generate with them as they are needed to generate the
relevant patch files. When you generate with a ROM game for the first time, you will be asked to locate its base ROM file.
This step only needs to be done once.
### Generating a multiplayer game
@@ -97,12 +104,9 @@ player name.
#### On the website
Gather all player YAML files into a single place, and compress them into a zip file. This can be done by pressing
ctrl/cmd + clicking on each file until all are selected, right-clicking one of the files, and clicking
`compress to ZIP file` or `send to > compressed folder`.
Navigate to the [Generate Page](/generate), select the host settings you would like, click on `Upload File`, and
select the newly created zip from the opened window.
Gather all player YAML files into a single place, then navigate to the [Generate Page](/generate). Select the host settings
you would like, click on `Upload File(s)`, and select all player YAML files. The site also accepts `zip` archives containing YAML
files.
After some time, you will be redirected to a seed info page that will display the generated seed, the time it was
created, the number of players, the spoiler (if one was created) and all rooms created from this seed.
@@ -114,8 +118,11 @@ It is possible to generate the multiworld locally, using a local Archipelago ins
Archipelago installation folder (usually C:\ProgramData\Archipelago) and placing each YAML file in the `Players` folder.
If the folder does not exist then it must be created manually. The files here should not be compressed.
After filling the `Players` folder, the `ArchipelagoGenerate.exe` program should be run in order to generate a
multiworld. The output of this process is placed in the `output` folder (usually named something like `AP_XXXXX.zip`).
After filling the `Players` folder, run`ArchipelagoGenerate.exe` or click `Generate` in the launcher. The output of
the generation is placed in the `output` folder (usually named something like `AP_XXXXX.zip`).
Please note that if any player in the game you want to generate plays a game that needs a ROM file to generate, you will
need the corresponding ROM files.
##### Changing local host settings for generation
@@ -123,10 +130,12 @@ Sometimes there are various settings that you may want to change before rolling
auto-release, plando support, or setting a password.
All of these settings, plus other options, may be changed by modifying the `host.yaml` file in the Archipelago
installation folder. The settings chosen here are baked into the `.archipelago` file that gets output with the other
files after generation, so if you are rolling locally, ensure this file is edited to your liking **before** rolling the
seed. This file is overwritten when running the Archipelago Installation software. If you have changed settings in this
file, and would like to retain them, you may rename the file to `options.yaml`.
installation folder. You can quickly access this file by clicking on `Open host.yaml` in the launcher. The settings
chosen here are baked into the `.archipelago` file that gets output with the other files after generation, so if you
are rolling locally, ensure this file is edited to your liking **before** rolling the seed. This file is overwritten
when running the Archipelago Installation software. If you have changed settings in this file, and would like to retain
them, you may rename the file to `options.yaml`.
## Hosting an Archipelago Server
File diff suppressed because one or more lines are too long
+30 -17
View File
@@ -2,7 +2,7 @@ import typing
from .ExtractedData import logic_options, starts, pool_options
from .Rules import cost_terms
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange
from Options import Option, DefaultOnToggle, Toggle, Choice, Range, OptionDict, NamedRange, DeathLink
from .Charms import vanilla_costs, names as charm_names
if typing.TYPE_CHECKING:
@@ -402,22 +402,34 @@ class WhitePalace(Choice):
default = 0
class DeathLink(Choice):
class ExtraPlatforms(DefaultOnToggle):
"""Places additional platforms to make traveling throughout Hallownest more convenient."""
class DeathLinkShade(Choice):
"""Sets whether to create a shade when you are killed by a DeathLink and how to handle your existing shade, if any.
vanilla: DeathLink deaths function like any other death and overrides your existing shade (including geo), if any.
shadeless: DeathLink deaths do not spawn shades. Your existing shade (including geo), if any, is untouched.
shade: DeathLink deaths spawn a shade if you do not have an existing shade. Otherwise, it acts like shadeless.
* This option has no effect if DeathLink is disabled.
** Self-death shade behavior is not changed; if a self-death normally creates a shade in vanilla, it will override
your existing shade, if any.
"""
When you die, everyone dies. Of course the reverse is true too.
When enabled, choose how incoming deathlinks are handled:
vanilla: DeathLink kills you and is just like any other death. RIP your previous shade and geo.
shadeless: DeathLink kills you, but no shade spawns and no geo is lost. Your previous shade, if any, is untouched.
shade: DeathLink functions like a normal death if you do not already have a shade, shadeless otherwise.
"""
option_off = 0
alias_no = 0
alias_true = 1
alias_on = 1
alias_yes = 1
option_vanilla = 0
option_shadeless = 1
option_vanilla = 2
option_shade = 3
option_shade = 2
default = 2
class DeathLinkBreaksFragileCharms(Toggle):
"""Sets if fragile charms break when you are killed by a DeathLink.
* This option has no effect if DeathLink is disabled.
** Self-death fragile charm behavior is not changed; if a self-death normally breaks fragile charms in vanilla, it
will continue to do so.
"""
class StartingGeo(Range):
@@ -476,7 +488,8 @@ hollow_knight_options: typing.Dict[str, type(Option)] = {
**{
option.__name__: option
for option in (
StartLocation, Goal, WhitePalace, StartingGeo, DeathLink,
StartLocation, Goal, WhitePalace, ExtraPlatforms, StartingGeo,
DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms,
MinimumGeoPrice, MaximumGeoPrice,
MinimumGrubPrice, MaximumGrubPrice,
MinimumEssencePrice, MaximumEssencePrice,
@@ -488,7 +501,7 @@ hollow_knight_options: typing.Dict[str, type(Option)] = {
LegEaterShopSlots, GrubfatherRewardSlots,
SeerRewardSlots, ExtraShopSlots,
SplitCrystalHeart, SplitMothwingCloak, SplitMantisClaw,
CostSanity, CostSanityHybridChance,
CostSanity, CostSanityHybridChance
)
},
**cost_sanity_weights
+10 -11
View File
@@ -419,17 +419,16 @@ class HKWorld(World):
def set_rules(self):
world = self.multiworld
player = self.player
if world.logic[player] != 'nologic':
goal = world.Goal[player]
if goal == Goal.option_hollowknight:
world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player)
elif goal == Goal.option_siblings:
world.completion_condition[player] = lambda state: state._hk_siblings_ending(player)
elif goal == Goal.option_radiance:
world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player)
else:
# Any goal
world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player)
goal = world.Goal[player]
if goal == Goal.option_hollowknight:
world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player)
elif goal == Goal.option_siblings:
world.completion_condition[player] = lambda state: state._hk_siblings_ending(player)
elif goal == Goal.option_radiance:
world.completion_condition[player] = lambda state: state._hk_can_beat_radiance(player)
else:
# Any goal
world.completion_condition[player] = lambda state: state._hk_can_beat_thk(player) or state._hk_can_beat_radiance(player)
set_rules(self)
+2
View File
@@ -444,6 +444,8 @@ def set_rules(hylics2world):
lambda state: paddle(state, player))
add_rule(world.get_location("Arcade 1: Alcove Medallion", player),
lambda state: paddle(state, player))
add_rule(world.get_location("Arcade 1: Lava Medallion", player),
lambda state: paddle(state, player))
add_rule(world.get_location("Foglast: Under Lair Medallion", player),
lambda state: bridge_key(state, player))
add_rule(world.get_location("Foglast: Mid-Air Medallion", player),
+82 -21
View File
@@ -29,6 +29,7 @@ class KH2Context(CommonContext):
self.kh2_local_items = None
self.growthlevel = None
self.kh2connected = False
self.kh2_finished_game = False
self.serverconneced = False
self.item_name_to_data = {name: data for name, data, in item_dictionary_table.items()}
self.location_name_to_data = {name: data for name, data, in all_locations.items()}
@@ -79,11 +80,6 @@ class KH2Context(CommonContext):
},
},
}
self.front_of_inventory = {
"Sora": 0x2546,
"Donald": 0x2658,
"Goofy": 0x276C,
}
self.kh2seedname = None
self.kh2slotdata = None
self.itemamount = {}
@@ -168,6 +164,14 @@ class KH2Context(CommonContext):
self.ability_code_list = None
self.master_growth = {"High Jump", "Quick Run", "Dodge Roll", "Aerial Dodge", "Glide"}
self.base_hp = 20
self.base_mp = 100
self.base_drive = 5
self.base_accessory_slots = 1
self.base_armor_slots = 1
self.base_item_slots = 3
self.front_ability_slots = [0x2546, 0x2658, 0x276C, 0x2548, 0x254A, 0x254C, 0x265A, 0x265C, 0x265E, 0x276E, 0x2770, 0x2772]
async def server_auth(self, password_requested: bool = False):
if password_requested and not self.password:
await super(KH2Context, self).server_auth(password_requested)
@@ -218,6 +222,12 @@ class KH2Context(CommonContext):
def kh2_read_byte(self, address):
return int.from_bytes(self.kh2.read_bytes(self.kh2.base_address + address, 1), "big")
def kh2_read_int(self, address):
return self.kh2.read_int(self.kh2.base_address + address)
def kh2_write_int(self, address, value):
self.kh2.write_int(self.kh2.base_address + address, value)
def on_package(self, cmd: str, args: dict):
if cmd in {"RoomInfo"}:
self.kh2seedname = args['seed_name']
@@ -475,7 +485,7 @@ class KH2Context(CommonContext):
async def give_item(self, item, location):
try:
# todo: ripout all the itemtype stuff and just have one dictionary. the only thing that needs to be tracked from the server/local is abilites
# todo: ripout all the itemtype stuff and just have one dictionary. the only thing that needs to be tracked from the server/local is abilites
itemname = self.lookup_id_to_item[item]
itemdata = self.item_name_to_data[itemname]
# itemcode = self.kh2_item_name_to_id[itemname]
@@ -506,6 +516,8 @@ class KH2Context(CommonContext):
ability_slot = self.kh2_seed_save_cache["GoofyInvo"][1]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["GoofyInvo"][1] -= 2
if ability_slot in self.front_ability_slots:
self.front_ability_slots.remove(ability_slot)
elif len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < \
self.AbilityQuantityDict[itemname]:
@@ -517,11 +529,14 @@ class KH2Context(CommonContext):
ability_slot = self.kh2_seed_save_cache["DonaldInvo"][0]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["DonaldInvo"][0] -= 2
elif itemname in self.goofy_ability_set:
else:
ability_slot = self.kh2_seed_save_cache["GoofyInvo"][0]
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
self.kh2_seed_save_cache["GoofyInvo"][0] -= 2
if ability_slot in self.front_ability_slots:
self.front_ability_slots.remove(ability_slot)
elif itemdata.memaddr in {0x36C4, 0x36C5, 0x36C6, 0x36C0, 0x36CA}:
# if memaddr is in a bitmask location in memory
if itemname not in self.kh2_seed_save_cache["AmountInvo"]["Bitmask"]:
@@ -614,7 +629,7 @@ class KH2Context(CommonContext):
master_sell = master_equipment | master_staff | master_shield
await asyncio.create_task(self.IsInShop(master_sell))
# print(self.kh2_seed_save_cache["AmountInvo"]["Ability"])
for item_name in master_amount:
item_data = self.item_name_to_data[item_name]
amount_of_items = 0
@@ -672,10 +687,10 @@ class KH2Context(CommonContext):
self.kh2_write_short(self.Save + slot, item_data.memaddr)
# removes the duped ability if client gave faster than the game.
for charInvo in {"Sora", "Donald", "Goofy"}:
if self.kh2_read_short(self.Save + self.front_of_inventory[charInvo]) != 0:
print(f"removed {self.Save + self.front_of_inventory[charInvo]} from {charInvo}")
self.kh2_write_short(self.Save + self.front_of_inventory[charInvo], 0)
for ability in self.front_ability_slots:
if self.kh2_read_short(self.Save + ability) != 0:
print(f"removed {self.Save + ability} from {ability}")
self.kh2_write_short(self.Save + ability, 0)
# remove the dummy level 1 growths if they are in these invo slots.
for inventorySlot in {0x25CE, 0x25D0, 0x25D2, 0x25D4, 0x25D6, 0x25D8}:
@@ -739,15 +754,60 @@ class KH2Context(CommonContext):
self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items)
for item_name in master_stat:
item_data = self.item_name_to_data[item_name]
amount_of_items = 0
amount_of_items += self.kh2_seed_save_cache["AmountInvo"]["StatIncrease"][item_name]
if self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5:
if item_name == ItemName.MaxHPUp:
if self.kh2_read_byte(self.Save + 0x2498) < 3: # Non-Critical
Bonus = 5
else: # Critical
Bonus = 2
if self.kh2_read_int(self.Slot1 + 0x004) != self.base_hp + (Bonus * amount_of_items):
self.kh2_write_int(self.Slot1 + 0x004, self.base_hp + (Bonus * amount_of_items))
elif item_name == ItemName.MaxMPUp:
if self.kh2_read_byte(self.Save + 0x2498) < 3: # Non-Critical
Bonus = 10
else: # Critical
Bonus = 5
if self.kh2_read_int(self.Slot1 + 0x184) != self.base_mp + (Bonus * amount_of_items):
self.kh2_write_int(self.Slot1 + 0x184, self.base_mp + (Bonus * amount_of_items))
elif item_name == ItemName.DriveGaugeUp:
current_max_drive = self.kh2_read_byte(self.Slot1 + 0x1B2)
# change when max drive is changed from 6 to 4
if current_max_drive < 9 and current_max_drive != self.base_drive + amount_of_items:
self.kh2_write_byte(self.Slot1 + 0x1B2, self.base_drive + amount_of_items)
elif item_name == ItemName.AccessorySlotUp:
current_accessory = self.kh2_read_byte(self.Save + 0x2501)
if current_accessory != self.base_accessory_slots + amount_of_items:
if 4 > current_accessory < self.base_accessory_slots + amount_of_items:
self.kh2_write_byte(self.Save + 0x2501, current_accessory + 1)
elif self.base_accessory_slots + amount_of_items < 4:
self.kh2_write_byte(self.Save + 0x2501, self.base_accessory_slots + amount_of_items)
elif item_name == ItemName.ArmorSlotUp:
current_armor_slots = self.kh2_read_byte(self.Save + 0x2500)
if current_armor_slots != self.base_armor_slots + amount_of_items:
if 4 > current_armor_slots < self.base_armor_slots + amount_of_items:
self.kh2_write_byte(self.Save + 0x2500, current_armor_slots + 1)
elif self.base_armor_slots + amount_of_items < 4:
self.kh2_write_byte(self.Save + 0x2500, self.base_armor_slots + amount_of_items)
elif item_name == ItemName.ItemSlotUp:
current_item_slots = self.kh2_read_byte(self.Save + 0x2502)
if current_item_slots != self.base_item_slots + amount_of_items:
if 8 > current_item_slots < self.base_item_slots + amount_of_items:
self.kh2_write_byte(self.Save + 0x2502, current_item_slots + 1)
elif self.base_item_slots + amount_of_items < 8:
self.kh2_write_byte(self.Save + 0x2502, self.base_item_slots + amount_of_items)
# if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items \
# and self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and \
# self.kh2_read_byte(self.Save + 0x23DF) & 0x1 << 3 > 0 and self.kh2_read_byte(0x741320) in {10, 8}:
# self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items)
# if slot1 has 5 drive gauge and goa lost illusion is checked and they are not in a cutscene
if self.kh2_read_byte(self.Save + item_data.memaddr) != amount_of_items \
and self.kh2_read_byte(self.Slot1 + 0x1B2) >= 5 and \
self.kh2_read_byte(self.Save + 0x23DF) & 0x1 << 3 > 0 and self.kh2_read_byte(0x741320) in {10, 8}:
self.kh2_write_byte(self.Save + item_data.memaddr, amount_of_items)
if "PoptrackerVersionCheck" in self.kh2slotdata:
if self.kh2slotdata["PoptrackerVersionCheck"] > 4.2 and self.kh2_read_byte(self.Save + 0x3607) != 1: # telling the goa they are on version 4.3
self.kh2_write_byte(self.Save + 0x3607, 1)
@@ -761,7 +821,8 @@ class KH2Context(CommonContext):
def finishedGame(ctx: KH2Context, message):
if ctx.kh2slotdata['FinalXemnas'] == 1:
if not ctx.final_xemnas and ctx.kh2_loc_name_to_id[LocationName.FinalXemnas] in ctx.locations_checked:
if not ctx.final_xemnas and ctx.kh2_read_byte(ctx.Save + all_world_locations[LocationName.FinalXemnas].addrObtained) \
& 0x1 << all_world_locations[LocationName.FinalXemnas].bitIndex > 0:
ctx.final_xemnas = True
# three proofs
if ctx.kh2slotdata['Goal'] == 0:
@@ -833,9 +894,9 @@ async def kh2_watcher(ctx: KH2Context):
await asyncio.create_task(ctx.verifyItems())
await asyncio.create_task(ctx.verifyLevel())
message = [{"cmd": 'LocationChecks', "locations": ctx.sending}]
if finishedGame(ctx, message):
if finishedGame(ctx, message) and not ctx.kh2_finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
ctx.kh2_finished_game = True
await ctx.send_msgs(message)
elif not ctx.kh2connected and ctx.serverconneced:
logger.info("Game Connection lost. waiting 15 seconds until trying to reconnect.")
+3 -18
View File
@@ -2,22 +2,7 @@ import typing
from BaseClasses import Item
from .Names import ItemName
class KH2Item(Item):
game: str = "Kingdom Hearts 2"
class ItemData(typing.NamedTuple):
quantity: int = 0
kh2id: int = 0
# Save+ mem addr
memaddr: int = 0
# some items have bitmasks. if bitmask>0 bitor to give item else
bitmask: int = 0
# if ability then
ability: bool = False
from .Subclasses import ItemData
# 0x130000
Reports_Table = {
@@ -209,7 +194,7 @@ Armor_Table = {
ItemName.GrandRibbon: ItemData(1, 157, 0x35D4),
}
Usefull_Table = {
ItemName.MickeyMunnyPouch: ItemData(1, 535, 0x3695), # 5000 munny per
ItemName.MickeyMunnyPouch: ItemData(1, 535, 0x3695), # 5000 munny per
ItemName.OletteMunnyPouch: ItemData(2, 362, 0x363C), # 2500 munny per
ItemName.HadesCupTrophy: ItemData(1, 537, 0x3696),
ItemName.UnknownDisk: ItemData(1, 462, 0x365F),
@@ -349,7 +334,7 @@ GoofyAbility_Table = {
Wincon_Table = {
ItemName.LuckyEmblem: ItemData(kh2id=367, memaddr=0x3641), # letter item
ItemName.Victory: ItemData(kh2id=263, memaddr=0x111),
# ItemName.Victory: ItemData(kh2id=263, memaddr=0x111),
ItemName.Bounty: ItemData(kh2id=461, memaddr=0x365E), # Dummy 14
# ItemName.UniversalKey:ItemData(,365,0x363F,0)#Tournament Poster
}
+71 -74
View File
@@ -1,19 +1,9 @@
import typing
from BaseClasses import Location
from .Names import LocationName, ItemName
class KH2Location(Location):
game: str = "Kingdom Hearts 2"
class LocationData(typing.NamedTuple):
locid: int
yml: str
charName: str = "Sora"
charNumber: int = 1
from .Names import LocationName, ItemName, RegionName
from .Subclasses import LocationData
from .Regions import KH2REGIONS
# data's addrcheck sys3 addr obtained roomid bit index is eventid
LoD_Checks = {
@@ -541,7 +531,7 @@ TWTNW_Checks = {
LocationName.Xemnas1: LocationData(26, "Double Get Bonus"),
LocationName.Xemnas1GetBonus: LocationData(26, "Second Get Bonus"),
LocationName.Xemnas1SecretAnsemReport13: LocationData(537, "Chest"),
LocationName.FinalXemnas: LocationData(71, "Get Bonus"),
# LocationName.FinalXemnas: LocationData(71, "Get Bonus"),
LocationName.XemnasDataPowerBoost: LocationData(554, "Chest"),
}
@@ -806,74 +796,75 @@ Atlantica_Checks = {
}
event_location_to_item = {
LocationName.HostileProgramEventLocation: ItemName.HostileProgramEvent,
LocationName.McpEventLocation: ItemName.McpEvent,
LocationName.HostileProgramEventLocation: ItemName.HostileProgramEvent,
LocationName.McpEventLocation: ItemName.McpEvent,
# LocationName.ASLarxeneEventLocation: ItemName.ASLarxeneEvent,
LocationName.DataLarxeneEventLocation: ItemName.DataLarxeneEvent,
LocationName.BarbosaEventLocation: ItemName.BarbosaEvent,
LocationName.GrimReaper1EventLocation: ItemName.GrimReaper1Event,
LocationName.GrimReaper2EventLocation: ItemName.GrimReaper2Event,
LocationName.DataLuxordEventLocation: ItemName.DataLuxordEvent,
LocationName.DataAxelEventLocation: ItemName.DataAxelEvent,
LocationName.CerberusEventLocation: ItemName.CerberusEvent,
LocationName.OlympusPeteEventLocation: ItemName.OlympusPeteEvent,
LocationName.HydraEventLocation: ItemName.HydraEvent,
LocationName.DataLarxeneEventLocation: ItemName.DataLarxeneEvent,
LocationName.BarbosaEventLocation: ItemName.BarbosaEvent,
LocationName.GrimReaper1EventLocation: ItemName.GrimReaper1Event,
LocationName.GrimReaper2EventLocation: ItemName.GrimReaper2Event,
LocationName.DataLuxordEventLocation: ItemName.DataLuxordEvent,
LocationName.DataAxelEventLocation: ItemName.DataAxelEvent,
LocationName.CerberusEventLocation: ItemName.CerberusEvent,
LocationName.OlympusPeteEventLocation: ItemName.OlympusPeteEvent,
LocationName.HydraEventLocation: ItemName.HydraEvent,
LocationName.OcPainAndPanicCupEventLocation: ItemName.OcPainAndPanicCupEvent,
LocationName.OcCerberusCupEventLocation: ItemName.OcCerberusCupEvent,
LocationName.HadesEventLocation: ItemName.HadesEvent,
LocationName.OcCerberusCupEventLocation: ItemName.OcCerberusCupEvent,
LocationName.HadesEventLocation: ItemName.HadesEvent,
# LocationName.ASZexionEventLocation: ItemName.ASZexionEvent,
LocationName.DataZexionEventLocation: ItemName.DataZexionEvent,
LocationName.Oc2TitanCupEventLocation: ItemName.Oc2TitanCupEvent,
LocationName.Oc2GofCupEventLocation: ItemName.Oc2GofCupEvent,
LocationName.DataZexionEventLocation: ItemName.DataZexionEvent,
LocationName.Oc2TitanCupEventLocation: ItemName.Oc2TitanCupEvent,
LocationName.Oc2GofCupEventLocation: ItemName.Oc2GofCupEvent,
# LocationName.Oc2CupsEventLocation: ItemName.Oc2CupsEventLocation,
LocationName.HadesCupEventLocations: ItemName.HadesCupEvents,
LocationName.PrisonKeeperEventLocation: ItemName.PrisonKeeperEvent,
LocationName.OogieBoogieEventLocation: ItemName.OogieBoogieEvent,
LocationName.ExperimentEventLocation: ItemName.ExperimentEvent,
LocationName.HadesCupEventLocations: ItemName.HadesCupEvents,
LocationName.PrisonKeeperEventLocation: ItemName.PrisonKeeperEvent,
LocationName.OogieBoogieEventLocation: ItemName.OogieBoogieEvent,
LocationName.ExperimentEventLocation: ItemName.ExperimentEvent,
# LocationName.ASVexenEventLocation: ItemName.ASVexenEvent,
LocationName.DataVexenEventLocation: ItemName.DataVexenEvent,
LocationName.ShanYuEventLocation: ItemName.ShanYuEvent,
LocationName.AnsemRikuEventLocation: ItemName.AnsemRikuEvent,
LocationName.StormRiderEventLocation: ItemName.StormRiderEvent,
LocationName.DataXigbarEventLocation: ItemName.DataXigbarEvent,
LocationName.RoxasEventLocation: ItemName.RoxasEvent,
LocationName.XigbarEventLocation: ItemName.XigbarEvent,
LocationName.LuxordEventLocation: ItemName.LuxordEvent,
LocationName.SaixEventLocation: ItemName.SaixEvent,
LocationName.XemnasEventLocation: ItemName.XemnasEvent,
LocationName.ArmoredXemnasEventLocation: ItemName.ArmoredXemnasEvent,
LocationName.ArmoredXemnas2EventLocation: ItemName.ArmoredXemnas2Event,
LocationName.DataVexenEventLocation: ItemName.DataVexenEvent,
LocationName.ShanYuEventLocation: ItemName.ShanYuEvent,
LocationName.AnsemRikuEventLocation: ItemName.AnsemRikuEvent,
LocationName.StormRiderEventLocation: ItemName.StormRiderEvent,
LocationName.DataXigbarEventLocation: ItemName.DataXigbarEvent,
LocationName.RoxasEventLocation: ItemName.RoxasEvent,
LocationName.XigbarEventLocation: ItemName.XigbarEvent,
LocationName.LuxordEventLocation: ItemName.LuxordEvent,
LocationName.SaixEventLocation: ItemName.SaixEvent,
LocationName.XemnasEventLocation: ItemName.XemnasEvent,
LocationName.ArmoredXemnasEventLocation: ItemName.ArmoredXemnasEvent,
LocationName.ArmoredXemnas2EventLocation: ItemName.ArmoredXemnas2Event,
# LocationName.FinalXemnasEventLocation: ItemName.FinalXemnasEvent,
LocationName.DataXemnasEventLocation: ItemName.DataXemnasEvent,
LocationName.ThresholderEventLocation: ItemName.ThresholderEvent,
LocationName.BeastEventLocation: ItemName.BeastEvent,
LocationName.DarkThornEventLocation: ItemName.DarkThornEvent,
LocationName.XaldinEventLocation: ItemName.XaldinEvent,
LocationName.DataXaldinEventLocation: ItemName.DataXaldinEvent,
LocationName.TwinLordsEventLocation: ItemName.TwinLordsEvent,
LocationName.GenieJafarEventLocation: ItemName.GenieJafarEvent,
LocationName.DataXemnasEventLocation: ItemName.DataXemnasEvent,
LocationName.ThresholderEventLocation: ItemName.ThresholderEvent,
LocationName.BeastEventLocation: ItemName.BeastEvent,
LocationName.DarkThornEventLocation: ItemName.DarkThornEvent,
LocationName.XaldinEventLocation: ItemName.XaldinEvent,
LocationName.DataXaldinEventLocation: ItemName.DataXaldinEvent,
LocationName.TwinLordsEventLocation: ItemName.TwinLordsEvent,
LocationName.GenieJafarEventLocation: ItemName.GenieJafarEvent,
# LocationName.ASLexaeusEventLocation: ItemName.ASLexaeusEvent,
LocationName.DataLexaeusEventLocation: ItemName.DataLexaeusEvent,
LocationName.ScarEventLocation: ItemName.ScarEvent,
LocationName.GroundShakerEventLocation: ItemName.GroundShakerEvent,
LocationName.DataSaixEventLocation: ItemName.DataSaixEvent,
LocationName.HBDemyxEventLocation: ItemName.HBDemyxEvent,
LocationName.ThousandHeartlessEventLocation: ItemName.ThousandHeartlessEvent,
LocationName.Mushroom13EventLocation: ItemName.Mushroom13Event,
LocationName.SephiEventLocation: ItemName.SephiEvent,
LocationName.DataDemyxEventLocation: ItemName.DataDemyxEvent,
LocationName.CorFirstFightEventLocation: ItemName.CorFirstFightEvent,
LocationName.CorSecondFightEventLocation: ItemName.CorSecondFightEvent,
LocationName.TransportEventLocation: ItemName.TransportEvent,
LocationName.OldPeteEventLocation: ItemName.OldPeteEvent,
LocationName.FuturePeteEventLocation: ItemName.FuturePeteEvent,
LocationName.DataLexaeusEventLocation: ItemName.DataLexaeusEvent,
LocationName.ScarEventLocation: ItemName.ScarEvent,
LocationName.GroundShakerEventLocation: ItemName.GroundShakerEvent,
LocationName.DataSaixEventLocation: ItemName.DataSaixEvent,
LocationName.HBDemyxEventLocation: ItemName.HBDemyxEvent,
LocationName.ThousandHeartlessEventLocation: ItemName.ThousandHeartlessEvent,
LocationName.Mushroom13EventLocation: ItemName.Mushroom13Event,
LocationName.SephiEventLocation: ItemName.SephiEvent,
LocationName.DataDemyxEventLocation: ItemName.DataDemyxEvent,
LocationName.CorFirstFightEventLocation: ItemName.CorFirstFightEvent,
LocationName.CorSecondFightEventLocation: ItemName.CorSecondFightEvent,
LocationName.TransportEventLocation: ItemName.TransportEvent,
LocationName.OldPeteEventLocation: ItemName.OldPeteEvent,
LocationName.FuturePeteEventLocation: ItemName.FuturePeteEvent,
# LocationName.ASMarluxiaEventLocation: ItemName.ASMarluxiaEvent,
LocationName.DataMarluxiaEventLocation: ItemName.DataMarluxiaEvent,
LocationName.TerraEventLocation: ItemName.TerraEvent,
LocationName.TwilightThornEventLocation: ItemName.TwilightThornEvent,
LocationName.Axel1EventLocation: ItemName.Axel1Event,
LocationName.Axel2EventLocation: ItemName.Axel2Event,
LocationName.DataRoxasEventLocation: ItemName.DataRoxasEvent,
LocationName.DataMarluxiaEventLocation: ItemName.DataMarluxiaEvent,
LocationName.TerraEventLocation: ItemName.TerraEvent,
LocationName.TwilightThornEventLocation: ItemName.TwilightThornEvent,
LocationName.Axel1EventLocation: ItemName.Axel1Event,
LocationName.Axel2EventLocation: ItemName.Axel2Event,
LocationName.DataRoxasEventLocation: ItemName.DataRoxasEvent,
LocationName.FinalXemnasEventLocation: ItemName.Victory,
}
all_weapon_slot = {
LocationName.FAKESlot,
@@ -1361,3 +1352,9 @@ exclusion_table = {
location for location, data in all_locations.items() if location not in event_location_to_item.keys() and location not in popups_set and location != LocationName.StationofSerenityPotion and data.yml == "Chest"
}
}
location_groups: typing.Dict[str, list]
location_groups = {
Region_Name: [loc for loc in Region_Locs if "Event" not in loc]
for Region_Name, Region_Locs in KH2REGIONS.items() if Region_Locs and "Event" not in Region_Locs[0]
}
+2 -2
View File
@@ -606,11 +606,11 @@ hard_data_xemnas = {
ItemName.LimitForm: 1,
}
final_leveling_access = {
LocationName.MemorysSkyscaperMythrilCrystal,
LocationName.RoxasEventLocation,
LocationName.GrimReaper2,
LocationName.Xaldin,
LocationName.StormRider,
LocationName.SunsetTerraceAbilityRing
LocationName.UndergroundConcourseMythrilGem
}
multi_form_region_access = {
+1 -1
View File
@@ -2,7 +2,7 @@ from dataclasses import dataclass
from Options import Choice, Range, Toggle, ItemDict, PerGameCommonOptions, StartInventoryPool
from worlds.kh2 import default_itempool_option
from . import default_itempool_option
class SoraEXP(Range):
+8 -6
View File
@@ -1,9 +1,11 @@
import typing
from BaseClasses import MultiWorld, Region
from . import Locations
from .Locations import KH2Location, event_location_to_item
from . import LocationName, RegionName, Events_Table
from .Subclasses import KH2Location
from .Names import LocationName, RegionName
from .Items import Events_Table
KH2REGIONS: typing.Dict[str, typing.List[str]] = {
"Menu": [],
@@ -788,7 +790,7 @@ KH2REGIONS: typing.Dict[str, typing.List[str]] = {
LocationName.ArmoredXemnas2EventLocation
],
RegionName.FinalXemnas: [
LocationName.FinalXemnas
LocationName.FinalXemnasEventLocation
],
RegionName.DataXemnas: [
LocationName.XemnasDataPowerBoost,
@@ -1020,10 +1022,10 @@ def create_regions(self):
multiworld.regions += [create_region(multiworld, player, active_locations, region, locations) for region, locations in
KH2REGIONS.items()]
# fill the event locations with events
multiworld.worlds[player].item_name_to_id.update({event_name: None for event_name in Events_Table})
for location, item in event_location_to_item.items():
for location, item in Locations.event_location_to_item.items():
multiworld.get_location(location, player).place_locked_item(
multiworld.worlds[player].create_item(item))
multiworld.worlds[player].create_event_item(item))
def connect_regions(self):
+10 -7
View File
@@ -1,7 +1,7 @@
from typing import Dict, Callable, TYPE_CHECKING
from BaseClasses import CollectionState
from .Items import exclusion_item_table, visit_locking_dict, DonaldAbility_Table, GoofyAbility_Table
from .Items import exclusion_item_table, visit_locking_dict, DonaldAbility_Table, GoofyAbility_Table, SupportAbility_Table
from .Locations import exclusion_table, popups_set, Goofy_Checks, Donald_Checks
from .Names import LocationName, ItemName, RegionName
from worlds.generic.Rules import add_rule, forbid_items, add_item_rule
@@ -83,6 +83,8 @@ class KH2Rules:
return state.has(ItemName.TornPages, self.player, amount)
def level_locking_unlock(self, state: CollectionState, amount):
if self.world.options.Promise_Charm and state.has(ItemName.PromiseCharm, self.player):
return True
return amount <= sum([state.count(item_name, self.player) for item_name in visit_locking_dict["2VisitLocking"]])
def summon_levels_unlocked(self, state: CollectionState, amount) -> bool:
@@ -224,7 +226,7 @@ class KH2WorldRules(KH2Rules):
RegionName.Pl2: lambda state: self.pl_unlocked(state, 2),
RegionName.Ag: lambda state: self.ag_unlocked(state, 1),
RegionName.Ag2: lambda state: self.ag_unlocked(state, 2),
RegionName.Ag2: lambda state: self.ag_unlocked(state, 2) and self.kh2_has_all([ItemName.FireElement, ItemName.BlizzardElement, ItemName.ThunderElement], state),
RegionName.Bc: lambda state: self.bc_unlocked(state, 1),
RegionName.Bc2: lambda state: self.bc_unlocked(state, 2),
@@ -266,10 +268,11 @@ class KH2WorldRules(KH2Rules):
add_item_rule(location, lambda item: item.player == self.player and item.name in GoofyAbility_Table.keys())
elif location.name in Donald_Checks:
add_item_rule(location, lambda item: item.player == self.player and item.name in DonaldAbility_Table.keys())
else:
add_item_rule(location, lambda item: item.player == self.player and item.name in SupportAbility_Table.keys())
def set_kh2_goal(self):
final_xemnas_location = self.multiworld.get_location(LocationName.FinalXemnas, self.player)
final_xemnas_location = self.multiworld.get_location(LocationName.FinalXemnasEventLocation, self.player)
if self.multiworld.Goal[self.player] == "three_proofs":
final_xemnas_location.access_rule = lambda state: self.kh2_has_all(three_proofs, state)
if self.multiworld.FinalXemnas[self.player]:
@@ -291,8 +294,8 @@ class KH2WorldRules(KH2Rules):
else:
self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value)
else:
final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) and\
state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value)
final_xemnas_location.access_rule = lambda state: state.has(ItemName.Bounty, self.player, self.multiworld.BountyRequired[self.player].value) and \
state.has(ItemName.LuckyEmblem, self.player, self.multiworld.LuckyEmblemsRequired[self.player].value)
if self.multiworld.FinalXemnas[self.player]:
self.multiworld.completion_condition[self.player] = lambda state: state.has(ItemName.Victory, self.player, 1)
else:
@@ -418,7 +421,7 @@ class KH2FightRules(KH2Rules):
RegionName.DataLexaeus: lambda state: self.get_data_lexaeus_rules(state),
RegionName.OldPete: lambda state: self.get_old_pete_rules(),
RegionName.FuturePete: lambda state: self.get_future_pete_rules(state),
RegionName.Terra: lambda state: self.get_terra_rules(state),
RegionName.Terra: lambda state: self.get_terra_rules(state) and state.has(ItemName.ProofofConnection, self.player),
RegionName.DataMarluxia: lambda state: self.get_data_marluxia_rules(state),
RegionName.Barbosa: lambda state: self.get_barbosa_rules(state),
RegionName.GrimReaper1: lambda state: self.get_grim_reaper1_rules(),
+29
View File
@@ -0,0 +1,29 @@
import typing
from BaseClasses import Location, Item
class KH2Location(Location):
game: str = "Kingdom Hearts 2"
class LocationData(typing.NamedTuple):
locid: int
yml: str
charName: str = "Sora"
charNumber: int = 1
class KH2Item(Item):
game: str = "Kingdom Hearts 2"
class ItemData(typing.NamedTuple):
quantity: int = 0
kh2id: int = 0
# Save+ mem addr
memaddr: int = 0
# some items have bitmasks. if bitmask>0 bitor to give item else
bitmask: int = 0
# if ability then
ability: bool = False
+11 -7
View File
@@ -12,6 +12,7 @@ from .OpenKH import patch_kh2
from .Options import KingdomHearts2Options
from .Regions import create_regions, connect_regions
from .Rules import *
from .Subclasses import KH2Item
def launch_client():
@@ -49,7 +50,9 @@ class KH2World(World):
for item_id, item in enumerate(item_dictionary_table.keys(), 0x130000)}
location_name_to_id = {item: location
for location, item in enumerate(all_locations.keys(), 0x130000)}
item_name_groups = item_groups
location_name_groups = location_groups
visitlocking_dict: Dict[str, int]
plando_locations: Dict[str, str]
@@ -119,11 +122,15 @@ class KH2World(World):
item_classification = ItemClassification.useful
else:
item_classification = ItemClassification.filler
created_item = KH2Item(name, item_classification, self.item_name_to_id[name], self.player)
return created_item
def create_event_item(self, name: str) -> Item:
item_classification = ItemClassification.progression
created_item = KH2Item(name, item_classification, None, self.player)
return created_item
def create_items(self) -> None:
"""
Fills ItemPool and manages schmovement, random growth, visit locking and random starting visit locking.
@@ -249,11 +256,8 @@ class KH2World(World):
self.goofy_gen_early()
self.keyblade_gen_early()
if self.multiworld.FinalXemnas[self.player]:
self.plando_locations[LocationName.FinalXemnas] = ItemName.Victory
else:
self.plando_locations[LocationName.FinalXemnas] = self.create_filler().name
self.total_locations -= 1
# final xemnas isn't a location anymore
# self.total_locations -= 1
if self.options.WeaponSlotStartHint:
for location in all_weapon_slot:
@@ -461,7 +465,7 @@ class KH2World(World):
if location in self.random_super_boss_list:
self.random_super_boss_list.remove(location)
if not self.options.SummonLevelLocationToggle:
if not self.options.SummonLevelLocationToggle and LocationName.Summonlvl7 in self.random_super_boss_list:
self.random_super_boss_list.remove(LocationName.Summonlvl7)
# Testing if the player has the right amount of Bounties for Completion.
+5 -1
View File
@@ -16,6 +16,7 @@ The [player settings page for this game](../player-settings) contains all the op
- Popups
- Get Bonuses
- Form Levels
- Summon Levels
- Sora's Levels
- Keyblade Stats
- Keyblade Abilities
@@ -23,7 +24,7 @@ The [player settings page for this game](../player-settings) contains all the op
<h2 style="text-transform:none";>What Kingdom Hearts 2 items can appear in other players' worlds?</h2>
Every item in the game except for party members' abilities.
Every item in the game except for abilities on weapons.
<h2 style="text-transform:none";>What is The Garden of Assemblage "GoA"?</h2>
@@ -73,6 +74,8 @@ The list of possible locations that can contain a bounty:
- Each of the 13 Data Fights
- Max level (7) for each Drive Form
- Max level (7) of Summons
- Last song of Atlantica
- Sephiroth
- Lingering Will
- Starry Hill
@@ -87,6 +90,7 @@ With the help of Shananas, Num, and ZakTheRobot we have many QoL features such a
- Faster Wardrobe.
- Faster Water Jafar Chase.
- Faster Bulky Vendors
- Carpet Skip.
- Start with Lion Dash.
- Faster Urns.
+7 -7
View File
@@ -7,14 +7,14 @@
<h2 style="text-transform:none";>Required Software:</h2>
`Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts)
- Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/)<br>
1. `3.0.0 OpenKH Mod Manager with Panacea`<br>
2. `Install mod from KH2FM-Mods-Num/GoA-ROM-Edition`<br>
3. `Setup Lua Backend From the 3.0.0 KH2Randomizer.exe per the setup guide linked above`<br>
1. `3.2.0 OpenKH Mod Manager with Panacea`<br>
2. `Lua Backend from the OpenKH Mod Manager`
3. `Install the mod KH2FM-Mods-Num/GoA-ROM-Edition using OpenKH Mod Manager`<br>
- Needed for Archipelago
1. [`ArchipelagoKH2Client.exe`](https://github.com/ArchipelagoMW/Archipelago/releases)<br>
2. `Install mod from JaredWeakStrike/APCompanion`<br>
3. `Install mod from KH2FM-Mods-equations19/auto-save`<br>
2. `Install the mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager`<br>
3. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager`<br>
4. `AP Randomizer Seed`
<h3 style="text-transform:none";>Required: Archipelago Companion Mod</h3>
@@ -68,8 +68,8 @@ Enter `The room's port number` into the top box <b> where the x's are</b> and pr
- Run the game in windows/borderless windowed mode. Fullscreen is stable but the game can crash if you alt-tab out.
- Make sure to save in a different save slot when playing in an async or disconnecting from the server to play a different seed
<h2 style="text-transform:none";>Requirement/logic sheet</h2>
Have any questions on what's in logic? This spreadsheet has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1Embae0t7pIrbzvX-NRywk7bTHHEvuFzzQBUUpSUL7Ak/edit?usp=sharing)
<h2 style="text-transform:none";>Logic Sheet</h2>
Have any questions on what's in logic? This spreadsheet made by Bulcon has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1nNi8ohEs1fv-sDQQRaP45o6NoRcMlLJsGckBonweDMY/edit?usp=sharing)
<h2 style="text-transform:none";>F.A.Q.</h2>
- Why is my HP/MP continuously increasing without stopping?
+2 -2
View File
@@ -146,7 +146,7 @@ def setReplacementName(key: str, value: str) -> None:
def formatText(instr: str, *, center: bool = False, ask: Optional[str] = None) -> bytes:
instr = instr.format(**_NAMES)
s = instr.encode("ascii")
s = instr.encode("ascii", errors="replace")
s = s.replace(b"'", b"^")
def padLine(line: bytes) -> bytes:
@@ -169,7 +169,7 @@ def formatText(instr: str, *, center: bool = False, ask: Optional[str] = None) -
if result_line:
result += padLine(result_line)
if ask is not None:
askbytes = ask.encode("ascii")
askbytes = ask.encode("ascii", errors="replace")
result = result.rstrip()
while len(result) % 32 != 16:
result += b' '
+29 -7
View File
@@ -349,18 +349,19 @@ class GfxMod(FreeText, LADXROption):
normal = ''
default = 'Link'
__spriteDir: str = Utils.local_path(os.path.join('data', 'sprites','ladx'))
__spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list)
__spriteDir: str = None
extensions = [".bin", ".bdiff", ".png", ".bmp"]
for file in os.listdir(__spriteDir):
name, extension = os.path.splitext(file)
if extension in extensions:
__spriteFiles[name].append(file)
def __init__(self, value: str):
super().__init__(value)
if not GfxMod.__spriteDir:
GfxMod.__spriteDir = Utils.local_path(os.path.join('data', 'sprites','ladx'))
for file in os.listdir(GfxMod.__spriteDir):
name, extension = os.path.splitext(file)
if extension in self.extensions:
GfxMod.__spriteFiles[name].append(file)
def verify(self, world, player_name: str, plando_options) -> None:
if self.value == "Link" or self.value in GfxMod.__spriteFiles:
@@ -398,6 +399,26 @@ class Palette(Choice):
option_pink = 4
option_inverted = 5
class Music(Choice, LADXROption):
"""
[Vanilla] Regular Music
[Shuffled] Shuffled Music
[Off] No music
"""
ladxr_name = "music"
option_vanilla = 0
option_shuffled = 1
option_off = 2
def to_ladxr_option(self, all_options):
s = ""
if self.value == self.option_shuffled:
s = "random"
elif self.value == self.option_off:
s = "off"
return self.ladxr_name, s
class WarpImprovements(DefaultOffToggle):
"""
[On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
@@ -443,6 +464,7 @@ links_awakening_options: typing.Dict[str, typing.Type[Option]] = {
'shuffle_maps': ShuffleMaps,
'shuffle_compasses': ShuffleCompasses,
'shuffle_stone_beaks': ShuffleStoneBeaks,
'music': Music,
'music_change_condition': MusicChangeCondition,
'nag_messages': NagMessages,
'ap_title_screen': APTitleScreen,
+6 -5
View File
@@ -2,7 +2,7 @@
## Required Software
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `Links Awakening DX`
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- Software capable of loading and playing GBC ROM files
- [RetroArch](https://retroarch.com?page=platforms) 1.10.3 or newer.
- [BizHawk](https://tasvideos.org/BizHawk) 2.8 or newer.
@@ -10,11 +10,12 @@
## Installation Procedures
1. Download and install LinksAwakeningClient from the link above, making sure to install the most recent version.
**The installer file is located in the assets section at the bottom of the version information**.
- During setup, you will be asked to locate your base ROM file. This is your Links Awakening DX ROM file.
1. Download and install [Archipelago](<https://github.com/ArchipelagoMW/Archipelago/releases/latest>). **The installer
file is located in the assets section at the bottom of the version information.**
2. The first time you do local generation or patch your game, you will be asked to locate your base ROM file.
This is your Links Awakening DX ROM file. This only needs to be done once..
2. You should assign your emulator as your default program for launching ROM
3. You should assign your emulator as your default program for launching ROM
files.
1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
2. Right-click on a ROM file and select **Open with...**
@@ -30,8 +30,8 @@ guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en)
### Where do I get a config file?
The [Player Settings Page](../player-settings) on the website allows you to easily configure your personal settings
and export a config file from them.
The [Player Settings Page](/games/Landstalker%20-%20The%20Treasures%20of%20King%20Nole/player-settings) on the website allows
you to easily configure your personal settings
## How-to-play
+19 -5
View File
@@ -1,6 +1,8 @@
"""
Archipelago init file for Lingo
"""
from logging import warning
from BaseClasses import Item, ItemClassification, Tutorial
from worlds.AutoWorld import WebWorld, World
from .items import ALL_ITEM_TABLE, LingoItem
@@ -9,7 +11,6 @@ from .options import LingoOptions
from .player_logic import LingoPlayerLogic
from .regions import create_regions
from .static_logic import Room, RoomEntrance
from .testing import LingoTestOptions
class LingoWebWorld(WebWorld):
@@ -49,6 +50,14 @@ class LingoWorld(World):
player_logic: LingoPlayerLogic
def generate_early(self):
if not (self.options.shuffle_doors or self.options.shuffle_colors):
if self.multiworld.players == 1:
warning(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any progression"
f" items. Please turn on Door Shuffle or Color Shuffle if that doesn't seem right.")
else:
raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Lingo world doesn't have any"
f" progression items. Please turn on Door Shuffle or Color Shuffle.")
self.player_logic = LingoPlayerLogic(self)
def create_regions(self):
@@ -73,9 +82,8 @@ class LingoWorld(World):
skips = int(non_traps * skip_percentage / 100.0)
non_skips = non_traps - skips
filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
for i in range(0, non_skips):
pool.append(self.create_item(filler_list[i % len(filler_list)]))
pool.append(self.create_item(self.get_filler_item_name()))
for i in range(0, skips):
pool.append(self.create_item("Puzzle Skip"))
@@ -94,9 +102,11 @@ class LingoWorld(World):
classification = item.classification
if hasattr(self, "options") and self.options.shuffle_paintings and len(item.painting_ids) > 0\
and len(item.door_ids) == 0 and all(painting_id not in self.player_logic.painting_mapping
for painting_id in item.painting_ids):
for painting_id in item.painting_ids)\
and "pilgrim_painting2" not in item.painting_ids:
# If this is a "door" that just moves one or more paintings, and painting shuffle is on and those paintings
# go nowhere, then this item should not be progression.
# go nowhere, then this item should not be progression. The Pilgrim Room painting is special and needs to be
# excluded from this.
classification = ItemClassification.filler
return LingoItem(name, classification, item.code, self.player)
@@ -119,3 +129,7 @@ class LingoWorld(World):
slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping
return slot_data
def get_filler_item_name(self) -> str:
filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
return self.random.choice(filler_list)
+186 -110
View File
@@ -373,6 +373,7 @@
ANOTHER TRY:
id: Entry Room/Panel_advance
tag: topwhite
non_counting: True # This is a counting panel in-game, but it can never count towards the LEVEL 2 panel hunt.
LEVEL 2:
# We will set up special rules for this in code.
id: EndPanel/Panel_level_2
@@ -1033,6 +1034,8 @@
Hallway Room (3): True
Hallway Room (4): True
Hedge Maze: True # through the door to the sectioned-off part of the hedge maze
Cellar:
door: Lookout Entrance
panels:
MASSACRED:
id: Palindrome Room/Panel_massacred_sacred
@@ -1115,7 +1118,13 @@
id: Cross Room/Panel_north_missing
colors: green
tag: forbid
required_room: Outside The Bold
required_panel:
- room: Outside The Bold
panel: MOUTH
- room: Outside The Bold
panel: YEAST
- room: Outside The Bold
panel: WET
DIAMONDS:
id: Cross Room/Panel_diamonds_missing
colors: green
@@ -1157,7 +1166,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- room: Champion's Rest
- room: Color Hunt
panel: PURPLE
Hallway Door:
id: Red Blue Purple Room Area Doors/Door_room_2
@@ -1168,11 +1177,21 @@
- KEEP
- BAILEY
- TOWER
Lookout Entrance:
id: Cross Room Doors/Door_missing
location_name: Outside The Agreeable - Lookout Panels
panels:
- NORTH
- WINTER
- DIAMONDS
- FIRE
paintings:
- id: panda_painting
orientation: south
- id: eyes_yellow_painting
orientation: east
- id: pencil_painting7
orientation: north
progression:
Progressive Hallway Room:
- Hallway Door
@@ -1938,7 +1957,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- room: Champion's Rest
- room: Color Hunt
panel: RED
Rhyme Room Entrance:
id: Double Room Area Doors/Door_room_entry_stairs2
@@ -1956,9 +1975,9 @@
- Color Arrow Room Doors/Door_orange_hider_1
- Color Arrow Room Doors/Door_orange_hider_2
- Color Arrow Room Doors/Door_orange_hider_3
location_name: Color Hunt - RED and YELLOW
group: Champion's Rest - Color Barriers
item_name: Champion's Rest - Orange Barrier
location_name: Color Barriers - RED and YELLOW
group: Color Hunt Barriers
item_name: Color Hunt - Orange Barrier
panels:
- RED
- room: Directional Gallery
@@ -2043,7 +2062,7 @@
door: Sixth Floor
Cellar:
room: Room Room
door: Shortcut to Fifth Floor
door: Cellar Exit
Welcome Back Area:
door: Welcome Back
Art Gallery:
@@ -2302,9 +2321,6 @@
id: Master Room/Panel_mastery_mastery3
tag: midwhite
hunt: True
required_door:
room: Orange Tower Seventh Floor
door: Mastery
THE LIBRARY:
id: EndPanel/Panel_library
check: True
@@ -2366,7 +2382,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- room: Champion's Rest
- room: Color Hunt
panel: GREEN
paintings:
- id: flower_painting_7
@@ -2625,12 +2641,6 @@
panels:
- OBSTACLE
The Colorful:
# The set of required_doors in the achievement panel should prevent
# generation from asking you to solve The Colorful before opening all of the
# doors. Access from the roof is included so that the painting here could be
# an entrance. The client will have to be hardcoded to not open the door to
# the achievement until all of the doors are open, whether by solving the
# panels or through receiving items.
entrances:
The Colorful (Gray):
room: The Colorful (Gray)
@@ -2641,31 +2651,53 @@
id: Countdown Panels/Panel_colorful_colorful
check: True
tag: forbid
required_door:
required_panel:
- room: The Colorful (White)
door: Progress Door
panel: BEGIN
- room: The Colorful (Black)
door: Progress Door
panel: FOUND
- room: The Colorful (Red)
door: Progress Door
panel: LOAF
- room: The Colorful (Yellow)
door: Progress Door
panel: CREAM
- room: The Colorful (Blue)
door: Progress Door
panel: SUN
- room: The Colorful (Purple)
door: Progress Door
panel: SPOON
- room: The Colorful (Orange)
door: Progress Door
panel: LETTERS
- room: The Colorful (Green)
door: Progress Door
panel: WALLS
- room: The Colorful (Brown)
door: Progress Door
panel: IRON
- room: The Colorful (Gray)
door: Progress Door
panel: OBSTACLE
achievement: The Colorful
paintings:
- id: arrows_painting_12
orientation: north
progression:
Progressive Colorful:
- room: The Colorful (White)
door: Progress Door
- room: The Colorful (Black)
door: Progress Door
- room: The Colorful (Red)
door: Progress Door
- room: The Colorful (Yellow)
door: Progress Door
- room: The Colorful (Blue)
door: Progress Door
- room: The Colorful (Purple)
door: Progress Door
- room: The Colorful (Orange)
door: Progress Door
- room: The Colorful (Green)
door: Progress Door
- room: The Colorful (Brown)
door: Progress Door
- room: The Colorful (Gray)
door: Progress Door
Welcome Back Area:
entrances:
Starting Room:
@@ -2675,6 +2707,10 @@
Outside The Undeterred: True
Outside The Agreeable: True
Outside The Wanderer: True
The Observant: True
Art Gallery: True
The Scientific: True
Cellar: True
Orange Tower Fifth Floor:
room: Orange Tower Fifth Floor
door: Welcome Back
@@ -2857,14 +2893,14 @@
group: Color Hunt Barriers
skip_location: True
panels:
- room: Champion's Rest
- room: Color Hunt
panel: BLUE
Orange Barrier:
id: Color Arrow Room Doors/Door_orange_3
group: Color Hunt Barriers
skip_location: True
panels:
- room: Champion's Rest
- room: Color Hunt
panel: ORANGE
Initiated Entrance:
id: Red Blue Purple Room Area Doors/Door_locked_knocked
@@ -2876,9 +2912,9 @@
# containing region.
Green Barrier:
id: Color Arrow Room Doors/Door_green_hider_1
location_name: Color Hunt - BLUE and YELLOW
item_name: Champion's Rest - Green Barrier
group: Champion's Rest - Color Barriers
location_name: Color Barriers - BLUE and YELLOW
item_name: Color Hunt - Green Barrier
group: Color Hunt Barriers
panels:
- BLUE
- room: Directional Gallery
@@ -2888,9 +2924,9 @@
- Color Arrow Room Doors/Door_purple_hider_1
- Color Arrow Room Doors/Door_purple_hider_2
- Color Arrow Room Doors/Door_purple_hider_3
location_name: Color Hunt - RED and BLUE
item_name: Champion's Rest - Purple Barrier
group: Champion's Rest - Color Barriers
location_name: Color Barriers - RED and BLUE
item_name: Color Hunt - Purple Barrier
group: Color Hunt Barriers
panels:
- BLUE
- room: Orange Tower Third Floor
@@ -2900,7 +2936,7 @@
- Color Arrow Room Doors/Door_all_hider_1
- Color Arrow Room Doors/Door_all_hider_2
- Color Arrow Room Doors/Door_all_hider_3
location_name: Color Hunt - GREEN, ORANGE and PURPLE
location_name: Color Barriers - GREEN, ORANGE and PURPLE
item_name: Champion's Rest - Entrance
panels:
- ORANGE
@@ -2991,8 +3027,7 @@
PATS:
id: Rhyme Room/Panel_wrath_path
colors: purple
tag: midpurp and rhyme
copy_to_sign: sign15
tag: forbid
KNIGHT:
id: Rhyme Room/Panel_knight_write
colors: purple
@@ -3141,8 +3176,8 @@
Outside The Bold:
entrances:
Color Hallways: True
Champion's Rest:
room: Champion's Rest
Color Hunt:
room: Color Hunt
door: Shortcut to The Steady
The Bearer:
room: The Bearer
@@ -3158,6 +3193,8 @@
door: Painting Shortcut
painting: True
Room Room: True # trapdoor
Outside The Agreeable:
painting: True
panels:
UNOPEN:
id: Truncate Room/Panel_unopened_open
@@ -3965,7 +4002,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- room: Champion's Rest
- room: Color Hunt
panel: YELLOW
paintings:
- id: smile_painting_7
@@ -3983,12 +4020,15 @@
orientation: south
- id: cherry_painting
orientation: east
Champion's Rest:
Color Hunt:
entrances:
Outside The Bold:
door: Shortcut to The Steady
Orange Tower Fourth Floor: True # sunwarp
Roof: True # through ceiling of sunwarp
Champion's Rest:
room: Outside The Initiated
door: Entrance
panels:
EXIT:
id: Rock Room/Panel_red_red
@@ -4029,41 +4069,6 @@
required_door:
room: Orange Tower Third Floor
door: Orange Barrier
YOU:
id: Color Arrow Room/Panel_you
required_door:
room: Outside The Initiated
door: Entrance
check: True
colors: gray
tag: forbid
ME:
id: Color Arrow Room/Panel_me
colors: gray
tag: forbid
required_door:
room: Outside The Initiated
door: Entrance
SECRET BLUE:
# Pretend this and the other two are white, because they are snipes.
# TODO: Extract them and randomize them?
id: Color Arrow Room/Panel_secret_blue
tag: forbid
required_door:
room: Outside The Initiated
door: Entrance
SECRET YELLOW:
id: Color Arrow Room/Panel_secret_yellow
tag: forbid
required_door:
room: Outside The Initiated
door: Entrance
SECRET RED:
id: Color Arrow Room/Panel_secret_red
tag: forbid
required_door:
room: Outside The Initiated
door: Entrance
doors:
Shortcut to The Steady:
id: Rock Room Doors/Door_hint
@@ -4078,12 +4083,35 @@
required_door:
room: Outside The Initiated
door: Entrance
Champion's Rest:
entrances:
Color Hunt:
room: Outside The Initiated
door: Entrance
panels:
YOU:
id: Color Arrow Room/Panel_you
check: True
colors: gray
tag: forbid
ME:
id: Color Arrow Room/Panel_me
colors: gray
tag: forbid
SECRET BLUE:
# Pretend this and the other two are white, because they are snipes.
# TODO: Extract them and randomize them?
id: Color Arrow Room/Panel_secret_blue
tag: forbid
SECRET YELLOW:
id: Color Arrow Room/Panel_secret_yellow
tag: forbid
SECRET RED:
id: Color Arrow Room/Panel_secret_red
tag: forbid
paintings:
- id: colors_painting
orientation: south
enter_only: True
required_door:
room: Outside The Initiated
door: Entrance
The Bearer:
entrances:
Outside The Bold:
@@ -4187,9 +4215,6 @@
SIX:
id: Backside Room/Panel_six_six_5
tag: midwhite
colors:
- red
- yellow
hunt: True
required_door:
room: Number Hunt
@@ -4265,9 +4290,6 @@
SIX:
id: Backside Room/Panel_six_six_6
tag: midwhite
colors:
- red
- yellow
hunt: True
required_door:
room: Number Hunt
@@ -4389,9 +4411,14 @@
colors: blue
tag: forbid
required_panel:
room: The Bearer (West)
panel: SMILE
required_room: Outside The Bold
- room: The Bearer (West)
panel: SMILE
- room: Outside The Bold
panel: MOUTH
- room: Outside The Bold
panel: YEAST
- room: Outside The Bold
panel: WET
Cross Tower (South):
entrances: # No roof access
The Bearer (North):
@@ -6157,6 +6184,7 @@
Exit:
id: Tower Room Area Doors/Door_painting_exit
include_reduce: True
item_name: Orange Tower Fifth Floor - Quadruple Intersection
panels:
- ONE ROAD MANY TURNS
paintings:
@@ -6176,7 +6204,6 @@
- Third Floor
- Fourth Floor
- Fifth Floor
- Exit
Art Gallery (Second Floor):
entrances:
Art Gallery:
@@ -6299,17 +6326,22 @@
SKELETON:
id: Double Room/Panel_bones_syn
tag: syn rhyme
colors: purple
subtag: bot
link: rhyme BONES
REPENTANCE:
id: Double Room/Panel_sentence_rhyme
colors: purple
colors:
- purple
- blue
tag: whole rhyme
subtag: top
link: rhyme SENTENCE
WORD:
id: Double Room/Panel_sentence_whole
colors: blue
colors:
- purple
- blue
tag: whole rhyme
subtag: bot
link: rhyme SENTENCE
@@ -6321,6 +6353,7 @@
link: rhyme DREAM
FANTASY:
id: Double Room/Panel_dream_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme DREAM
@@ -6332,6 +6365,7 @@
link: rhyme MYSTERY
SECRET:
id: Double Room/Panel_mystery_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme MYSTERY
@@ -6386,25 +6420,33 @@
door: Nines
FERN:
id: Double Room/Panel_return_rhyme
colors: purple
colors:
- purple
- black
tag: ant rhyme
subtag: top
link: rhyme RETURN
STAY:
id: Double Room/Panel_return_ant
colors: black
colors:
- purple
- black
tag: ant rhyme
subtag: bot
link: rhyme RETURN
FRIEND:
id: Double Room/Panel_descend_rhyme
colors: purple
colors:
- purple
- black
tag: ant rhyme
subtag: top
link: rhyme DESCEND
RISE:
id: Double Room/Panel_descend_ant
colors: black
colors:
- purple
- black
tag: ant rhyme
subtag: bot
link: rhyme DESCEND
@@ -6416,6 +6458,7 @@
link: rhyme JUMP
BOUNCE:
id: Double Room/Panel_jump_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme JUMP
@@ -6427,6 +6470,7 @@
link: rhyme FALL
PLUNGE:
id: Double Room/Panel_fall_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme FALL
@@ -6456,13 +6500,17 @@
panels:
BIRD:
id: Double Room/Panel_word_rhyme
colors: purple
colors:
- purple
- blue
tag: whole rhyme
subtag: top
link: rhyme WORD
LETTER:
id: Double Room/Panel_word_whole
colors: blue
colors:
- purple
- blue
tag: whole rhyme
subtag: bot
link: rhyme WORD
@@ -6474,6 +6522,7 @@
link: rhyme HIDDEN
CONCEALED:
id: Double Room/Panel_hidden_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme HIDDEN
@@ -6485,6 +6534,7 @@
link: rhyme SILENT
MUTE:
id: Double Room/Panel_silent_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme SILENT
@@ -6531,6 +6581,7 @@
link: rhyme BLOCKED
OBSTRUCTED:
id: Double Room/Panel_blocked_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme BLOCKED
@@ -6542,6 +6593,7 @@
link: rhyme RISE
SWELL:
id: Double Room/Panel_rise_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme RISE
@@ -6553,6 +6605,7 @@
link: rhyme ASCEND
CLIMB:
id: Double Room/Panel_ascend_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme ASCEND
@@ -6564,6 +6617,7 @@
link: rhyme DOUBLE
DUPLICATE:
id: Double Room/Panel_double_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme DOUBLE
@@ -6642,6 +6696,7 @@
link: rhyme CHILD
KID:
id: Double Room/Panel_child_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme CHILD
@@ -6653,6 +6708,7 @@
link: rhyme CRYSTAL
QUARTZ:
id: Double Room/Panel_crystal_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme CRYSTAL
@@ -6664,6 +6720,7 @@
link: rhyme CREATIVE
INNOVATIVE (Bottom):
id: Double Room/Panel_creative_syn
colors: purple
tag: syn rhyme
subtag: bot
link: rhyme CREATIVE
@@ -6882,7 +6939,7 @@
event: True
panels:
- WALL (1)
Shortcut to Fifth Floor:
Cellar Exit:
id:
- Tower Room Area Doors/Door_panel_basement
- Tower Room Area Doors/Door_panel_basement2
@@ -6895,7 +6952,10 @@
door: Excavation
Orange Tower Fifth Floor:
room: Room Room
door: Shortcut to Fifth Floor
door: Cellar Exit
Outside The Agreeable:
room: Outside The Agreeable
door: Lookout Entrance
Outside The Wise:
entrances:
Orange Tower Sixth Floor:
@@ -7319,49 +7379,65 @@
link: change GRAVITY
PART:
id: Chemistry Room/Panel_physics_2
colors: blue
colors:
- blue
- red
tag: blue mid red bot
subtag: mid
link: xur PARTICLE
MATTER:
id: Chemistry Room/Panel_physics_1
colors: red
colors:
- blue
- red
tag: blue mid red bot
subtag: bot
link: xur PARTICLE
ELECTRIC:
id: Chemistry Room/Panel_physics_6
colors: purple
colors:
- purple
- red
tag: purple mid red bot
subtag: mid
link: xpr ELECTRON
ATOM (1):
id: Chemistry Room/Panel_physics_3
colors: red
colors:
- purple
- red
tag: purple mid red bot
subtag: bot
link: xpr ELECTRON
NEUTRAL:
id: Chemistry Room/Panel_physics_7
colors: purple
colors:
- purple
- red
tag: purple mid red bot
subtag: mid
link: xpr NEUTRON
ATOM (2):
id: Chemistry Room/Panel_physics_4
colors: red
colors:
- purple
- red
tag: purple mid red bot
subtag: bot
link: xpr NEUTRON
PROPEL:
id: Chemistry Room/Panel_physics_8
colors: purple
colors:
- purple
- red
tag: purple mid red bot
subtag: mid
link: xpr PROTON
ATOM (3):
id: Chemistry Room/Panel_physics_5
colors: red
colors:
- purple
- red
tag: purple mid red bot
subtag: bot
link: xpr PROTON
+8 -4
View File
@@ -489,7 +489,7 @@ panels:
WINDWARD: 444803
LIGHT: 444804
REWIND: 444805
Champion's Rest:
Color Hunt:
EXIT: 444806
HUES: 444807
RED: 444808
@@ -498,6 +498,7 @@ panels:
GREEN: 444811
PURPLE: 444812
ORANGE: 444813
Champion's Rest:
YOU: 444814
ME: 444815
SECRET BLUE: 444816
@@ -1064,6 +1065,9 @@ doors:
Hallway Door:
item: 444459
location: 445214
Lookout Entrance:
item: 444579
location: 445271
Dread Hallway:
Tenacious Entrance:
item: 444462
@@ -1283,7 +1287,7 @@ doors:
location: 445246
Yellow Barrier:
item: 444538
Champion's Rest:
Color Hunt:
Shortcut to The Steady:
item: 444539
location: 444806
@@ -1402,7 +1406,7 @@ doors:
item: 444570
location: 445266
Room Room:
Shortcut to Fifth Floor:
Cellar Exit:
item: 444571
location: 445076
Outside The Wise:
@@ -1439,7 +1443,6 @@ door_groups:
Fearless Doors: 444469
Backside Doors: 444473
Orange Tower First Floor - Shortcuts: 444484
Champion's Rest - Color Barriers: 444489
Welcome Back Doors: 444492
Colorful Doors: 444498
Directional Gallery Doors: 444531
@@ -1449,3 +1452,4 @@ progression:
Progressive Fearless: 444470
Progressive Orange Tower: 444482
Progressive Art Gallery: 444563
Progressive Colorful: 444580
+6
View File
@@ -28,6 +28,10 @@ class ItemData(NamedTuple):
# door shuffle is on and tower isn't progressive
return world.options.shuffle_doors != ShuffleDoors.option_none \
and not world.options.progressive_orange_tower
elif self.mode == "the colorful":
# complex door shuffle is on and colorful isn't progressive
return world.options.shuffle_doors == ShuffleDoors.option_complex \
and not world.options.progressive_colorful
elif self.mode == "complex door":
return world.options.shuffle_doors == ShuffleDoors.option_complex
elif self.mode == "door group":
@@ -70,6 +74,8 @@ def load_item_data():
if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]:
if room_name == "Orange Tower":
door_mode = "orange tower"
elif room_name == "The Colorful":
door_mode = "the colorful"
else:
door_mode = "special"
+11 -2
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions
from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions, StartInventoryPool
class ShuffleDoors(Choice):
@@ -21,6 +21,13 @@ class ProgressiveOrangeTower(DefaultOnToggle):
display_name = "Progressive Orange Tower"
class ProgressiveColorful(DefaultOnToggle):
"""When "Shuffle Doors" is on "complex", this setting governs the manner in which The Colorful opens up.
If off, there is an item for each room of The Colorful, meaning that random rooms in the middle of the sequence can open up without giving you access to them.
If on, there are ten progressive items, which open up the sequence from White forward."""
display_name = "Progressive Colorful"
class LocationChecks(Choice):
"""On "normal", there will be a location check for each panel set that would ordinarily open a door, as well as for
achievement panels and a small handful of other panels.
@@ -32,7 +39,7 @@ class LocationChecks(Choice):
option_insanity = 2
class ShuffleColors(Toggle):
class ShuffleColors(DefaultOnToggle):
"""If on, an item is added to the pool for every puzzle color (besides White).
You will need to unlock the requisite colors in order to be able to solve puzzles of that color."""
display_name = "Shuffle Colors"
@@ -117,6 +124,7 @@ class DeathLink(Toggle):
class LingoOptions(PerGameCommonOptions):
shuffle_doors: ShuffleDoors
progressive_orange_tower: ProgressiveOrangeTower
progressive_colorful: ProgressiveColorful
location_checks: LocationChecks
shuffle_colors: ShuffleColors
shuffle_panels: ShufflePanels
@@ -128,3 +136,4 @@ class LingoOptions(PerGameCommonOptions):
trap_percentage: TrapPercentage
puzzle_skip_percentage: PuzzleSkipPercentage
death_link: DeathLink
start_inventory_from_pool: StartInventoryPool
+26 -10
View File
@@ -6,7 +6,6 @@ from .options import LocationChecks, ShuffleDoors, VictoryCondition
from .static_logic import DOORS_BY_ROOM, Door, PAINTINGS, PAINTINGS_BY_ROOM, PAINTING_ENTRANCES, PAINTING_EXITS, \
PANELS_BY_ROOM, PROGRESSION_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS, RoomAndDoor, \
RoomAndPanel
from .testing import LingoTestOptions
if TYPE_CHECKING:
from . import LingoWorld
@@ -84,7 +83,8 @@ class LingoPlayerLogic:
def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"):
if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]:
if room_name == "Orange Tower" and not world.options.progressive_orange_tower:
if (room_name == "Orange Tower" and not world.options.progressive_orange_tower)\
or (room_name == "The Colorful" and not world.options.progressive_colorful):
self.set_door_item(room_name, door_data.name, door_data.item_name)
else:
progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name
@@ -190,6 +190,24 @@ class LingoPlayerLogic:
if item.should_include(world):
self.real_items.append(name)
# Calculate the requirements for the fake pilgrimage.
fake_pilgrimage = [
["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"],
["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"],
["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"],
["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"],
["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"], ["Art Gallery", "Exit"],
["The Tenacious", "Shortcut to Hub Room"], ["Outside The Agreeable", "Tenacious Entrance"]
]
pilgrimage_reqs = AccessRequirements()
for door in fake_pilgrimage:
door_object = DOORS_BY_ROOM[door[0]][door[1]]
if door_object.event or world.options.shuffle_doors == ShuffleDoors.option_none:
pilgrimage_reqs.merge(self.calculate_door_requirements(door[0], door[1], world))
else:
pilgrimage_reqs.doors.add(RoomAndDoor(door[0], door[1]))
self.door_reqs.setdefault("Pilgrim Antechamber", {})["Pilgrimage"] = pilgrimage_reqs
# Create the paintings mapping, if painting shuffle is on.
if painting_shuffle:
# Shuffle paintings until we get something workable.
@@ -205,7 +223,7 @@ class LingoPlayerLogic:
"kind of logic error.")
if door_shuffle != ShuffleDoors.option_none and location_classification != LocationClassification.insanity \
and not early_color_hallways and LingoTestOptions.disable_forced_good_item is False:
and not early_color_hallways:
# If shuffle doors is on, force a useful item onto the HI panel. This may not necessarily get you out of BK,
# but the goal is to allow you to reach at least one more check. The non-painting ones are hardcoded right
# now. We only allow the entrance to the Pilgrim Room if color shuffle is off, because otherwise there are
@@ -369,11 +387,9 @@ class LingoPlayerLogic:
door_object = DOORS_BY_ROOM[room][door]
for req_panel in door_object.panels:
if req_panel.room is not None and req_panel.room != room:
access_reqs.rooms.add(req_panel.room)
sub_access_reqs = self.calculate_panel_requirements(room if req_panel.room is None else req_panel.room,
req_panel.panel, world)
panel_room = room if req_panel.room is None else req_panel.room
access_reqs.rooms.add(panel_room)
sub_access_reqs = self.calculate_panel_requirements(panel_room, req_panel.panel, world)
access_reqs.merge(sub_access_reqs)
self.door_reqs[room][door] = access_reqs
@@ -397,8 +413,8 @@ class LingoPlayerLogic:
unhindered_panels_by_color: dict[Optional[str], int] = {}
for panel_name, panel_data in room_data.items():
# We won't count non-counting panels.
if panel_data.non_counting:
# We won't count non-counting panels. THE MASTER has special access rules and is handled separately.
if panel_data.non_counting or panel_name == "THE MASTER":
continue
# We won't coalesce any panels that have requirements beyond colors. To simplify things for now, we will
+4 -11
View File
@@ -4,7 +4,7 @@ from BaseClasses import Entrance, ItemClassification, Region
from .items import LingoItem
from .locations import LingoLocation
from .player_logic import LingoPlayerLogic
from .rules import lingo_can_use_entrance, lingo_can_use_pilgrimage, make_location_lambda
from .rules import lingo_can_use_entrance, make_location_lambda
from .static_logic import ALL_ROOMS, PAINTINGS, Room, RoomAndDoor
if TYPE_CHECKING:
@@ -25,15 +25,6 @@ def create_region(room: Room, world: "LingoWorld", player_logic: LingoPlayerLogi
return new_region
def handle_pilgrim_room(regions: Dict[str, Region], world: "LingoWorld", player_logic: LingoPlayerLogic) -> None:
target_region = regions["Pilgrim Antechamber"]
source_region = regions["Outside The Agreeable"]
source_region.connect(
target_region,
"Pilgrimage",
lambda state: lingo_can_use_pilgrimage(state, world, player_logic))
def connect_entrance(regions: Dict[str, Region], source_region: Region, target_region: Region, description: str,
door: Optional[RoomAndDoor], world: "LingoWorld", player_logic: LingoPlayerLogic):
connection = Entrance(world.player, description, source_region)
@@ -91,7 +82,9 @@ def create_regions(world: "LingoWorld", player_logic: LingoPlayerLogic) -> None:
connect_entrance(regions, regions[entrance.room], regions[room.name], entrance_name, entrance.door, world,
player_logic)
handle_pilgrim_room(regions, world, player_logic)
# Add the fake pilgrimage.
connect_entrance(regions, regions["Outside The Agreeable"], regions["Pilgrim Antechamber"], "Pilgrimage",
RoomAndDoor("Pilgrim Antechamber", "Pilgrimage"), world, player_logic)
if early_color_hallways:
regions["Starting Room"].connect(regions["Outside The Undeterred"], "Early Color Hallways")
+6 -17
View File
@@ -17,23 +17,6 @@ def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor,
return _lingo_can_open_door(state, effective_room, door.door, world, player_logic)
def lingo_can_use_pilgrimage(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic):
fake_pilgrimage = [
["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"],
["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"],
["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"],
["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"],
["Champion's Rest", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"],
["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"],
["Outside The Agreeable", "Tenacious Entrance"]
]
for entrance in fake_pilgrimage:
if not _lingo_can_open_door(state, entrance[0], entrance[1], world, player_logic):
return False
return True
def lingo_can_use_location(state: CollectionState, location: PlayerLocation, world: "LingoWorld",
player_logic: LingoPlayerLogic):
return _lingo_can_satisfy_requirements(state, location.access, world, player_logic)
@@ -56,6 +39,12 @@ def lingo_can_use_level_2_location(state: CollectionState, world: "LingoWorld",
counted_panels += panel_count
if counted_panels >= world.options.level_2_requirement.value - 1:
return True
# THE MASTER has to be handled separately, because it has special access rules.
if state.can_reach("Orange Tower Seventh Floor", "Region", world.player)\
and lingo_can_use_mastery_location(state, world, player_logic):
counted_panels += 1
if counted_panels >= world.options.level_2_requirement.value - 1:
return True
return False
+16 -3
View File
@@ -3,10 +3,13 @@ from . import LingoTestBase
class TestRequiredRoomLogic(LingoTestBase):
options = {
"shuffle_doors": "complex"
"shuffle_doors": "complex",
"shuffle_colors": "false",
}
def test_pilgrim_first(self) -> None:
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Pilgrim Antechamber", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player))
@@ -27,6 +30,8 @@ class TestRequiredRoomLogic(LingoTestBase):
self.assertTrue(self.can_reach_location("The Seeker - Achievement"))
def test_hidden_first(self) -> None:
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player))
self.assertFalse(self.can_reach_location("The Seeker - Achievement"))
@@ -49,10 +54,13 @@ class TestRequiredRoomLogic(LingoTestBase):
class TestRequiredDoorLogic(LingoTestBase):
options = {
"shuffle_doors": "complex"
"shuffle_doors": "complex",
"shuffle_colors": "false",
}
def test_through_rhyme(self) -> None:
self.remove_forced_good_item()
self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall"))
self.collect_by_name("Starting Room - Rhyme Room Entrance")
@@ -62,6 +70,8 @@ class TestRequiredDoorLogic(LingoTestBase):
self.assertTrue(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall"))
def test_through_hidden(self) -> None:
self.remove_forced_good_item()
self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall"))
self.collect_by_name("Starting Room - Rhyme Room Entrance")
@@ -76,10 +86,13 @@ class TestRequiredDoorLogic(LingoTestBase):
class TestSimpleDoors(LingoTestBase):
options = {
"shuffle_doors": "simple"
"shuffle_doors": "simple",
"shuffle_colors": "false",
}
def test_requirement(self):
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
+4
View File
@@ -8,6 +8,8 @@ class TestProgressiveOrangeTower(LingoTestBase):
}
def test_from_welcome_back(self) -> None:
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
@@ -83,6 +85,8 @@ class TestProgressiveOrangeTower(LingoTestBase):
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player))
def test_from_hub_room(self) -> None:
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
+9 -2
View File
@@ -7,6 +7,8 @@ class TestComplexProgressiveHallwayRoom(LingoTestBase):
}
def test_item(self):
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player))
@@ -58,6 +60,8 @@ class TestSimpleHallwayRoom(LingoTestBase):
}
def test_item(self):
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player))
@@ -81,10 +85,13 @@ class TestSimpleHallwayRoom(LingoTestBase):
class TestProgressiveArtGallery(LingoTestBase):
options = {
"shuffle_doors": "complex"
"shuffle_doors": "complex",
"shuffle_colors": "false",
}
def test_item(self):
self.remove_forced_good_item()
self.assertFalse(self.multiworld.state.can_reach("Art Gallery", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
@@ -135,7 +142,7 @@ class TestProgressiveArtGallery(LingoTestBase):
self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect(progressive_gallery_room[4])
self.collect_by_name("Orange Tower Fifth Floor - Quadruple Intersection")
self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
+6 -2
View File
@@ -1,7 +1,6 @@
from typing import ClassVar
from test.bases import WorldTestBase
from .. import LingoTestOptions
class LingoTestBase(WorldTestBase):
@@ -9,5 +8,10 @@ class LingoTestBase(WorldTestBase):
player: ClassVar[int] = 1
def world_setup(self, *args, **kwargs):
LingoTestOptions.disable_forced_good_item = True
super().world_setup(*args, **kwargs)
def remove_forced_good_item(self):
location = self.multiworld.get_location("Second Room - Good Luck", self.player)
self.remove(location.item)
self.multiworld.itempool.append(location.item)
self.multiworld.state.events.add(location)
-2
View File
@@ -1,2 +0,0 @@
class LingoTestOptions:
disable_forced_good_item: bool = False
+1 -1
View File
@@ -40,7 +40,7 @@ mentioned_panels = Set[]
door_groups = {}
directives = Set["entrances", "panels", "doors", "paintings", "progression"]
panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting"]
panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt"]
door_directives = Set["id", "painting_id", "panels", "item_name", "location_name", "skip_location", "skip_item", "group", "include_reduce", "junk_item", "event"]
painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"]
+6
View File
@@ -170,6 +170,9 @@ pullpc
ScriptTX:
STA $7FD4F1 ; (overwritten instruction)
LDA $05AC ; load map number
CMP.b #$F1 ; check if ancient cave final floor
BNE +
REP #$20
LDA $7FD4EF ; read script item id
CMP.w #$01C2 ; test for ancient key
@@ -261,6 +264,9 @@ SpecialItemGet:
BRA ++
+: CMP.w #$01C2 ; ancient key
BNE +
LDA.w #$0008
ORA $0796
STA $0796 ; set ancient key EV flag ($C3)
LDA.w #$0200
ORA $0797
STA $0797 ; set boss item EV flag ($D1)
Binary file not shown.
+6 -5
View File
@@ -2,7 +2,7 @@
## Required Software
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `SNI Client - Lufia II Ancient Cave Patch Setup`
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases).
- Hardware or software capable of loading and playing SNES ROM files
- An emulator capable of connecting to SNI
([snes9x rr](https://github.com/gocha/snes9x-rr/releases),
@@ -14,11 +14,12 @@ modded SNES minis are currently not supported by SNI**
## Installation Procedures
1. Download and install SNIClient from the link above, making sure to install the most recent version.
**The installer file is located in the assets section at the bottom of the version information**.
- During setup, you will be asked to locate your base ROM file. This is your American Lufia II - Rise of the Sinistrals ROM file.
1. Download and install [Archipelago](<https://github.com/ArchipelagoMW/Archipelago/releases/latest>). **The installer
file is located in the assets section at the bottom of the version information.**
2. The first time you do local generation or patch your game, you will be asked to locate your base ROM file.
This is your American Lufia II - Rise of the Sinistrals ROM file. This only needs to be done once.
2. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM
files.
1. Extract your emulator's folder to your Desktop, or somewhere you will remember.
2. Right-click on a ROM file and select **Open with...**
+1 -1
View File
@@ -62,7 +62,7 @@ class MessengerWorld(World):
"Money Wrench",
], base_offset)}
required_client_version = (0, 4, 1)
required_client_version = (0, 4, 2)
web = MessengerWeb()
+2 -4
View File
@@ -1,12 +1,10 @@
# The Messenger
## Quick Links
- [Setup](../../../../tutorial/The%20Messenger/setup/en)
- [Settings Page](../../../../games/The%20Messenger/player-settings)
- [Setup](/tutorial/The%20Messenger/setup/en)
- [Options Page](/games/The%20Messenger/player-options)
- [Courier Github](https://github.com/Brokemia/Courier)
- [The Messenger Randomizer Github](https://github.com/minous27/TheMessengerRandomizerMod)
- [The Messenger Randomizer AP Github](https://github.com/alwaysintreble/TheMessengerRandomizerModAP)
- [Jacksonbird8237's Item Tracker](https://github.com/Jacksonbird8237/TheMessengerItemTracker)
- [PopTracker Pack](https://github.com/alwaysintreble/TheMessengerTrackPack)
## What does randomization do in this game?
+3 -4
View File
@@ -1,16 +1,15 @@
# The Messenger Randomizer Setup Guide
## Quick Links
- [Game Info](../../../../games/The%20Messenger/info/en)
- [Settings Page](../../../../games/The%20Messenger/player-settings)
- [Game Info](/games/The%20Messenger/info/en)
- [Options Page](/games/The%20Messenger/player-options)
- [Courier Github](https://github.com/Brokemia/Courier)
- [The Messenger Randomizer AP Github](https://github.com/alwaysintreble/TheMessengerRandomizerModAP)
- [Jacksonbird8237's Item Tracker](https://github.com/Jacksonbird8237/TheMessengerItemTracker)
- [PopTracker Pack](https://github.com/alwaysintreble/TheMessengerTrackPack)
## Installation
1. Read the [Game Info Page](../../../../games/The%20Messenger/info/en) for how the game works, caveats and known issues
1. Read the [Game Info Page](/games/The%20Messenger/info/en) for how the game works, caveats and known issues
2. Download and install Courier Mod Loader using the instructions on the release page
* [Latest release is currently 0.7.1](https://github.com/Brokemia/Courier/releases)
3. Download and install the randomizer mod
+4 -1
View File
@@ -63,7 +63,10 @@ class MessengerRules:
"Searing Crags Seal - Triple Ball Spinner": self.has_vertical,
"Searing Crags - Astral Tea Leaves":
lambda state: state.can_reach("Ninja Village - Astral Seed", "Location", self.player),
"Searing Crags - Key of Strength": lambda state: state.has("Power Thistle", self.player),
"Searing Crags - Key of Strength": lambda state: state.has("Power Thistle", self.player)
and (self.has_dart(state)
or (self.has_wingsuit(state)
and self.can_destroy_projectiles(state))),
# glacial peak
"Glacial Peak Seal - Ice Climbers": self.has_dart,
"Glacial Peak Seal - Projectile Spike Pit": self.can_destroy_projectiles,
-1
View File
@@ -5,7 +5,6 @@
- Minecraft Java Edition from
the [Minecraft Java Edition Store Page](https://www.minecraft.net/en-us/store/minecraft-java-edition)
- Archipelago from the [Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases)
- (select `Minecraft Client` during installation.)
## Configuring your YAML file
+1 -2
View File
@@ -10,8 +10,7 @@ As we are using Bizhawk, this guide is only applicable to Windows and Linux syst
- Version 2.7.0 and later are supported.
- Detailed installation instructions for Bizhawk can be found at the above link.
- Windows users must run the prereq installer first, which can also be found at the above link.
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
(select `MegaMan Battle Network 3 Client` during installation).
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases).
- A US MegaMan Battle Network 3 Blue Rom. If you have the [MegaMan Battle Network Legacy Collection Vol. 1](https://store.steampowered.com/app/1798010/Mega_Man_Battle_Network_Legacy_Collection_Vol_1/)
on Steam, you can obtain a copy of this ROM from the game's files, see instructions below.
+19 -6
View File
@@ -26,7 +26,8 @@ class MuseDashCollections:
# MUSE_PLUS_DLC, # To be included when OptionSets are rendered as part of basic settings.
# "maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026.
"Miku in Museland", # Paid DLC not included in Muse Plus
"MSR Anthology", # Part of Muse Plus. Goes away 20th Jan 2024.
"Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
"MSR Anthology", # Now no longer available.
]
DIFF_OVERRIDES: List[str] = [
@@ -34,6 +35,7 @@ class MuseDashCollections:
"Rush-Hour",
"Find this Month's Featured Playlist",
"PeroPero in the Universe",
"umpopoff"
]
album_items: Dict[str, AlbumData] = {}
@@ -81,11 +83,22 @@ class MuseDashCollections:
steamer_mode = sections[3] == "True"
if song_name in self.DIFF_OVERRIDES:
# Note: These difficulties may not actually be representative of these songs.
# The game does not provide these difficulties so they have to be filled in.
diff_of_easy = 4
diff_of_hard = 7
diff_of_master = 10
# These songs use non-standard difficulty values. Which are being overriden with standard values.
# But also avoid filling any missing difficulties (i.e. 0s) with a difficulty value.
if sections[4] != '0':
diff_of_easy = 4
else:
diff_of_easy = None
if sections[5] != '0':
diff_of_hard = 7
else:
diff_of_hard = None
if sections[6] != '0':
diff_of_master = 10
else:
diff_of_master = None
else:
diff_of_easy = self.parse_song_difficulty(sections[4])
diff_of_hard = self.parse_song_difficulty(sections[5])
+31 -3
View File
@@ -119,7 +119,7 @@ Prestige and Vestige|56-4|Give Up TREATMENT Vol.11|True|6|8|11|
Tiny Fate|56-5|Give Up TREATMENT Vol.11|False|7|9|11|
Tsuki ni Murakumo Hana ni Kaze|55-0|Touhou Mugakudan -2-|False|3|5|7|
Patchouli's - Best Hit GSK|55-1|Touhou Mugakudan -2-|False|3|5|8|
Monosugoi Space Shuttle de Koishi ga Monosugoi uta|55-2|Touhou Mugakudan -2-|False|3|5|7|
Monosugoi Space Shuttle de Koishi ga Monosugoi uta|55-2|Touhou Mugakudan -2-|False|3|5|7|11
Kakoinaki Yo wa Ichigo no Tsukikage|55-3|Touhou Mugakudan -2-|False|3|6|8|
Psychedelic Kizakura Doumei|55-4|Touhou Mugakudan -2-|False|4|7|10|
Mischievous Sensation|55-5|Touhou Mugakudan -2-|False|5|7|9|
@@ -484,7 +484,7 @@ Hand in Hand|66-1|Miku in Museland|False|1|3|6|
Cynical Night Plan|66-2|Miku in Museland|False|4|6|8|
God-ish|66-3|Miku in Museland|False|4|7|10|
Darling Dance|66-4|Miku in Museland|False|4|7|9|
Hatsune Creation Myth|66-5|Miku in Museland|False|6|8|10|
Hatsune Creation Myth|66-5|Miku in Museland|False|6|8|10|11
The Vampire|66-6|Miku in Museland|False|4|6|9|
Future Eve|66-7|Miku in Museland|False|4|8|11|
Unknown Mother Goose|66-8|Miku in Museland|False|4|8|10|
@@ -501,4 +501,32 @@ slic.hertz|68-1|Gambler's Tricks|True|5|7|9|
Fuzzy-Navel|68-2|Gambler's Tricks|True|6|8|10|11
Swing Edge|68-3|Gambler's Tricks|True|4|8|10|
Twisted Escape|68-4|Gambler's Tricks|True|5|8|10|11
Swing Sweet Twee Dance|68-5|Gambler's Tricks|False|4|7|10|
Swing Sweet Twee Dance|68-5|Gambler's Tricks|False|4|7|10|
Sanyousei SAY YA!!!|43-42|MD Plus Project|False|4|6|8|
YUKEMURI TAMAONSEN II|43-43|MD Plus Project|False|3|6|9|
Samayoi no mei Amatsu|69-0|Touhou Mugakudan -3-|False|4|6|9|
INTERNET SURVIVOR|69-1|Touhou Mugakudan -3-|False|5|8|10|
Shuki*RaiRai|69-2|Touhou Mugakudan -3-|False|5|7|9|
HELLOHELL|69-3|Touhou Mugakudan -3-|False|4|7|10|
Calamity Fortune|69-4|Touhou Mugakudan -3-|True|6|8|10|11
Tsurupettan|69-5|Touhou Mugakudan -3-|True|2|5|8|
Twilight Poems|43-44|MD Plus Project|True|3|6|8|
All My Friends feat. RANASOL|43-45|MD Plus Project|True|4|7|9|
Heartache|43-46|MD Plus Project|True|5|7|10|
Blue Lemonade|43-47|MD Plus Project|True|3|6|8|
Haunted Dance|43-48|MD Plus Project|False|6|9|11|
Hey Vincent.|43-49|MD Plus Project|True|6|8|10|
Meteor feat. TEA|43-50|MD Plus Project|True|3|6|9|
Narcissism Angel|43-51|MD Plus Project|True|1|3|6|
AlterLuna|43-52|MD Plus Project|True|6|8|11|
Niki Tousen|43-53|MD Plus Project|True|6|8|10|11
Rettou Joutou|70-0|Rin Len's Mirrorland|False|4|7|9|
Telecaster B-Boy|70-1|Rin Len's Mirrorland|False|5|7|10|
Iya Iya Iya|70-2|Rin Len's Mirrorland|False|2|4|7|
Nee Nee Nee|70-3|Rin Len's Mirrorland|False|4|6|8|
Chaotic Love Revolution|70-4|Rin Len's Mirrorland|False|4|6|8|
Dance of the Corpses|70-5|Rin Len's Mirrorland|False|2|5|8|
Bitter Choco Decoration|70-6|Rin Len's Mirrorland|False|3|6|9|
Dance Robot Dance|70-7|Rin Len's Mirrorland|False|4|7|10|
Sweet Devil|70-8|Rin Len's Mirrorland|False|5|7|9|
Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9|
+1 -1
View File
@@ -36,7 +36,7 @@ class AdditionalSongs(Range):
- The final song count may be lower due to other settings.
"""
range_start = 15
range_end = 500 # Note will probably not reach this high if any other settings are done.
range_end = 528 # Note will probably not reach this high if any other settings are done.
default = 40
display_name = "Additional Song Count"
+2 -1
View File
@@ -328,5 +328,6 @@ class MuseDashWorld(World):
"victoryLocation": self.victory_song_name,
"deathLink": self.options.death_link.value,
"musicSheetWinCount": self.get_music_sheet_win_count(),
"gradeNeeded": self.options.grade_needed.value
"gradeNeeded": self.options.grade_needed.value,
"hasFiller": True,
}
+7 -1
View File
@@ -66,5 +66,11 @@ class DifficultyRanges(MuseDashTestBase):
for song_name in muse_dash_world.md_collection.DIFF_OVERRIDES:
song = muse_dash_world.md_collection.song_items[song_name]
self.assertTrue(song.easy is not None and song.hard is not None and song.master is not None,
# umpopoff is a one time weird song. Its currently the only song in the game
# with non-standard difficulties and also doesn't have 3 or more difficulties.
if song_name == 'umpopoff':
self.assertTrue(song.easy is None and song.hard is not None and song.master is None,
f"Song '{song_name}' difficulty not set when it should be.")
else:
self.assertTrue(song.easy is not None and song.hard is not None and song.master is not None,
f"Song '{song_name}' difficulty not set when it should be.")
-42
View File
@@ -1,42 +0,0 @@
from typing import Dict
from BaseClasses import Item, ItemClassification, Location, MultiWorld, Region
from . import Items, Locations
def create_event(player: int, name: str) -> Item:
return Items.NoitaItem(name, ItemClassification.progression, None, player)
def create_location(player: int, name: str, region: Region) -> Location:
return Locations.NoitaLocation(player, name, None, region)
def create_locked_location_event(multiworld: MultiWorld, player: int, region_name: str, item: str) -> Location:
region = multiworld.get_region(region_name, player)
new_location = create_location(player, item, region)
new_location.place_locked_item(create_event(player, item))
region.locations.append(new_location)
return new_location
def create_all_events(multiworld: MultiWorld, player: int) -> None:
for region, event in event_locks.items():
create_locked_location_event(multiworld, player, region, event)
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
# Maps region names to event names
event_locks: Dict[str, str] = {
"The Work": "Victory",
"Mines": "Portal to Holy Mountain 1",
"Coal Pits": "Portal to Holy Mountain 2",
"Snowy Depths": "Portal to Holy Mountain 3",
"Hiisi Base": "Portal to Holy Mountain 4",
"Underground Jungle": "Portal to Holy Mountain 5",
"The Vault": "Portal to Holy Mountain 6",
"Temple of the Art": "Portal to Holy Mountain 7",
}
-166
View File
@@ -1,166 +0,0 @@
from typing import List, NamedTuple, Set
from BaseClasses import CollectionState, MultiWorld
from . import Items, Locations
from .Options import BossesAsChecks, VictoryCondition
from worlds.generic import Rules as GenericRules
class EntranceLock(NamedTuple):
source: str
destination: str
event: str
items_needed: int
entrance_locks: List[EntranceLock] = [
EntranceLock("Mines", "Coal Pits Holy Mountain", "Portal to Holy Mountain 1", 1),
EntranceLock("Coal Pits", "Snowy Depths Holy Mountain", "Portal to Holy Mountain 2", 2),
EntranceLock("Snowy Depths", "Hiisi Base Holy Mountain", "Portal to Holy Mountain 3", 3),
EntranceLock("Hiisi Base", "Underground Jungle Holy Mountain", "Portal to Holy Mountain 4", 4),
EntranceLock("Underground Jungle", "Vault Holy Mountain", "Portal to Holy Mountain 5", 5),
EntranceLock("The Vault", "Temple of the Art Holy Mountain", "Portal to Holy Mountain 6", 6),
EntranceLock("Temple of the Art", "Laboratory Holy Mountain", "Portal to Holy Mountain 7", 7),
]
holy_mountain_regions: List[str] = [
"Coal Pits Holy Mountain",
"Snowy Depths Holy Mountain",
"Hiisi Base Holy Mountain",
"Underground Jungle Holy Mountain",
"Vault Holy Mountain",
"Temple of the Art Holy Mountain",
"Laboratory Holy Mountain",
]
wand_tiers: List[str] = [
"Wand (Tier 1)", # Coal Pits
"Wand (Tier 2)", # Snowy Depths
"Wand (Tier 3)", # Hiisi Base
"Wand (Tier 4)", # Underground Jungle
"Wand (Tier 5)", # The Vault
"Wand (Tier 6)", # Temple of the Art
]
items_hidden_from_shops: List[str] = ["Gold (200)", "Gold (1000)", "Potion", "Random Potion", "Secret Potion",
"Chaos Die", "Greed Die", "Kammi", "Refreshing Gourd", "Sädekivi", "Broken Wand",
"Powder Pouch"]
perk_list: List[str] = list(filter(Items.item_is_perk, Items.item_table.keys()))
# ----------------
# Helper Functions
# ----------------
def has_perk_count(state: CollectionState, player: int, amount: int) -> bool:
return sum(state.count(perk, player) for perk in perk_list) >= amount
def has_orb_count(state: CollectionState, player: int, amount: int) -> bool:
return state.count("Orb", player) >= amount
def forbid_items_at_location(multiworld: MultiWorld, location_name: str, items: Set[str], player: int):
location = multiworld.get_location(location_name, player)
GenericRules.forbid_items_for_player(location, items, player)
# ----------------
# Rule Functions
# ----------------
# Prevent gold and potions from appearing as purchasable items in shops (because physics will destroy them)
def ban_items_from_shops(multiworld: MultiWorld, player: int) -> None:
for location_name in Locations.location_name_to_id.keys():
if "Shop Item" in location_name:
forbid_items_at_location(multiworld, location_name, items_hidden_from_shops, player)
# Prevent high tier wands from appearing in early Holy Mountain shops
def ban_early_high_tier_wands(multiworld: MultiWorld, player: int) -> None:
for i, region_name in enumerate(holy_mountain_regions):
wands_to_forbid = wand_tiers[i+1:]
locations_in_region = Locations.location_region_mapping[region_name].keys()
for location_name in locations_in_region:
forbid_items_at_location(multiworld, location_name, wands_to_forbid, player)
# Prevent high tier wands from appearing in the Secret shop
wands_to_forbid = wand_tiers[3:]
locations_in_region = Locations.location_region_mapping["Secret Shop"].keys()
for location_name in locations_in_region:
forbid_items_at_location(multiworld, location_name, wands_to_forbid, player)
def lock_holy_mountains_into_spheres(multiworld: MultiWorld, player: int) -> None:
for lock in entrance_locks:
location = multiworld.get_entrance(f"From {lock.source} To {lock.destination}", player)
GenericRules.set_rule(location, lambda state, evt=lock.event: state.has(evt, player))
def holy_mountain_unlock_conditions(multiworld: MultiWorld, player: int) -> None:
victory_condition = multiworld.victory_condition[player].value
for lock in entrance_locks:
location = multiworld.get_location(lock.event, player)
if victory_condition == VictoryCondition.option_greed_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, player, items_needed//2)
)
elif victory_condition == VictoryCondition.option_pure_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, player, items_needed//2) and
has_orb_count(state, player, items_needed)
)
elif victory_condition == VictoryCondition.option_peaceful_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, player, items_needed//2) and
has_orb_count(state, player, items_needed * 3)
)
def biome_unlock_conditions(multiworld: MultiWorld, player: int):
lukki_entrances = multiworld.get_region("Lukki Lair", player).entrances
magical_entrances = multiworld.get_region("Magical Temple", player).entrances
wizard_entrances = multiworld.get_region("Wizards' Den", player).entrances
for entrance in lukki_entrances:
entrance.access_rule = lambda state: state.has("Melee Immunity Perk", player) and\
state.has("All-Seeing Eye Perk", player)
for entrance in magical_entrances:
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player)
for entrance in wizard_entrances:
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player)
def victory_unlock_conditions(multiworld: MultiWorld, player: int) -> None:
victory_condition = multiworld.victory_condition[player].value
victory_location = multiworld.get_location("Victory", player)
if victory_condition == VictoryCondition.option_pure_ending:
victory_location.access_rule = lambda state: has_orb_count(state, player, 11)
elif victory_condition == VictoryCondition.option_peaceful_ending:
victory_location.access_rule = lambda state: has_orb_count(state, player, 33)
# ----------------
# Main Function
# ----------------
def create_all_rules(multiworld: MultiWorld, player: int) -> None:
if multiworld.players > 1:
ban_items_from_shops(multiworld, player)
ban_early_high_tier_wands(multiworld, player)
lock_holy_mountains_into_spheres(multiworld, player)
holy_mountain_unlock_conditions(multiworld, player)
biome_unlock_conditions(multiworld, player)
victory_unlock_conditions(multiworld, player)
# Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
if multiworld.bosses_as_checks[player].value >= BossesAsChecks.option_all_bosses:
forbid_items_at_location(multiworld, "Toveri", {"Spatial Awareness Perk"}, player)
+21 -14
View File
@@ -1,6 +1,8 @@
from BaseClasses import Item, Tutorial
from worlds.AutoWorld import WebWorld, World
from . import Events, Items, Locations, Options, Regions, Rules
from typing import Dict, Any
from . import events, items, locations, regions, rules
from .options import NoitaOptions
class NoitaWeb(WebWorld):
@@ -24,33 +26,38 @@ class NoitaWorld(World):
"""
game = "Noita"
option_definitions = Options.noita_options
options: NoitaOptions
options_dataclass = NoitaOptions
item_name_to_id = Items.item_name_to_id
location_name_to_id = Locations.location_name_to_id
item_name_to_id = items.item_name_to_id
location_name_to_id = locations.location_name_to_id
item_name_groups = Items.item_name_groups
location_name_groups = Locations.location_name_groups
item_name_groups = items.item_name_groups
location_name_groups = locations.location_name_groups
data_version = 2
web = NoitaWeb()
def generate_early(self):
if not self.multiworld.get_player_name(self.player).isascii():
raise Exception("Noita yaml's slot name has invalid character(s).")
# Returned items will be sent over to the client
def fill_slot_data(self):
return {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions}
def fill_slot_data(self) -> Dict[str, Any]:
return self.options.as_dict("death_link", "victory_condition", "path_option", "hidden_chests",
"pedestal_checks", "orbs_as_checks", "bosses_as_checks", "extra_orbs", "shop_price")
def create_regions(self) -> None:
Regions.create_all_regions_and_connections(self.multiworld, self.player)
Events.create_all_events(self.multiworld, self.player)
regions.create_all_regions_and_connections(self)
def create_item(self, name: str) -> Item:
return Items.create_item(self.player, name)
return items.create_item(self.player, name)
def create_items(self) -> None:
Items.create_all_items(self.multiworld, self.player)
items.create_all_items(self)
def set_rules(self) -> None:
Rules.create_all_rules(self.multiworld, self.player)
rules.create_all_rules(self)
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(Items.filler_items)
return self.random.choice(items.filler_items)
+3 -1
View File
@@ -40,6 +40,8 @@ or try restarting your game.
### What is a YAML and why do I need one?
You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) here on the Archipelago website to learn
about why Archipelago uses YAML files and what they're for.
Please note that Noita only allows you to type certain characters for your slot name.
These characters are: `` !#$%&'()+,-.0123456789;=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}~<>|\/``
### Where do I get a YAML?
You can use the [game settings page for Noita](/games/Noita/player-settings) here on the Archipelago website to
@@ -54,4 +56,4 @@ Place the unzipped pack in the `packs` folder. Then, open Poptracker and open th
Click on the "AP" symbol at the top, then enter the desired address, slot name, and password.
That's all you need for it. It will provide you with a quick reference to see which checks you've done and
which checks you still have left.
which checks you still have left.
+43
View File
@@ -0,0 +1,43 @@
from typing import Dict, TYPE_CHECKING
from BaseClasses import Item, ItemClassification, Location, Region
from . import items, locations
if TYPE_CHECKING:
from . import NoitaWorld
def create_event(player: int, name: str) -> Item:
return items.NoitaItem(name, ItemClassification.progression, None, player)
def create_location(player: int, name: str, region: Region) -> Location:
return locations.NoitaLocation(player, name, None, region)
def create_locked_location_event(player: int, region: Region, item: str) -> Location:
new_location = create_location(player, item, region)
new_location.place_locked_item(create_event(player, item))
region.locations.append(new_location)
return new_location
def create_all_events(world: "NoitaWorld", created_regions: Dict[str, Region]) -> None:
for region_name, event in event_locks.items():
region = created_regions[region_name]
create_locked_location_event(world.player, region, event)
world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player)
# Maps region names to event names
event_locks: Dict[str, str] = {
"The Work": "Victory",
"Mines": "Portal to Holy Mountain 1",
"Coal Pits": "Portal to Holy Mountain 2",
"Snowy Depths": "Portal to Holy Mountain 3",
"Hiisi Base": "Portal to Holy Mountain 4",
"Underground Jungle": "Portal to Holy Mountain 5",
"The Vault": "Portal to Holy Mountain 6",
"Temple of the Art": "Portal to Holy Mountain 7",
}
+25 -19
View File
@@ -1,9 +1,14 @@
import itertools
from collections import Counter
from typing import Dict, List, NamedTuple, Set
from typing import Dict, List, NamedTuple, Set, TYPE_CHECKING
from BaseClasses import Item, ItemClassification, MultiWorld
from .Options import BossesAsChecks, VictoryCondition, ExtraOrbs
from BaseClasses import Item, ItemClassification
from .options import BossesAsChecks, VictoryCondition, ExtraOrbs
if TYPE_CHECKING:
from . import NoitaWorld
else:
NoitaWorld = object
class ItemData(NamedTuple):
@@ -44,39 +49,40 @@ def create_kantele(victory_condition: VictoryCondition) -> List[str]:
return ["Kantele"] if victory_condition.value >= VictoryCondition.option_pure_ending else []
def create_random_items(multiworld: MultiWorld, player: int, weights: Dict[str, int], count: int) -> List[str]:
def create_random_items(world: NoitaWorld, weights: Dict[str, int], count: int) -> List[str]:
filler_pool = weights.copy()
if multiworld.bad_effects[player].value == 0:
if not world.options.bad_effects:
del filler_pool["Trap"]
return multiworld.random.choices(population=list(filler_pool.keys()),
weights=list(filler_pool.values()),
k=count)
return world.random.choices(population=list(filler_pool.keys()),
weights=list(filler_pool.values()),
k=count)
def create_all_items(multiworld: MultiWorld, player: int) -> None:
locations_to_fill = len(multiworld.get_unfilled_locations(player))
def create_all_items(world: NoitaWorld) -> None:
player = world.player
locations_to_fill = len(world.multiworld.get_unfilled_locations(player))
itempool = (
create_fixed_item_pool()
+ create_orb_items(multiworld.victory_condition[player], multiworld.extra_orbs[player])
+ create_spatial_awareness_item(multiworld.bosses_as_checks[player])
+ create_kantele(multiworld.victory_condition[player])
+ create_orb_items(world.options.victory_condition, world.options.extra_orbs)
+ create_spatial_awareness_item(world.options.bosses_as_checks)
+ create_kantele(world.options.victory_condition)
)
# if there's not enough shop-allowed items in the pool, we can encounter gen issues
# 39 is the number of shop-valid items we need to guarantee
if len(itempool) < 39:
itempool += create_random_items(multiworld, player, shop_only_filler_weights, 39 - len(itempool))
itempool += create_random_items(world, shop_only_filler_weights, 39 - len(itempool))
# this is so that it passes tests and gens if you have minimal locations and only one player
if multiworld.players == 1:
for location in multiworld.get_unfilled_locations(player):
if world.multiworld.players == 1:
for location in world.multiworld.get_unfilled_locations(player):
if "Shop Item" in location.name:
location.item = create_item(player, itempool.pop())
locations_to_fill = len(multiworld.get_unfilled_locations(player))
locations_to_fill = len(world.multiworld.get_unfilled_locations(player))
itempool += create_random_items(multiworld, player, filler_weights, locations_to_fill - len(itempool))
multiworld.itempool += [create_item(player, name) for name in itempool]
itempool += create_random_items(world, filler_weights, locations_to_fill - len(itempool))
world.multiworld.itempool += [create_item(player, name) for name in itempool]
# 110000 - 110032
@@ -201,11 +201,10 @@ location_region_mapping: Dict[str, Dict[str, LocationData]] = {
}
# Iterating the hidden chest and pedestal locations here to avoid clutter above
def generate_location_entries(locname: str, locinfo: LocationData) -> Dict[str, int]:
if locinfo.ltype in ["chest", "pedestal"]:
return {f"{locname} {i + 1}": locinfo.id + i for i in range(20)}
return {locname: locinfo.id}
def make_location_range(location_name: str, base_id: int, amt: int) -> Dict[str, int]:
if amt == 1:
return {location_name: base_id}
return {f"{location_name} {i+1}": base_id + i for i in range(amt)}
location_name_groups: Dict[str, Set[str]] = {"shop": set(), "orb": set(), "boss": set(), "chest": set(),
@@ -215,9 +214,11 @@ location_name_to_id: Dict[str, int] = {}
for location_group in location_region_mapping.values():
for locname, locinfo in location_group.items():
location_name_to_id.update(generate_location_entries(locname, locinfo))
if locinfo.ltype in ["chest", "pedestal"]:
for i in range(20):
location_name_groups[locinfo.ltype].add(f"{locname} {i + 1}")
else:
location_name_groups[locinfo.ltype].add(locname)
# Iterating the hidden chest and pedestal locations here to avoid clutter above
amount = 20 if locinfo.ltype in ["chest", "pedestal"] else 1
entries = make_location_range(locname, locinfo.id, amount)
location_name_to_id.update(entries)
location_name_groups[locinfo.ltype].update(entries.keys())
shop_locations = {name for name in location_name_to_id.keys() if "Shop Item" in name}
@@ -1,5 +1,5 @@
from typing import Dict
from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool
from Options import Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool, PerGameCommonOptions
from dataclasses import dataclass
class PathOption(Choice):
@@ -99,16 +99,16 @@ class ShopPrice(Choice):
default = 100
noita_options: Dict[str, AssembleOptions] = {
"start_inventory_from_pool": StartInventoryPool,
"death_link": DeathLink,
"bad_effects": Traps,
"victory_condition": VictoryCondition,
"path_option": PathOption,
"hidden_chests": HiddenChests,
"pedestal_checks": PedestalChecks,
"orbs_as_checks": OrbsAsChecks,
"bosses_as_checks": BossesAsChecks,
"extra_orbs": ExtraOrbs,
"shop_price": ShopPrice,
}
@dataclass
class NoitaOptions(PerGameCommonOptions):
start_inventory_from_pool: StartInventoryPool
death_link: DeathLink
bad_effects: Traps
victory_condition: VictoryCondition
path_option: PathOption
hidden_chests: HiddenChests
pedestal_checks: PedestalChecks
orbs_as_checks: OrbsAsChecks
bosses_as_checks: BossesAsChecks
extra_orbs: ExtraOrbs
shop_price: ShopPrice
@@ -1,48 +1,43 @@
# Regions are areas in your game that you travel to.
from typing import Dict, Set, List
from typing import Dict, List, TYPE_CHECKING
from BaseClasses import Entrance, MultiWorld, Region
from . import Locations
from BaseClasses import Entrance, Region
from . import locations
from .events import create_all_events
if TYPE_CHECKING:
from . import NoitaWorld
def add_location(player: int, loc_name: str, id: int, region: Region) -> None:
location = Locations.NoitaLocation(player, loc_name, id, region)
region.locations.append(location)
def add_locations(multiworld: MultiWorld, player: int, region: Region) -> None:
locations = Locations.location_region_mapping.get(region.name, {})
for location_name, location_data in locations.items():
def create_locations(world: "NoitaWorld", region: Region) -> None:
locs = locations.location_region_mapping.get(region.name, {})
for location_name, location_data in locs.items():
location_type = location_data.ltype
flag = location_data.flag
opt_orbs = multiworld.orbs_as_checks[player].value
opt_bosses = multiworld.bosses_as_checks[player].value
opt_paths = multiworld.path_option[player].value
opt_num_chests = multiworld.hidden_chests[player].value
opt_num_pedestals = multiworld.pedestal_checks[player].value
is_orb_allowed = location_type == "orb" and flag <= world.options.orbs_as_checks
is_boss_allowed = location_type == "boss" and flag <= world.options.bosses_as_checks
amount = 0
if flag == locations.LocationFlag.none or is_orb_allowed or is_boss_allowed:
amount = 1
elif location_type == "chest" and flag <= world.options.path_option:
amount = world.options.hidden_chests.value
elif location_type == "pedestal" and flag <= world.options.path_option:
amount = world.options.pedestal_checks.value
is_orb_allowed = location_type == "orb" and flag <= opt_orbs
is_boss_allowed = location_type == "boss" and flag <= opt_bosses
if flag == Locations.LocationFlag.none or is_orb_allowed or is_boss_allowed:
add_location(player, location_name, location_data.id, region)
elif location_type == "chest" and flag <= opt_paths:
for i in range(opt_num_chests):
add_location(player, f"{location_name} {i+1}", location_data.id + i, region)
elif location_type == "pedestal" and flag <= opt_paths:
for i in range(opt_num_pedestals):
add_location(player, f"{location_name} {i+1}", location_data.id + i, region)
region.add_locations(locations.make_location_range(location_name, location_data.id, amount),
locations.NoitaLocation)
# Creates a new Region with the locations found in `location_region_mapping` and adds them to the world.
def create_region(multiworld: MultiWorld, player: int, region_name: str) -> Region:
new_region = Region(region_name, player, multiworld)
add_locations(multiworld, player, new_region)
def create_region(world: "NoitaWorld", region_name: str) -> Region:
new_region = Region(region_name, world.player, world.multiworld)
create_locations(world, new_region)
return new_region
def create_regions(multiworld: MultiWorld, player: int) -> Dict[str, Region]:
return {name: create_region(multiworld, player, name) for name in noita_regions}
def create_regions(world: "NoitaWorld") -> Dict[str, Region]:
return {name: create_region(world, name) for name in noita_regions}
# An "Entrance" is really just a connection between two regions
@@ -60,11 +55,12 @@ def create_connections(player: int, regions: Dict[str, Region]) -> None:
# Creates all regions and connections. Called from NoitaWorld.
def create_all_regions_and_connections(multiworld: MultiWorld, player: int) -> None:
created_regions = create_regions(multiworld, player)
create_connections(player, created_regions)
def create_all_regions_and_connections(world: "NoitaWorld") -> None:
created_regions = create_regions(world)
create_connections(world.player, created_regions)
create_all_events(world, created_regions)
multiworld.regions += created_regions.values()
world.multiworld.regions += created_regions.values()
# Oh, what a tangled web we weave
+172
View File
@@ -0,0 +1,172 @@
from typing import List, NamedTuple, Set, TYPE_CHECKING
from BaseClasses import CollectionState
from . import items, locations
from .options import BossesAsChecks, VictoryCondition
from worlds.generic import Rules as GenericRules
if TYPE_CHECKING:
from . import NoitaWorld
class EntranceLock(NamedTuple):
source: str
destination: str
event: str
items_needed: int
entrance_locks: List[EntranceLock] = [
EntranceLock("Mines", "Coal Pits Holy Mountain", "Portal to Holy Mountain 1", 1),
EntranceLock("Coal Pits", "Snowy Depths Holy Mountain", "Portal to Holy Mountain 2", 2),
EntranceLock("Snowy Depths", "Hiisi Base Holy Mountain", "Portal to Holy Mountain 3", 3),
EntranceLock("Hiisi Base", "Underground Jungle Holy Mountain", "Portal to Holy Mountain 4", 4),
EntranceLock("Underground Jungle", "Vault Holy Mountain", "Portal to Holy Mountain 5", 5),
EntranceLock("The Vault", "Temple of the Art Holy Mountain", "Portal to Holy Mountain 6", 6),
EntranceLock("Temple of the Art", "Laboratory Holy Mountain", "Portal to Holy Mountain 7", 7),
]
holy_mountain_regions: List[str] = [
"Coal Pits Holy Mountain",
"Snowy Depths Holy Mountain",
"Hiisi Base Holy Mountain",
"Underground Jungle Holy Mountain",
"Vault Holy Mountain",
"Temple of the Art Holy Mountain",
"Laboratory Holy Mountain",
]
wand_tiers: List[str] = [
"Wand (Tier 1)", # Coal Pits
"Wand (Tier 2)", # Snowy Depths
"Wand (Tier 3)", # Hiisi Base
"Wand (Tier 4)", # Underground Jungle
"Wand (Tier 5)", # The Vault
"Wand (Tier 6)", # Temple of the Art
]
items_hidden_from_shops: Set[str] = {"Gold (200)", "Gold (1000)", "Potion", "Random Potion", "Secret Potion",
"Chaos Die", "Greed Die", "Kammi", "Refreshing Gourd", "Sädekivi", "Broken Wand",
"Powder Pouch"}
perk_list: List[str] = list(filter(items.item_is_perk, items.item_table.keys()))
# ----------------
# Helper Functions
# ----------------
def has_perk_count(state: CollectionState, player: int, amount: int) -> bool:
return sum(state.count(perk, player) for perk in perk_list) >= amount
def has_orb_count(state: CollectionState, player: int, amount: int) -> bool:
return state.count("Orb", player) >= amount
def forbid_items_at_locations(world: "NoitaWorld", shop_locations: Set[str], forbidden_items: Set[str]):
for shop_location in shop_locations:
location = world.multiworld.get_location(shop_location, world.player)
GenericRules.forbid_items_for_player(location, forbidden_items, world.player)
# ----------------
# Rule Functions
# ----------------
# Prevent gold and potions from appearing as purchasable items in shops (because physics will destroy them)
# def ban_items_from_shops(world: "NoitaWorld") -> None:
# for location_name in Locations.location_name_to_id.keys():
# if "Shop Item" in location_name:
# forbid_items_at_location(world, location_name, items_hidden_from_shops)
def ban_items_from_shops(world: "NoitaWorld") -> None:
forbid_items_at_locations(world, locations.shop_locations, items_hidden_from_shops)
# Prevent high tier wands from appearing in early Holy Mountain shops
def ban_early_high_tier_wands(world: "NoitaWorld") -> None:
for i, region_name in enumerate(holy_mountain_regions):
wands_to_forbid = set(wand_tiers[i+1:])
locations_in_region = set(locations.location_region_mapping[region_name].keys())
forbid_items_at_locations(world, locations_in_region, wands_to_forbid)
# Prevent high tier wands from appearing in the Secret shop
wands_to_forbid = set(wand_tiers[3:])
locations_in_region = set(locations.location_region_mapping["Secret Shop"].keys())
forbid_items_at_locations(world, locations_in_region, wands_to_forbid)
def lock_holy_mountains_into_spheres(world: "NoitaWorld") -> None:
for lock in entrance_locks:
location = world.multiworld.get_entrance(f"From {lock.source} To {lock.destination}", world.player)
GenericRules.set_rule(location, lambda state, evt=lock.event: state.has(evt, world.player))
def holy_mountain_unlock_conditions(world: "NoitaWorld") -> None:
victory_condition = world.options.victory_condition.value
for lock in entrance_locks:
location = world.multiworld.get_location(lock.event, world.player)
if victory_condition == VictoryCondition.option_greed_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, world.player, items_needed//2)
)
elif victory_condition == VictoryCondition.option_pure_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, world.player, items_needed//2) and
has_orb_count(state, world.player, items_needed)
)
elif victory_condition == VictoryCondition.option_peaceful_ending:
location.access_rule = lambda state, items_needed=lock.items_needed: (
has_perk_count(state, world.player, items_needed//2) and
has_orb_count(state, world.player, items_needed * 3)
)
def biome_unlock_conditions(world: "NoitaWorld"):
lukki_entrances = world.multiworld.get_region("Lukki Lair", world.player).entrances
magical_entrances = world.multiworld.get_region("Magical Temple", world.player).entrances
wizard_entrances = world.multiworld.get_region("Wizards' Den", world.player).entrances
for entrance in lukki_entrances:
entrance.access_rule = lambda state: state.has("Melee Immunity Perk", world.player) and\
state.has("All-Seeing Eye Perk", world.player)
for entrance in magical_entrances:
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", world.player)
for entrance in wizard_entrances:
entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", world.player)
def victory_unlock_conditions(world: "NoitaWorld") -> None:
victory_condition = world.options.victory_condition.value
victory_location = world.multiworld.get_location("Victory", world.player)
if victory_condition == VictoryCondition.option_pure_ending:
victory_location.access_rule = lambda state: has_orb_count(state, world.player, 11)
elif victory_condition == VictoryCondition.option_peaceful_ending:
victory_location.access_rule = lambda state: has_orb_count(state, world.player, 33)
# ----------------
# Main Function
# ----------------
def create_all_rules(world: "NoitaWorld") -> None:
if world.multiworld.players > 1:
ban_items_from_shops(world)
ban_early_high_tier_wands(world)
lock_holy_mountains_into_spheres(world)
holy_mountain_unlock_conditions(world)
biome_unlock_conditions(world)
victory_unlock_conditions(world)
# Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
if world.options.bosses_as_checks.value >= BossesAsChecks.option_all_bosses:
toveri = world.multiworld.get_location("Toveri", world.player)
GenericRules.forbid_items_for_player(toveri, {"Spatial Awareness Perk"}, world.player)
+1 -1
View File
@@ -1271,7 +1271,7 @@ class LogicTricks(OptionList):
https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/LogicTricks.py
"""
display_name = "Logic Tricks"
valid_keys = frozenset(normalized_name_tricks)
valid_keys = tuple(normalized_name_tricks.keys())
valid_keys_casefold = True
+10 -1
View File
@@ -118,7 +118,16 @@ class OOTWeb(WebWorld):
["TheLynk"]
)
tutorials = [setup, setup_es, setup_fr]
setup_de = Tutorial(
setup.tutorial_name,
setup.description,
"Deutsch",
"setup_de.md",
"setup/de",
["Held_der_Zeit"]
)
tutorials = [setup, setup_es, setup_fr, setup_de]
class OOTWorld(World):
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

+41
View File
@@ -0,0 +1,41 @@
# The Legend of Zelda: Ocarina of Time
## Wo ist die Seite für die Einstellungen?
Die [Seite für die Spielereinstellungen dieses Spiels](../player-options) enthält alle Optionen die man benötigt um
eine YAML-Datei zu konfigurieren und zu exportieren.
## Was macht der Randomizer in diesem Spiel?
Items, welche der Spieler für gewöhnlich im Verlauf des Spiels erhalten würde, wurden umhergemischt. Die Logik bleit
bestehen, damit ist das Spiel immer durchspielbar. Doch weil die Items durch das ganze Spiel gemischt wurden, müssen
manche Bereiche früher bescuht werden, als man es in Vanilla tun würde.
Eine Liste von implementierter Logik, die unoffensichtlich erscheinen kann, kann
[hier (Englisch)](https://wiki.ootrandomizer.com/index.php?title=Logic) gefunden werden.
## Welche Items und Bereiche werden gemischt?
Alle ausrüstbare und sammelbare Gegenstände, sowie Munition können gemischt werden. Und alle Bereiche, die einen
dieser Items enthalten könnten, haben (sehr wahrscheinlich) ihren Inhalt verändert. Goldene Skulltulas können ebenfalls
dazugezählt werden, je nach Wunsch des Spielers.
## Welche Items können in sich in der Welt eines anderen Spielers befinden?
Jedes dieser Items, die gemicht werden können, können in einer Multiworld auch in der Welt eines anderen Spielers
fallen. Es ist jedoch möglich ausgewählte Items auf deine eigene Welt zu beschränken.
## Wie sieht ein Item einer anderen Welt in OoT aus?
Items, die zu einer anderen Welt gehören, werden repräsentiert durch Zelda's Brief.
## Was passiert, wenn der Spieler ein Item erhält?
Sobald der Spieler ein Item erhält, wird Link das Item über seinen Kopf halten und der ganzen Welt präsentieren.
Gut für's Geschäft!
## Einzigartige Lokale Befehle
Die folgenden Befehle stehen nur im OoTClient, um mit Archipelago zu spielen, zur Verfügung:
- `/n64` Überprüffe den Verbindungsstatus deiner N64
- `/deathlink` Schalte den "Deathlink" des Clients um. Überschreibt die zuvor konfigurierten Einstellungen.
+108
View File
@@ -0,0 +1,108 @@
# Setup Anleitung für Ocarina of Time: Archipelago Edition
## WICHTIG
Da wir BizHawk benutzen, gilt diese Anleitung nur für Windows und Linux.
## Benötigte Software
- BizHawk: [BizHawk Veröffentlichungen von TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
- Version 2.3.1 und später werden unterstützt. Version 2.9 ist empfohlen.
- Detailierte Installtionsanweisungen für BizHawk können über den obrigen Link gefunden werden.
- Windows-Benutzer müssen die Prerequisiten installiert haben. Diese können ebenfalls über
den obrigen Link gefunden werden.
- Der integrierte Archipelago-Client, welcher [hier](https://github.com/ArchipelagoMW/Archipelago/releases) installiert
werden kann.
- Eine `Ocarina of Time v1.0 US(?) ROM`. (Nicht aus Europa und keine Master-Quest oder Debug-Rom!)
## Konfigurieren von BizHawk
Sobald Bizhawk einmal installiert wurde, öffne **EmuHawk** und ändere die folgenen Einsteluungen:
- (≤ 2.8) Gehe zu `Config > Customize`. Wechlse zu dem `Advanced`-Reiter, wechsle dann den `Lua Core` von "NLua+KopiLua" zu
`"Lua+LuaInterface"`. Starte danach EmuHawk neu. Dies ist zwingend notwendig, damit die Lua-Scripts, mit denen man sich mit dem Client verbindet, ordnungsgemäß funktionieren.
**ANMERKUNG: Selbst wenn "Lua+LuaInterface" bereits ausgewählt ist, wechsle zwischen den beiden Optionen umher und**
**wähle es erneut aus. Neue Installationen oder Versionen von EmuHawk neigen dazu "Lua+LuaInterface" als die**
**Standard-Option anzuzeigen, aber laden dennoch "NLua+KopiLua", bis dieser Schritt getan ist.**
- Unter `Config > Customize > Advanced`, gehe sicher dass der Haken bei `AutoSaveRAM` ausgeählt ist, und klicke dann
den 5s-Knopf. Dies verringert die Wahrscheinlichkeit den Speicherfrotschritt zu verlieren, sollte der Emulator mal
abstürzen.
- **(Optional)** Unter `Config > Customize` kannst du die Haken in den "Run in background"
(Laufe weiter im Hintergrund) und "Accept background input" (akzeptiere Tastendruck im Hintergrund) Kästchen setzen.
Dies erlaubt dir das Spiel im Hintergrund weiter zu spielen, selbst wenn ein anderes Fenster aktiv ist. (Nützlich bei
mehreren oder eher großen Bildschrimen/Monitoren.)
- Unter `Config > Hotkeys` sind viele Hotkeys, die mit oft genuten Tasten belegt worden sind. Es wird empfohlen die
meisten (oder alle) Hotkeys zu deaktivieren. Dies kann schnell mit `Esc` erledigt werden.
- Wird mit einem Kontroller gespielt, bei der Tastenbelegung (bei einem Laufendem Spiel, unter
`Config > Controllers...`), deaktiviere "P1 A Up", "P1 A Down", "P1 A Left", and "P1 A Right" und gehe stattdessen in
den Reiter `Analog Controls` um den Stick zu belegen, da sonst Probleme beim Zielen auftreten (mit dem Bogen oder
ähnliches). Y-Axis ist für Oben und Unten, und die X-Axis ist für Links und Rechts.
- Unter `N64` setze einen Haken bei "Use Expansion Slot" (Benutze Erweiterungs-Slot). Dies wird benötigt damit
savestates/schnellspeichern funktioniert. (Das N64-Menü taucht nur **nach** dem laden einer N64-ROM auf.)
Es wird sehr empfohlen N64 Rom-Erweiterungen (\*.n64, \*.z64) mit dem Emuhawk - welcher zuvor installiert wurde - zu
verknüpfen.
Um dies zu tun, muss eine beliebige N64 Rom aufgefunden werden, welche in deinem Besitz ist, diese Rechtsklicken und
dann auf "Öffnen mit..." gehen. Gehe dann auf "Andere App auswählen" und suche nach deinen BizHawk-Ordner, in der
sich der Emulator befindet, und wähle dann `EmuHawk.exe` **(NICHT "DiscoHawk.exe"!)** aus.
Eine Alternative BizHawk Setup Anleitung (auf Englisch), sowie weitere Hilfe bei Problemen kann
[hier](https://wiki.ootrandomizer.com/index.php?title=Bizhawk) gefunden werden.
## Erstelle eine YAML-Datei
### Was ist eine YAML-Datei und Warum brauch ich eine?
Eine YAML-Datie enthält einen Satz an einstellbaren Optionen, die dem Generator mitteilen, wie
dein Spiel generiert werden soll. In einer Multiworld stellt jeder Spieler eine eigene YAML-Datei zur Verfügung. Dies
erlaubt jeden Spieler eine personalisierte Erfahrung nach derem Geschmack. Damit kann auch jeder Spieler in einer
Multiworld (des gleichen Spiels) völlig unterschiedliche Einstellungen haben.
Für weitere Informationen, besuche die allgemeine Anleitung zum Erstellen einer
YAML-Datei: [Archipelago Setup Anleitung](/tutorial/Archipelago/setup/en)
### Woher bekomme ich eine YAML-Datei?
Die Seite für die Spielereinstellungen auf dieser Website erlaubt es dir deine persönlichen Einstellungen nach
vorlieben zu konfigurieren und eine YAML-Datei zu exportieren.
Seite für die Spielereinstellungen:
[Seite für die Spielereinstellungen von Ocarina of Time](/games/Ocarina%20of%20Time/player-options)
### Überprüfen deiner YAML-Datei
Wenn du deine YAML-Datei überprüfen möchtest, um sicher zu gehen, dass sie funktioniert, kannst du dies auf der
YAML-Überprüfungsseite tun.
YAML-Überprüfungsseite: [YAML-Überprüfungsseite](/check)
## Beitreten einer Multiworld
### Erhalte deinen OoT-Patch
(Der folgende Prozess ist bei den meisten ROM-basierenden Spielen sehr ähnlich.)
Wenn du einer Multiworld beitrittst, wirst du gefordert eine YAML-Datei bei dem Host abzugeben. Ist dies getan,
erhälst du (in der Regel) einen Link vom Host der Multiworld. Dieser führt dich zu einem Raum, in dem alle
teilnehmenden Spieler (bzw. Welten) aufgelistet sind. Du solltest dich dann auf **deine** Welt konzentrieren
und klicke dann auf `Download APZ5 File...`.
![Screenshot of a Multiworld Room with an Ocarina of Time Player](/static/generated/docs/Ocarina%20of%20Time/MultiWorld-room_oot.png)
Führe die `.apz5`-Datei mit einem Doppelklick aus, um deinen Ocarina Of Time-Client zu starten, sowie das patchen
deiner ROM. Ist dieser Prozess fertig (kann etwas dauern), startet sich der Client und der Emulator automatisch
(sofern das "Öffnen mit..." ausgewählt wurde).
### Verbinde zum Multiserver
Sind einmal der Client und der Emulator gestartet, müssen sie nur noch miteinander verbunden werden. Gehe dazu in
deinen Archipelago-Ordner, dann zu `data/lua`, und füge das `connector_oot.lua` Script per Drag&Drop (ziehen und
fallen lassen) auf das EmuHawk-Fenster. (Alternativ kannst du die Lua-Konsole manuell öffnen, gehe dazu auf
`Script > Open Script` und durchsuche die Ordner nach `data/lua/connector_oot.lua`.)
Um den Client mit dem Multiserver zu verbinden, füge einfach `<Adresse>:<Port>` in das Textfeld ganz oben im
Client ein und drücke Enter oder "Connect" (verbinden). Wird ein Passwort benötigt, musst du es danach unten in das
Textfeld (für den Chat und Befehle) eingeben.
Alternativ kannst du auch in dem unterem Textfeld den folgenden Befehl schreiben:
`/connect <Adresse>:<Port> [Passwort]` (wie die Adresse und der Port lautet steht in dem Raum, oder wird von deinem
Host an dich weitergegeben.)
Beispiel: `/connect archipelago.gg:12345 Passw123`
Du bist nun bereit für dein Zeitreise-Abenteuer in Hyrule.
+1 -2
View File
@@ -10,8 +10,7 @@ As we are using BizHawk, this guide is only applicable to Windows and Linux syst
- Version 2.3.1 and later are supported. Version 2.7 is recommended for stability.
- Detailed installation instructions for BizHawk can be found at the above link.
- Windows users must run the prereq installer first, which can also be found at the above link.
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
(select `Ocarina of Time Client` during installation).
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases).
- An Ocarina of Time v1.0 ROM.
## Configuring BizHawk

Some files were not shown because too many files have changed in this diff Show More