mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-05-27 14:49:54 -07:00
Merge branch 'main' into instruction_patch_clean
This commit is contained in:
+5
-1
@@ -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
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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...**
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
@@ -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*~~
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
@@ -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".
|
||||
|
||||

|
||||
- 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.
|
||||

|
||||
|
||||
- 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
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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
@@ -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]]
|
||||
|
||||
@@ -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", [])
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 = {
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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,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?
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class LingoTestOptions:
|
||||
disable_forced_good_item: bool = False
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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.
@@ -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...**
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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|
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
@@ -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 |
@@ -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.
|
||||
@@ -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...`.
|
||||

|
||||
|
||||
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.
|
||||
@@ -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
Reference in New Issue
Block a user