Compare commits

..

1 Commits

Author SHA1 Message Date
Berserker
1529c8938c WebHost: add document for other games and tools, wiki etc. 2026-03-15 23:32:31 +01:00
158 changed files with 5251 additions and 2518 deletions

View File

@@ -3,7 +3,6 @@
"../BizHawkClient.py", "../BizHawkClient.py",
"../Patch.py", "../Patch.py",
"../rule_builder/cached_world.py", "../rule_builder/cached_world.py",
"../rule_builder/field_resolvers.py",
"../rule_builder/options.py", "../rule_builder/options.py",
"../rule_builder/rules.py", "../rule_builder/rules.py",
"../test/param.py", "../test/param.py",

View File

@@ -14,8 +14,6 @@ env:
BEFORE: ${{ github.event.before }} BEFORE: ${{ github.event.before }}
AFTER: ${{ github.event.after }} AFTER: ${{ github.event.after }}
permissions: {}
jobs: jobs:
flake8-or-mypy: flake8-or-mypy:
strategy: strategy:
@@ -27,7 +25,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- name: "Determine modified files (pull_request)" - name: "Determine modified files (pull_request)"
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
@@ -52,7 +50,7 @@ jobs:
run: | run: |
echo "diff=." >> $GITHUB_ENV echo "diff=." >> $GITHUB_ENV
- uses: actions/setup-python@v6.2.0 - uses: actions/setup-python@v5
if: env.diff != '' if: env.diff != ''
with: with:
python-version: '3.11' python-version: '3.11'

View File

@@ -41,9 +41,9 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
# - copy code below to release.yml - # - copy code below to release.yml -
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- name: Install python - name: Install python
uses: actions/setup-python@v6.2.0 uses: actions/setup-python@v5
with: with:
python-version: '~3.12.7' python-version: '~3.12.7'
check-latest: true check-latest: true
@@ -82,7 +82,7 @@ jobs:
# - copy code above to release.yml - # - copy code above to release.yml -
- name: Attest Build - name: Attest Build
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/attest@v4.1.0 uses: actions/attest-build-provenance@v2
with: with:
subject-path: | subject-path: |
build/exe.*/ArchipelagoLauncher.exe build/exe.*/ArchipelagoLauncher.exe
@@ -110,17 +110,18 @@ jobs:
cp Players/Templates/VVVVVV.yaml Players/ cp Players/Templates/VVVVVV.yaml Players/
timeout 30 ./ArchipelagoGenerate timeout 30 ./ArchipelagoGenerate
- name: Store 7z - name: Store 7z
uses: actions/upload-artifact@v7.0.0 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.ZIP_NAME }}
path: dist/${{ env.ZIP_NAME }} path: dist/${{ env.ZIP_NAME }}
archive: false compression-level: 0 # .7z is incompressible by zip
if-no-files-found: error if-no-files-found: error
retention-days: 7 # keep for 7 days, should be enough retention-days: 7 # keep for 7 days, should be enough
- name: Store Setup - name: Store Setup
uses: actions/upload-artifact@v7.0.0 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.SETUP_NAME }}
path: setups/${{ env.SETUP_NAME }} path: setups/${{ env.SETUP_NAME }}
archive: false
if-no-files-found: error if-no-files-found: error
retention-days: 7 # keep for 7 days, should be enough retention-days: 7 # keep for 7 days, should be enough
@@ -128,14 +129,14 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
# - copy code below to release.yml - # - copy code below to release.yml -
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- name: Install base dependencies - name: Install base dependencies
run: | run: |
sudo apt update sudo apt update
sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0 sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0
sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below
- name: Get a recent python - name: Get a recent python
uses: actions/setup-python@v6.2.0 uses: actions/setup-python@v5
with: with:
python-version: '~3.12.7' python-version: '~3.12.7'
check-latest: true check-latest: true
@@ -172,7 +173,7 @@ jobs:
# - copy code above to release.yml - # - copy code above to release.yml -
- name: Attest Build - name: Attest Build
if: ${{ github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/attest@v4.1.0 uses: actions/attest-build-provenance@v2
with: with:
subject-path: | subject-path: |
build/exe.*/ArchipelagoLauncher build/exe.*/ArchipelagoLauncher
@@ -203,17 +204,17 @@ jobs:
cp Players/Templates/VVVVVV.yaml Players/ cp Players/Templates/VVVVVV.yaml Players/
timeout 30 ./ArchipelagoGenerate timeout 30 ./ArchipelagoGenerate
- name: Store AppImage - name: Store AppImage
uses: actions/upload-artifact@v7.0.0 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.APPIMAGE_NAME }}
path: dist/${{ env.APPIMAGE_NAME }} path: dist/${{ env.APPIMAGE_NAME }}
archive: false
# TODO: decide if we want to also upload the zsync
if-no-files-found: error if-no-files-found: error
retention-days: 7 retention-days: 7
- name: Store .tar.gz - name: Store .tar.gz
uses: actions/upload-artifact@v7.0.0 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.TAR_NAME }}
path: dist/${{ env.TAR_NAME }} path: dist/${{ env.TAR_NAME }}
archive: false compression-level: 0 # .gz is incompressible by zip
if-no-files-found: error if-no-files-found: error
retention-days: 7 retention-days: 7

View File

@@ -17,26 +17,17 @@ on:
paths: paths:
- '**.py' - '**.py'
- '**.js' - '**.js'
- '.github/workflows/*.yml' - '.github/workflows/codeql-analysis.yml'
- '.github/workflows/*.yaml'
- '**/action.yml'
- '**/action.yaml'
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ main ] branches: [ main ]
paths: paths:
- '**.py' - '**.py'
- '**.js' - '**.js'
- '.github/workflows/*.yml' - '.github/workflows/codeql-analysis.yml'
- '.github/workflows/*.yaml'
- '**/action.yml'
- '**/action.yaml'
schedule: schedule:
- cron: '44 8 * * 1' - cron: '44 8 * * 1'
permissions:
security-events: write
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
@@ -45,17 +36,18 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'javascript', 'python', 'actions' ] language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more: # Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6.0.2 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4.35.1 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -66,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v4.35.1 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -80,4 +72,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.35.1 uses: github/codeql-action/analyze@v3

View File

@@ -24,8 +24,6 @@ on:
- '**/CMakeLists.txt' - '**/CMakeLists.txt'
- '.github/workflows/ctest.yml' - '.github/workflows/ctest.yml'
permissions: {}
jobs: jobs:
ctest: ctest:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -37,7 +35,7 @@ jobs:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
if: startsWith(matrix.os,'windows') if: startsWith(matrix.os,'windows')
- uses: Bacondish2023/setup-googletest@49065d1f7a6d21f6134864dd65980fe5dbe06c73 - uses: Bacondish2023/setup-googletest@49065d1f7a6d21f6134864dd65980fe5dbe06c73

View File

@@ -19,8 +19,6 @@ on:
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
permissions: {}
jobs: jobs:
prepare: prepare:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -31,7 +29,7 @@ jobs:
package-name: ${{ steps.package.outputs.name }} package-name: ${{ steps.package.outputs.name }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6.0.2 uses: actions/checkout@v4
- name: Set lowercase image name - name: Set lowercase image name
id: image id: image
@@ -45,7 +43,7 @@ jobs:
- name: Extract metadata - name: Extract metadata
id: meta id: meta
uses: docker/metadata-action@v6.0.0 uses: docker/metadata-action@v5
with: with:
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }} images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
tags: | tags: |
@@ -94,13 +92,13 @@ jobs:
cache-scope: arm64 cache-scope: arm64
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6.0.2 uses: actions/checkout@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -117,7 +115,7 @@ jobs:
echo "tags=$(IFS=','; echo "${suffixed[*]}")" >> $GITHUB_OUTPUT echo "tags=$(IFS=','; echo "${suffixed[*]}")" >> $GITHUB_OUTPUT
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v7.0.0 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
@@ -137,7 +135,7 @@ jobs:
packages: write packages: write
steps: steps:
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@@ -14,7 +14,7 @@ jobs:
name: 'Apply content-based labels' name: 'Apply content-based labels'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v6.0.1 - uses: actions/labeler@v5
with: with:
sync-labels: false sync-labels: false
peer_review: peer_review:

View File

@@ -48,9 +48,9 @@ jobs:
shell: bash shell: bash
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
# - code below copied from build.yml - # - code below copied from build.yml -
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- name: Install python - name: Install python
uses: actions/setup-python@v6.2.0 uses: actions/setup-python@v5
with: with:
python-version: '~3.12.7' python-version: '~3.12.7'
check-latest: true check-latest: true
@@ -88,7 +88,7 @@ jobs:
echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV
# - code above copied from build.yml - # - code above copied from build.yml -
- name: Attest Build - name: Attest Build
uses: actions/attest@v4.1.0 uses: actions/attest-build-provenance@v2
with: with:
subject-path: | subject-path: |
build/exe.*/ArchipelagoLauncher.exe build/exe.*/ArchipelagoLauncher.exe
@@ -114,14 +114,14 @@ jobs:
- name: Set env - name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
# - code below copied from build.yml - # - code below copied from build.yml -
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- name: Install base dependencies - name: Install base dependencies
run: | run: |
sudo apt update sudo apt update
sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0 sudo apt -y install build-essential p7zip xz-utils wget libglib2.0-0
sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below sudo apt -y install python3-gi libgirepository1.0-dev # should pull dependencies for gi installation below
- name: Get a recent python - name: Get a recent python
uses: actions/setup-python@v6.2.0 uses: actions/setup-python@v5
with: with:
python-version: '~3.12.7' python-version: '~3.12.7'
check-latest: true check-latest: true
@@ -157,7 +157,7 @@ jobs:
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
# - code above copied from build.yml - # - code above copied from build.yml -
- name: Attest Build - name: Attest Build
uses: actions/attest@v4.1.0 uses: actions/attest-build-provenance@v2
with: with:
subject-path: | subject-path: |
build/exe.*/ArchipelagoLauncher build/exe.*/ArchipelagoLauncher

View File

@@ -28,14 +28,12 @@ on:
- 'requirements.txt' - 'requirements.txt'
- '.github/workflows/scan-build.yml' - '.github/workflows/scan-build.yml'
permissions: {}
jobs: jobs:
scan-build: scan-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
- name: Install newer Clang - name: Install newer Clang
@@ -47,7 +45,7 @@ jobs:
run: | run: |
sudo apt install clang-tools-19 sudo apt install clang-tools-19
- name: Get a recent python - name: Get a recent python
uses: actions/setup-python@v6.2.0 uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.11'
- name: Install dependencies - name: Install dependencies
@@ -61,9 +59,7 @@ jobs:
scan-build-19 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y scan-build-19 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y
- name: Store report - name: Store report
if: failure() if: failure()
uses: actions/upload-artifact@v7.0.0 uses: actions/upload-artifact@v4
with: with:
name: scan-build-reports name: scan-build-reports
path: scan-build-reports path: scan-build-reports
compression-level: 9 # highly compressible
if-no-files-found: error

View File

@@ -14,15 +14,13 @@ on:
- ".github/workflows/strict-type-check.yml" - ".github/workflows/strict-type-check.yml"
- "**.pyi" - "**.pyi"
permissions: {}
jobs: jobs:
pyright: pyright:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- uses: actions/setup-python@v6.2.0 - uses: actions/setup-python@v5
with: with:
python-version: "3.11" python-version: "3.11"

View File

@@ -29,8 +29,6 @@ on:
- '!.github/workflows/**' - '!.github/workflows/**'
- '.github/workflows/unittests.yml' - '.github/workflows/unittests.yml'
permissions: {}
jobs: jobs:
unit: unit:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -53,9 +51,9 @@ jobs:
os: macos-latest os: macos-latest
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python.version }} - name: Set up Python ${{ matrix.python.version }}
uses: actions/setup-python@v6.2.0 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python.version }} python-version: ${{ matrix.python.version }}
- name: Install dependencies - name: Install dependencies
@@ -80,9 +78,9 @@ jobs:
- {version: '3.13'} # current - {version: '3.13'} # current
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python.version }} - name: Set up Python ${{ matrix.python.version }}
uses: actions/setup-python@v6.2.0 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python.version }} python-version: ${{ matrix.python.version }}
- name: Install dependencies - name: Install dependencies

View File

@@ -87,7 +87,6 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
seed = get_seed(args.seed) seed = get_seed(args.seed)
if __name__ == "__main__":
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level, add_timestamp=args.log_time) Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level, add_timestamp=args.log_time)
random.seed(seed) random.seed(seed)
seed_name = get_seed_name(random) seed_name = get_seed_name(random)

View File

@@ -29,8 +29,8 @@ if __name__ == "__main__":
import settings import settings
import Utils import Utils
from Utils import (env_cleared_lib_path, init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, from Utils import (init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, messagebox, open_filename,
messagebox, open_filename, user_path) user_path)
if __name__ == "__main__": if __name__ == "__main__":
init_logging('Launcher') init_logging('Launcher')
@@ -52,7 +52,10 @@ def open_host_yaml():
webbrowser.open(file) webbrowser.open(file)
return return
env = env_cleared_lib_path() env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
subprocess.Popen([exe, file], env=env) subprocess.Popen([exe, file], env=env)
def open_patch(): def open_patch():
@@ -103,7 +106,10 @@ def open_folder(folder_path):
return return
if exe: if exe:
env = env_cleared_lib_path() env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
subprocess.Popen([exe, folder_path], env=env) subprocess.Popen([exe, folder_path], env=env)
else: else:
logging.warning(f"No file browser available to open {folder_path}") logging.warning(f"No file browser available to open {folder_path}")
@@ -196,32 +202,22 @@ def get_exe(component: str | Component) -> Sequence[str] | None:
return [sys.executable, local_path(f"{component.script_name}.py")] if component.script_name else None return [sys.executable, local_path(f"{component.script_name}.py")] if component.script_name else None
def launch(exe: Sequence[str], in_terminal: bool = False) -> bool: def launch(exe, in_terminal=False):
"""Runs the given command/args in `exe` in a new process.
If `in_terminal` is True, it will attempt to run in a terminal window,
and the return value will indicate whether one was found."""
if in_terminal: if in_terminal:
if is_windows: if is_windows:
# intentionally using a window title with a space so it gets quoted and treated as a title # intentionally using a window title with a space so it gets quoted and treated as a title
subprocess.Popen(["start", "Running Archipelago", *exe], shell=True) subprocess.Popen(["start", "Running Archipelago", *exe], shell=True)
return True return
elif is_linux: elif is_linux:
terminal = which("x-terminal-emulator") or which("konsole") or which("gnome-terminal") or which("xterm") terminal = which('x-terminal-emulator') or which('gnome-terminal') or which('xterm')
if terminal: if terminal:
# Clear LD_LIB_PATH during terminal startup, but set it again when running command in case it's needed subprocess.Popen([terminal, '-e', shlex.join(exe)])
ld_lib_path = os.environ.get("LD_LIBRARY_PATH") return
lib_path_setter = f"env LD_LIBRARY_PATH={shlex.quote(ld_lib_path)} " if ld_lib_path else ""
env = env_cleared_lib_path()
subprocess.Popen([terminal, "-e", lib_path_setter + shlex.join(exe)], env=env)
return True
elif is_macos: elif is_macos:
terminal = [which("open"), "-W", "-a", "Terminal.app"] terminal = [which('open'), '-W', '-a', 'Terminal.app']
subprocess.Popen([*terminal, *exe]) subprocess.Popen([*terminal, *exe])
return True return
subprocess.Popen(exe) subprocess.Popen(exe)
return False
def create_shortcut(button: Any, component: Component) -> None: def create_shortcut(button: Any, component: Component) -> None:
@@ -410,17 +406,12 @@ def run_gui(launch_components: list[Component], args: Any) -> None:
@staticmethod @staticmethod
def component_action(button): def component_action(button):
open_text = "Opening in a new window..." MDSnackbar(MDSnackbarText(text="Opening in a new window..."), y=dp(24), pos_hint={"center_x": 0.5},
size_hint_x=0.5).open()
if button.component.func: if button.component.func:
# Note: if we want to draw the Snackbar before running func, func needs to be wrapped in schedule_once
button.component.func() button.component.func()
else: else:
# if launch returns False, it started the process in background (not in a new terminal) launch(get_exe(button.component), button.component.cli)
if not launch(get_exe(button.component), button.component.cli) and button.component.cli:
open_text = "Running in the background..."
MDSnackbar(MDSnackbarText(text=open_text), y=dp(24), pos_hint={"center_x": 0.5},
size_hint_x=0.5).open()
def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None: def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None:
""" When a patch file is dropped into the window, run the associated component. """ """ When a patch file is dropped into the window, run the associated component. """

View File

@@ -384,11 +384,10 @@ class OptionsCreator(ThemedApp):
def create_free_text(self, option: typing.Type[FreeText] | typing.Type[TextChoice], name: str): def create_free_text(self, option: typing.Type[FreeText] | typing.Type[TextChoice], name: str):
text = VisualFreeText(option=option, name=name) text = VisualFreeText(option=option, name=name)
def set_value(instance, value): def set_value(instance):
self.options[name] = value self.options[name] = instance.text
text.bind(text=set_value) text.bind(on_text_validate=set_value)
self.options[name] = option.default
return text return text
def create_choice(self, option: typing.Type[Choice], name: str): def create_choice(self, option: typing.Type[Choice], name: str):

View File

@@ -24,6 +24,7 @@ Currently, the following games are supported:
* The Witness * The Witness
* Sonic Adventure 2: Battle * Sonic Adventure 2: Battle
* Starcraft 2 * Starcraft 2
* Donkey Kong Country 3
* Dark Souls 3 * Dark Souls 3
* Super Mario World * Super Mario World
* Pokémon Red and Blue * Pokémon Red and Blue

View File

@@ -22,7 +22,7 @@ from datetime import datetime, timezone
from settings import Settings, get_settings from settings import Settings, get_settings
from time import sleep from time import sleep
from typing import BinaryIO, Coroutine, Mapping, Optional, Set, Dict, Any, Union, TypeGuard from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union, TypeGuard
from yaml import load, load_all, dump from yaml import load, load_all, dump
from pathspec import PathSpec, GitIgnoreSpec from pathspec import PathSpec, GitIgnoreSpec
from typing_extensions import deprecated from typing_extensions import deprecated
@@ -236,7 +236,10 @@ def open_file(filename: typing.Union[str, "pathlib.Path"]) -> None:
open_command = which("open") if is_macos else (which("xdg-open") or which("gnome-open") or which("kde-open")) open_command = which("open") if is_macos else (which("xdg-open") or which("gnome-open") or which("kde-open"))
assert open_command, "Didn't find program for open_file! Please report this together with system details." assert open_command, "Didn't find program for open_file! Please report this together with system details."
env = env_cleared_lib_path() env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
subprocess.call([open_command, filename], env=env) subprocess.call([open_command, filename], env=env)
@@ -342,9 +345,6 @@ def persistent_load() -> Dict[str, Dict[str, Any]]:
try: try:
with open(path, "r") as f: with open(path, "r") as f:
storage = unsafe_parse_yaml(f.read()) storage = unsafe_parse_yaml(f.read())
if "datapackage" in storage:
del storage["datapackage"]
logging.debug("Removed old datapackage from persistent storage")
except Exception as e: except Exception as e:
logging.debug(f"Could not read store: {e}") logging.debug(f"Could not read store: {e}")
if storage is None: if storage is None:
@@ -369,6 +369,11 @@ def load_data_package_for_checksum(game: str, checksum: typing.Optional[str]) ->
except Exception as e: except Exception as e:
logging.debug(f"Could not load data package: {e}") logging.debug(f"Could not load data package: {e}")
# fall back to old cache
cache = persistent_load().get("datapackage", {}).get("games", {}).get(game, {})
if cache.get("checksum") == checksum:
return cache
# cache does not match # cache does not match
return {} return {}
@@ -753,19 +758,6 @@ def is_kivy_running() -> bool:
return False return False
def env_cleared_lib_path() -> Mapping[str, str]:
"""
Creates a copy of the current environment vars with the LD_LIBRARY_PATH removed if set, as this can interfere when
launching something in a subprocess.
"""
env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"]
return env
def _mp_open_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args: Any) -> None: def _mp_open_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args: Any) -> None:
if is_kivy_running(): if is_kivy_running():
raise RuntimeError("kivy should not be running in multiprocess") raise RuntimeError("kivy should not be running in multiprocess")
@@ -778,7 +770,10 @@ def _mp_save_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args:
res.put(save_filename(*args)) res.put(save_filename(*args))
def _run_for_stdout(*args: str): def _run_for_stdout(*args: str):
env = env_cleared_lib_path() env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
return subprocess.run(args, capture_output=True, text=True, env=env).stdout.split("\n", 1)[0] or None return subprocess.run(args, capture_output=True, text=True, env=env).stdout.split("\n", 1)[0] or None

View File

@@ -110,14 +110,13 @@ if __name__ == "__main__":
logging.exception(e) logging.exception(e)
logging.warning("Could not update LttP sprites.") logging.warning("Could not update LttP sprites.")
app = get_app() app = get_app()
from worlds import AutoWorldRegister, network_data_package from worlds import AutoWorldRegister
# Update to only valid WebHost worlds # Update to only valid WebHost worlds
invalid_worlds = {name for name, world in AutoWorldRegister.world_types.items() invalid_worlds = {name for name, world in AutoWorldRegister.world_types.items()
if not hasattr(world.web, "tutorials")} if not hasattr(world.web, "tutorials")}
if invalid_worlds: if invalid_worlds:
logging.error(f"Following worlds not loaded as they are invalid for WebHost: {invalid_worlds}") logging.error(f"Following worlds not loaded as they are invalid for WebHost: {invalid_worlds}")
AutoWorldRegister.world_types = {k: v for k, v in AutoWorldRegister.world_types.items() if k not in invalid_worlds} AutoWorldRegister.world_types = {k: v for k, v in AutoWorldRegister.world_types.items() if k not in invalid_worlds}
network_data_package["games"] = {k: v for k, v in network_data_package["games"].items() if k not in invalid_worlds}
create_options_files() create_options_files()
copy_tutorials_files_to_static() copy_tutorials_files_to_static()
if app.config["SELFLAUNCH"]: if app.config["SELFLAUNCH"]:

View File

@@ -1,14 +1,14 @@
flask==3.1.3 flask>=3.1.1
werkzeug==3.1.6 werkzeug>=3.1.3
pony==0.7.19; python_version <= '3.12' pony>=0.7.19; python_version <= '3.12'
pony @ git+https://github.com/black-sliver/pony@7feb1221953b7fa4a6735466bf21a8b4d35e33ba#0.7.19; python_version >= '3.13' pony @ git+https://github.com/black-sliver/pony@7feb1221953b7fa4a6735466bf21a8b4d35e33ba#0.7.19; python_version >= '3.13'
waitress==3.0.2 waitress>=3.0.2
Flask-Caching==2.3.1 Flask-Caching>=2.3.0
Flask-Compress==1.18 # pkg_resources can't resolve the "backports.zstd" dependency of >1.18, breaking ModuleUpdate.py Flask-Compress==1.18 # pkg_resources can't resolve the "backports.zstd" dependency of >1.18, breaking ModuleUpdate.py
Flask-Limiter==4.1.1 Flask-Limiter>=3.12
Flask-Cors==6.0.2 Flask-Cors>=6.0.2
bokeh==3.8.2 bokeh>=3.6.3
markupsafe==3.0.3 markupsafe>=3.0.2
setproctitle==1.3.7 setproctitle>=1.3.5
mistune==3.2.0 mistune>=3.1.3
docutils==0.22.4 docutils>=0.22.2

View File

@@ -1,6 +1,6 @@
{% block footer %} {% block footer %}
<footer id="island-footer"> <footer id="island-footer">
<div id="copyright-notice">Copyright 2026 Archipelago</div> <div id="copyright-notice">Copyright 2025 Archipelago</div>
<div id="links"> <div id="links">
<a href="/sitemap">Site Map</a> <a href="/sitemap">Site Map</a>
- -

View File

@@ -33,9 +33,7 @@
<h1>Currently Supported Games</h1> <h1>Currently Supported Games</h1>
<p>Below are the games that are currently included with the Archipelago software. To play a game that is not on <p>Below are the games that are currently included with the Archipelago software. To play a game that is not on
this page, please refer to the <a href="/tutorial/Archipelago/setup/en#playing-with-custom-worlds">playing with this page, please refer to the <a href="/tutorial/Archipelago/setup/en#playing-with-custom-worlds">playing with
custom worlds</a> section of the setup guide and the custom worlds</a> section of the setup guide.</p>
<a href="{{ url_for("tutorial", game="Archipelago", file="other_en") }}">other games and tools guide</a>
to find more.</p>
<div class="js-only"> <div class="js-only">
<label for="game-search">Search for your game below!</label><br /> <label for="game-search">Search for your game below!</label><br />
<div class="page-controls"> <div class="page-controls">

View File

@@ -20,7 +20,11 @@
{% for file_name, file_data in tutorial_data.files.items() %} {% for file_name, file_data in tutorial_data.files.items() %}
<li> <li>
<a href="{{ url_for("tutorial", game=world_name, file=file_name) }}">{{ file_data.language }}</a> <a href="{{ url_for("tutorial", game=world_name, file=file_name) }}">{{ file_data.language }}</a>
by {{ file_data.authors | join(", ") }} by
{% for author in file_data.authors %}
{{ author }}
{% if not loop.last %}, {% endif %}
{% endfor %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -19,6 +19,8 @@
# NewSoupVi is acting maintainer, but world belongs to core with the exception of the music # NewSoupVi is acting maintainer, but world belongs to core with the exception of the music
/worlds/apquest/ @NewSoupVi /worlds/apquest/ @NewSoupVi
# Sudoku (APSudoku)
/worlds/apsudoku/ @EmilyV99
# Aquaria # Aquaria
/worlds/aquaria/ @tioui /worlds/aquaria/ @tioui
@@ -56,6 +58,9 @@
# Dark Souls III # Dark Souls III
/worlds/dark_souls_3/ @Marechal-L @nex3 /worlds/dark_souls_3/ @Marechal-L @nex3
# Donkey Kong Country 3
/worlds/dkc3/ @PoryGone
# DLCQuest # DLCQuest
/worlds/dlcquest/ @axe-y @agilbert1412 /worlds/dlcquest/ @axe-y @agilbert1412

View File

@@ -69,6 +69,12 @@ flowchart LR
end end
SNI <-- Various, depending on SNES device --> SMZ SNI <-- Various, depending on SNES device --> SMZ
%% Donkey Kong Country 3
subgraph Donkey Kong Country 3
DK3[SNES]
end
SNI <-- Various, depending on SNES device --> DK3
%% Super Mario World %% Super Mario World
subgraph Super Mario World subgraph Super Mario World
SMW[SNES] SMW[SNES]

View File

@@ -129,42 +129,6 @@ common_rule_only_on_easy = common_rule & easy_filter
common_rule_skipped_on_easy = common_rule | easy_filter common_rule_skipped_on_easy = common_rule | easy_filter
``` ```
### Field resolvers
When creating rules you may sometimes need to set a field to a value that depends on the world instance. You can use a `FieldResolver` to define how to populate that field when the rule is being resolved.
There are two build-in field resolvers:
- `FromOption`: Resolves to the value of the given option
- `FromWorldAttr`: Resolves to the value of the given world instance attribute, can specify a dotted path `a.b.c` to get a nested attribute or dict item
```python
world.options.mcguffin_count = 5
world.precalculated_value = 99
rule = (
Has("A", count=FromOption(McguffinCount))
| HasGroup("Important items", count=FromWorldAttr("precalculated_value"))
)
# Results in Has("A", count=5) | HasGroup("Important items", count=99)
```
You can define your own resolvers by creating a class that inherits from `FieldResolver`, provides your game name, and implements a `resolve` function:
```python
@dataclasses.dataclass(frozen=True)
class FromCustomResolution(FieldResolver, game="MyGame"):
modifier: str
@override
def resolve(self, world: "World") -> Any:
return some_math_calculation(world, self.modifier)
rule = Has("Combat Level", count=FromCustomResolution("combat"))
```
If you want to support rule serialization and your resolver contains non-serializable properties you may need to override `to_dict` or `from_dict`.
## Enabling caching ## Enabling caching
The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your rules. The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your rules.

View File

@@ -108,6 +108,7 @@ Example:
```json ```json
{ {
... ...
"Donkey Kong Country 3":"f90acedcd958213f483a6a4c238e2a3faf92165e",
"Factorio":"a699194a9589db3ebc0d821915864b422c782f44", "Factorio":"a699194a9589db3ebc0d821915864b422c782f44",
... ...
} }

View File

@@ -98,6 +98,11 @@ Root: HKCR; Subkey: "{#MyAppName}smpatch"; ValueData: "Arc
Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: "";
Root: HKCR; Subkey: ".apdkc3"; ValueData: "{#MyAppName}dkc3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}dkc3patch"; ValueData: "Archipelago Donkey Kong Country 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}dkc3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}dkc3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: "";
Root: HKCR; Subkey: ".apsmw"; ValueData: "{#MyAppName}smwpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Root: HKCR; Subkey: ".apsmw"; ValueData: "{#MyAppName}smwpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}smwpatch"; ValueData: "Archipelago Super Mario World Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}smwpatch"; ValueData: "Archipelago Super Mario World Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: "";
Root: HKCR; Subkey: "{#MyAppName}smwpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "{#MyAppName}smwpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: "";

View File

@@ -1,21 +1,21 @@
colorama==0.4.6 colorama>=0.4.6
websockets==13.1 # ,<14 websockets>=13.0.1,<14
PyYAML==6.0.3 PyYAML>=6.0.3
jellyfish==1.2.1 jellyfish>=1.2.1
jinja2==3.1.6 jinja2>=3.1.6
schema==0.7.8 schema>=0.7.8
kivy==2.3.1 kivy>=2.3.1
bsdiff4==1.2.6 bsdiff4>=1.2.6
platformdirs==4.9.4 platformdirs>=4.5.0
certifi==2026.2.25 certifi>=2025.11.12
cython==3.2.4 cython>=3.2.1
cymem==2.0.13 cymem>=2.0.13
orjson==3.11.7 orjson>=3.11.4
typing_extensions==4.15.0 typing_extensions>=4.15.0
pyshortcuts==1.9.7 pyshortcuts>=1.9.6
pathspec==1.0.4 pathspec>=0.12.1
kivymd @ git+https://github.com/kivymd/KivyMD@5ff9d0d kivymd @ git+https://github.com/kivymd/KivyMD@5ff9d0d
kivymd>=2.0.1.dev0 kivymd>=2.0.1.dev0
# Legacy world dependencies that custom worlds rely on # Legacy world dependencies that custom worlds rely on
Pymem==1.14.0 Pymem>=1.13.0

View File

@@ -1,162 +0,0 @@
import dataclasses
import importlib
from abc import ABC, abstractmethod
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, ClassVar, Self, TypeVar, cast, overload
from typing_extensions import override
from Options import Option
if TYPE_CHECKING:
from worlds.AutoWorld import World
class FieldResolverRegister:
"""A container class to contain world custom resolvers"""
custom_resolvers: ClassVar[dict[str, dict[str, type["FieldResolver"]]]] = {}
"""
A mapping of game name to mapping of resolver name to resolver class
to hold custom resolvers implemented by worlds
"""
@classmethod
def get_resolver_cls(cls, game_name: str, resolver_name: str) -> type["FieldResolver"]:
"""Returns the world-registered or default resolver with the given name"""
custom_resolver_classes = cls.custom_resolvers.get(game_name, {})
if resolver_name not in DEFAULT_RESOLVERS and resolver_name not in custom_resolver_classes:
raise ValueError(f"Resolver '{resolver_name}' for game '{game_name}' not found")
return custom_resolver_classes.get(resolver_name) or DEFAULT_RESOLVERS[resolver_name]
@dataclasses.dataclass(frozen=True)
class FieldResolver(ABC):
@abstractmethod
def resolve(self, world: "World") -> Any: ...
def to_dict(self) -> dict[str, Any]:
"""Returns a JSON compatible dict representation of this resolver"""
fields = {field.name: getattr(self, field.name, None) for field in dataclasses.fields(self)}
return {
"resolver": self.__class__.__name__,
**fields,
}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> Self:
"""Returns a new instance of this resolver from a serialized dict representation"""
assert data.get("resolver", None) == cls.__name__
return cls(**{k: v for k, v in data.items() if k != "resolver"})
@override
def __str__(self) -> str:
return self.__class__.__name__
@classmethod
def __init_subclass__(cls, /, game: str) -> None:
if game != "Archipelago":
custom_resolvers = FieldResolverRegister.custom_resolvers.setdefault(game, {})
if cls.__qualname__ in custom_resolvers:
raise TypeError(f"Resolver {cls.__qualname__} has already been registered for game {game}")
custom_resolvers[cls.__qualname__] = cls
elif cls.__module__ != "rule_builder.field_resolvers":
raise TypeError("You cannot define custom resolvers for the base Archipelago world")
@dataclasses.dataclass(frozen=True)
class FromOption(FieldResolver, game="Archipelago"):
option: type[Option[Any]]
field: str = "value"
@override
def resolve(self, world: "World") -> Any:
option_name = next(
(name for name, cls in world.options.__class__.type_hints.items() if cls is self.option),
None,
)
if option_name is None:
raise ValueError(
f"Cannot find option {self.option.__name__} in options class {world.options.__class__.__name__}"
)
opt = cast(Option[Any] | None, getattr(world.options, option_name, None))
if opt is None:
raise ValueError(f"Invalid option: {option_name}")
return getattr(opt, self.field)
@override
def to_dict(self) -> dict[str, Any]:
return {
"resolver": "FromOption",
"option": f"{self.option.__module__}.{self.option.__name__}",
"field": self.field,
}
@override
@classmethod
def from_dict(cls, data: dict[str, Any]) -> Self:
if "option" not in data:
raise ValueError("Missing required option")
option_path = data["option"]
try:
option_mod_name, option_cls_name = option_path.rsplit(".", 1)
option_module = importlib.import_module(option_mod_name)
option = getattr(option_module, option_cls_name, None)
except (ValueError, ImportError) as e:
raise ValueError(f"Cannot parse option '{option_path}'") from e
if option is None or not issubclass(option, Option):
raise ValueError(f"Invalid option '{option_path}' returns type '{option}' instead of Option subclass")
return cls(cast(type[Option[Any]], option), data.get("field", "value"))
@override
def __str__(self) -> str:
field = f".{self.field}" if self.field != "value" else ""
return f"FromOption({self.option.__name__}{field})"
@dataclasses.dataclass(frozen=True)
class FromWorldAttr(FieldResolver, game="Archipelago"):
name: str
@override
def resolve(self, world: "World") -> Any:
obj: Any = world
for field in self.name.split("."):
if obj is None:
return None
if isinstance(obj, Mapping):
obj = obj.get(field, None) # pyright: ignore[reportUnknownMemberType]
else:
obj = getattr(obj, field, None)
return obj
@override
def __str__(self) -> str:
return f"FromWorldAttr({self.name})"
T = TypeVar("T")
@overload
def resolve_field(field: Any, world: "World", expected_type: type[T]) -> T: ...
@overload
def resolve_field(field: Any, world: "World", expected_type: None = None) -> Any: ...
def resolve_field(field: Any, world: "World", expected_type: type[T] | None = None) -> T | Any:
if isinstance(field, FieldResolver):
field = field.resolve(world)
if expected_type:
assert isinstance(field, expected_type), f"Expected type {expected_type} but got {type(field)}"
return field
DEFAULT_RESOLVERS = {
resolver_name: resolver_class
for resolver_name, resolver_class in locals().items()
if isinstance(resolver_class, type)
and issubclass(resolver_class, FieldResolver)
and resolver_class is not FieldResolver
}

View File

@@ -7,7 +7,6 @@ from typing_extensions import TypeVar, dataclass_transform, override
from BaseClasses import CollectionState from BaseClasses import CollectionState
from NetUtils import JSONMessagePart from NetUtils import JSONMessagePart
from .field_resolvers import FieldResolver, FieldResolverRegister, resolve_field
from .options import OptionFilter from .options import OptionFilter
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -109,14 +108,11 @@ class Rule(Generic[TWorld]):
def to_dict(self) -> dict[str, Any]: def to_dict(self) -> dict[str, Any]:
"""Returns a JSON compatible dict representation of this rule""" """Returns a JSON compatible dict representation of this rule"""
args = {} args = {
for field in dataclasses.fields(self): field.name: getattr(self, field.name, None)
if field.name in ("options", "filtered_resolution"): for field in dataclasses.fields(self)
continue if field.name not in ("options", "filtered_resolution")
value = getattr(self, field.name, None) }
if isinstance(value, FieldResolver):
value = value.to_dict()
args[field.name] = value
return { return {
"rule": self.__class__.__qualname__, "rule": self.__class__.__qualname__,
"options": [o.to_dict() for o in self.options], "options": [o.to_dict() for o in self.options],
@@ -128,19 +124,7 @@ class Rule(Generic[TWorld]):
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self: def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
"""Returns a new instance of this rule from a serialized dict representation""" """Returns a new instance of this rule from a serialized dict representation"""
options = OptionFilter.multiple_from_dict(data.get("options", ())) options = OptionFilter.multiple_from_dict(data.get("options", ()))
args = cls._parse_field_resolvers(data.get("args", {}), world_cls.game) return cls(**data.get("args", {}), options=options, filtered_resolution=data.get("filtered_resolution", False))
return cls(**args, options=options, filtered_resolution=data.get("filtered_resolution", False))
@classmethod
def _parse_field_resolvers(cls, data: Mapping[str, Any], game_name: str) -> dict[str, Any]:
result: dict[str, Any] = {}
for name, value in data.items():
if isinstance(value, dict) and "resolver" in value:
resolver_cls = FieldResolverRegister.get_resolver_cls(game_name, value["resolver"]) # pyright: ignore[reportUnknownArgumentType]
result[name] = resolver_cls.from_dict(value) # pyright: ignore[reportUnknownArgumentType]
else:
result[name] = value
return result
def __and__(self, other: "Rule[Any] | Iterable[OptionFilter] | OptionFilter") -> "Rule[TWorld]": def __and__(self, other: "Rule[Any] | Iterable[OptionFilter] | OptionFilter") -> "Rule[TWorld]":
"""Combines two rules or a rule and an option filter into an And rule""" """Combines two rules or a rule and an option filter into an And rule"""
@@ -543,7 +527,7 @@ class Or(NestedRule[TWorld], game="Archipelago"):
items[item] = 1 items[item] = 1
elif isinstance(child, HasAnyCount.Resolved): elif isinstance(child, HasAnyCount.Resolved):
for item, count in child.item_counts: for item, count in child.item_counts:
if item not in items or count < items[item]: if item not in items or items[item] < count:
items[item] = count items[item] = count
else: else:
clauses.append(child) clauses.append(child)
@@ -704,24 +688,24 @@ class Filtered(WrapperRule[TWorld], game="Archipelago"):
class Has(Rule[TWorld], game="Archipelago"): class Has(Rule[TWorld], game="Archipelago"):
"""A rule that checks if the player has at least `count` of a given item""" """A rule that checks if the player has at least `count` of a given item"""
item_name: str | FieldResolver item_name: str
"""The item to check for""" """The item to check for"""
count: int | FieldResolver = 1 count: int = 1
"""The count the player is required to have""" """The count the player is required to have"""
@override @override
def _instantiate(self, world: TWorld) -> Rule.Resolved: def _instantiate(self, world: TWorld) -> Rule.Resolved:
return self.Resolved( return self.Resolved(
resolve_field(self.item_name, world, str), self.item_name,
count=resolve_field(self.count, world, int), self.count,
player=world.player, player=world.player,
caching_enabled=getattr(world, "rule_caching_enabled", False), caching_enabled=getattr(world, "rule_caching_enabled", False),
) )
@override @override
def __str__(self) -> str: def __str__(self) -> str:
count = f", count={self.count}" if isinstance(self.count, FieldResolver) or self.count > 1 else "" count = f", count={self.count}" if self.count > 1 else ""
options = f", options={self.options}" if self.options else "" options = f", options={self.options}" if self.options else ""
return f"{self.__class__.__name__}({self.item_name}{count}{options})" return f"{self.__class__.__name__}({self.item_name}{count}{options})"
@@ -1007,7 +991,7 @@ class HasAny(Rule[TWorld], game="Archipelago"):
class HasAllCounts(Rule[TWorld], game="Archipelago"): class HasAllCounts(Rule[TWorld], game="Archipelago"):
"""A rule that checks if the player has all of the specified counts of the given items""" """A rule that checks if the player has all of the specified counts of the given items"""
item_counts: Mapping[str, int | FieldResolver] item_counts: dict[str, int]
"""A mapping of item name to count to check for""" """A mapping of item name to count to check for"""
@override @override
@@ -1018,30 +1002,12 @@ class HasAllCounts(Rule[TWorld], game="Archipelago"):
if len(self.item_counts) == 1: if len(self.item_counts) == 1:
item = next(iter(self.item_counts)) item = next(iter(self.item_counts))
return Has(item, self.item_counts[item]).resolve(world) return Has(item, self.item_counts[item]).resolve(world)
item_counts = tuple((name, resolve_field(count, world, int)) for name, count in self.item_counts.items())
return self.Resolved( return self.Resolved(
item_counts, tuple(self.item_counts.items()),
player=world.player, player=world.player,
caching_enabled=getattr(world, "rule_caching_enabled", False), caching_enabled=getattr(world, "rule_caching_enabled", False),
) )
@override
def to_dict(self) -> dict[str, Any]:
output = super().to_dict()
output["args"]["item_counts"] = {
key: value.to_dict() if isinstance(value, FieldResolver) else value
for key, value in output["args"]["item_counts"].items()
}
return output
@override
@classmethod
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
args = data.get("args", {})
item_counts = cls._parse_field_resolvers(args.get("item_counts", {}), world_cls.game)
options = OptionFilter.multiple_from_dict(data.get("options", ()))
return cls(item_counts, options=options, filtered_resolution=data.get("filtered_resolution", False))
@override @override
def __str__(self) -> str: def __str__(self) -> str:
items = ", ".join([f"{item} x{count}" for item, count in self.item_counts.items()]) items = ", ".join([f"{item} x{count}" for item, count in self.item_counts.items()])
@@ -1130,7 +1096,7 @@ class HasAllCounts(Rule[TWorld], game="Archipelago"):
class HasAnyCount(Rule[TWorld], game="Archipelago"): class HasAnyCount(Rule[TWorld], game="Archipelago"):
"""A rule that checks if the player has any of the specified counts of the given items""" """A rule that checks if the player has any of the specified counts of the given items"""
item_counts: Mapping[str, int | FieldResolver] item_counts: dict[str, int]
"""A mapping of item name to count to check for""" """A mapping of item name to count to check for"""
@override @override
@@ -1141,30 +1107,12 @@ class HasAnyCount(Rule[TWorld], game="Archipelago"):
if len(self.item_counts) == 1: if len(self.item_counts) == 1:
item = next(iter(self.item_counts)) item = next(iter(self.item_counts))
return Has(item, self.item_counts[item]).resolve(world) return Has(item, self.item_counts[item]).resolve(world)
item_counts = tuple((name, resolve_field(count, world, int)) for name, count in self.item_counts.items())
return self.Resolved( return self.Resolved(
item_counts, tuple(self.item_counts.items()),
player=world.player, player=world.player,
caching_enabled=getattr(world, "rule_caching_enabled", False), caching_enabled=getattr(world, "rule_caching_enabled", False),
) )
@override
def to_dict(self) -> dict[str, Any]:
output = super().to_dict()
output["args"]["item_counts"] = {
key: value.to_dict() if isinstance(value, FieldResolver) else value
for key, value in output["args"]["item_counts"].items()
}
return output
@override
@classmethod
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
args = data.get("args", {})
item_counts = cls._parse_field_resolvers(args.get("item_counts", {}), world_cls.game)
options = OptionFilter.multiple_from_dict(data.get("options", ()))
return cls(item_counts, options=options, filtered_resolution=data.get("filtered_resolution", False))
@override @override
def __str__(self) -> str: def __str__(self) -> str:
items = ", ".join([f"{item} x{count}" for item, count in self.item_counts.items()]) items = ", ".join([f"{item} x{count}" for item, count in self.item_counts.items()])
@@ -1256,13 +1204,13 @@ class HasFromList(Rule[TWorld], game="Archipelago"):
item_names: tuple[str, ...] item_names: tuple[str, ...]
"""A tuple of item names to check for""" """A tuple of item names to check for"""
count: int | FieldResolver = 1 count: int = 1
"""The number of items the player needs to have""" """The number of items the player needs to have"""
def __init__( def __init__(
self, self,
*item_names: str, *item_names: str,
count: int | FieldResolver = 1, count: int = 1,
options: Iterable[OptionFilter] = (), options: Iterable[OptionFilter] = (),
filtered_resolution: bool = False, filtered_resolution: bool = False,
) -> None: ) -> None:
@@ -1279,7 +1227,7 @@ class HasFromList(Rule[TWorld], game="Archipelago"):
return Has(self.item_names[0], self.count).resolve(world) return Has(self.item_names[0], self.count).resolve(world)
return self.Resolved( return self.Resolved(
self.item_names, self.item_names,
count=resolve_field(self.count, world, int), self.count,
player=world.player, player=world.player,
caching_enabled=getattr(world, "rule_caching_enabled", False), caching_enabled=getattr(world, "rule_caching_enabled", False),
) )
@@ -1287,7 +1235,7 @@ class HasFromList(Rule[TWorld], game="Archipelago"):
@override @override
@classmethod @classmethod
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self: def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
args = cls._parse_field_resolvers(data.get("args", {}), world_cls.game) args = {**data.get("args", {})}
item_names = args.pop("item_names", ()) item_names = args.pop("item_names", ())
options = OptionFilter.multiple_from_dict(data.get("options", ())) options = OptionFilter.multiple_from_dict(data.get("options", ()))
return cls(*item_names, **args, options=options, filtered_resolution=data.get("filtered_resolution", False)) return cls(*item_names, **args, options=options, filtered_resolution=data.get("filtered_resolution", False))
@@ -1390,13 +1338,13 @@ class HasFromListUnique(Rule[TWorld], game="Archipelago"):
item_names: tuple[str, ...] item_names: tuple[str, ...]
"""A tuple of item names to check for""" """A tuple of item names to check for"""
count: int | FieldResolver = 1 count: int = 1
"""The number of items the player needs to have""" """The number of items the player needs to have"""
def __init__( def __init__(
self, self,
*item_names: str, *item_names: str,
count: int | FieldResolver = 1, count: int = 1,
options: Iterable[OptionFilter] = (), options: Iterable[OptionFilter] = (),
filtered_resolution: bool = False, filtered_resolution: bool = False,
) -> None: ) -> None:
@@ -1406,15 +1354,14 @@ class HasFromListUnique(Rule[TWorld], game="Archipelago"):
@override @override
def _instantiate(self, world: TWorld) -> Rule.Resolved: def _instantiate(self, world: TWorld) -> Rule.Resolved:
count = resolve_field(self.count, world, int) if len(self.item_names) == 0 or len(self.item_names) < self.count:
if len(self.item_names) == 0 or len(self.item_names) < count:
# match state.has_from_list_unique # match state.has_from_list_unique
return False_().resolve(world) return False_().resolve(world)
if len(self.item_names) == 1: if len(self.item_names) == 1:
return Has(self.item_names[0]).resolve(world) return Has(self.item_names[0]).resolve(world)
return self.Resolved( return self.Resolved(
self.item_names, self.item_names,
count, self.count,
player=world.player, player=world.player,
caching_enabled=getattr(world, "rule_caching_enabled", False), caching_enabled=getattr(world, "rule_caching_enabled", False),
) )
@@ -1422,7 +1369,7 @@ class HasFromListUnique(Rule[TWorld], game="Archipelago"):
@override @override
@classmethod @classmethod
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self: def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
args = cls._parse_field_resolvers(data.get("args", {}), world_cls.game) args = {**data.get("args", {})}
item_names = args.pop("item_names", ()) item_names = args.pop("item_names", ())
options = OptionFilter.multiple_from_dict(data.get("options", ())) options = OptionFilter.multiple_from_dict(data.get("options", ()))
return cls(*item_names, **args, options=options, filtered_resolution=data.get("filtered_resolution", False)) return cls(*item_names, **args, options=options, filtered_resolution=data.get("filtered_resolution", False))
@@ -1521,7 +1468,7 @@ class HasGroup(Rule[TWorld], game="Archipelago"):
item_name_group: str item_name_group: str
"""The name of the item group containing the items""" """The name of the item group containing the items"""
count: int | FieldResolver = 1 count: int = 1
"""The number of items the player needs to have""" """The number of items the player needs to have"""
@override @override
@@ -1530,14 +1477,14 @@ class HasGroup(Rule[TWorld], game="Archipelago"):
return self.Resolved( return self.Resolved(
self.item_name_group, self.item_name_group,
item_names, item_names,
count=resolve_field(self.count, world, int), self.count,
player=world.player, player=world.player,
caching_enabled=getattr(world, "rule_caching_enabled", False), caching_enabled=getattr(world, "rule_caching_enabled", False),
) )
@override @override
def __str__(self) -> str: def __str__(self) -> str:
count = f", count={self.count}" if isinstance(self.count, FieldResolver) or self.count > 1 else "" count = f", count={self.count}" if self.count > 1 else ""
options = f", options={self.options}" if self.options else "" options = f", options={self.options}" if self.options else ""
return f"{self.__class__.__name__}({self.item_name_group}{count}{options})" return f"{self.__class__.__name__}({self.item_name_group}{count}{options})"
@@ -1595,7 +1542,7 @@ class HasGroupUnique(Rule[TWorld], game="Archipelago"):
item_name_group: str item_name_group: str
"""The name of the item group containing the items""" """The name of the item group containing the items"""
count: int | FieldResolver = 1 count: int = 1
"""The number of items the player needs to have""" """The number of items the player needs to have"""
@override @override
@@ -1604,14 +1551,14 @@ class HasGroupUnique(Rule[TWorld], game="Archipelago"):
return self.Resolved( return self.Resolved(
self.item_name_group, self.item_name_group,
item_names, item_names,
count=resolve_field(self.count, world, int), self.count,
player=world.player, player=world.player,
caching_enabled=getattr(world, "rule_caching_enabled", False), caching_enabled=getattr(world, "rule_caching_enabled", False),
) )
@override @override
def __str__(self) -> str: def __str__(self) -> str:
count = f", count={self.count}" if isinstance(self.count, FieldResolver) or self.count > 1 else "" count = f", count={self.count}" if self.count > 1 else ""
options = f", options={self.options}" if self.options else "" options = f", options={self.options}" if self.options else ""
return f"{self.__class__.__name__}({self.item_name_group}{count}{options})" return f"{self.__class__.__name__}({self.item_name_group}{count}{options})"

View File

@@ -71,6 +71,7 @@ non_apworlds: set[str] = {
"Ocarina of Time", "Ocarina of Time",
"Overcooked! 2", "Overcooked! 2",
"Raft", "Raft",
"Sudoku",
"Super Mario 64", "Super Mario 64",
"VVVVVV", "VVVVVV",
"Wargroove", "Wargroove",
@@ -657,7 +658,7 @@ cx_Freeze.setup(
options={ options={
"build_exe": { "build_exe": {
"packages": ["worlds", "kivy", "cymem", "websockets", "kivymd"], "packages": ["worlds", "kivy", "cymem", "websockets", "kivymd"],
"includes": ["rule_builder.cached_world"], "includes": [],
"excludes": ["numpy", "Cython", "PySide2", "PIL", "excludes": ["numpy", "Cython", "PySide2", "PIL",
"pandas"], "pandas"],
"zip_includes": [], "zip_includes": [],

View File

@@ -11,7 +11,7 @@ class TestImplemented(unittest.TestCase):
def test_completion_condition(self): def test_completion_condition(self):
"""Ensure a completion condition is set that has requirements.""" """Ensure a completion condition is set that has requirements."""
for game_name, world_type in AutoWorldRegister.world_types.items(): for game_name, world_type in AutoWorldRegister.world_types.items():
if not world_type.hidden: if not world_type.hidden and game_name not in {"Sudoku"}:
with self.subTest(game_name): with self.subTest(game_name):
multiworld = setup_solo_multiworld(world_type) multiworld = setup_solo_multiworld(world_type)
self.assertFalse(multiworld.completion_condition[1](multiworld.state)) self.assertFalse(multiworld.completion_condition[1](multiworld.state))
@@ -59,7 +59,7 @@ class TestImplemented(unittest.TestCase):
def test_prefill_items(self): def test_prefill_items(self):
"""Test that every world can reach every location from allstate before pre_fill.""" """Test that every world can reach every location from allstate before pre_fill."""
for gamename, world_type in AutoWorldRegister.world_types.items(): for gamename, world_type in AutoWorldRegister.world_types.items():
if gamename not in ("Archipelago", "Final Fantasy", "Test Game"): if gamename not in ("Archipelago", "Sudoku", "Final Fantasy", "Test Game"):
with self.subTest(gamename): with self.subTest(gamename):
multiworld = setup_solo_multiworld(world_type, ("generate_early", "create_regions", "create_items", multiworld = setup_solo_multiworld(world_type, ("generate_early", "create_regions", "create_items",
"set_rules", "connect_entrances", "generate_basic")) "set_rules", "connect_entrances", "generate_basic"))

View File

@@ -109,7 +109,7 @@ class TestOptions(unittest.TestCase):
def test_option_set_keys_random(self): def test_option_set_keys_random(self):
"""Tests that option sets do not contain 'random' and its variants as valid keys""" """Tests that option sets do not contain 'random' and its variants as valid keys"""
for game_name, world_type in AutoWorldRegister.world_types.items(): for game_name, world_type in AutoWorldRegister.world_types.items():
if game_name not in ("Archipelago", "Super Metroid"): if game_name not in ("Archipelago", "Sudoku", "Super Metroid"):
for option_key, option in world_type.options_dataclass.type_hints.items(): for option_key, option in world_type.options_dataclass.type_hints.items():
if issubclass(option, OptionSet): if issubclass(option, OptionSet):
with self.subTest(game=game_name, option=option_key): with self.subTest(game=game_name, option=option_key):

View File

@@ -6,9 +6,8 @@ from typing_extensions import override
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
from NetUtils import JSONMessagePart from NetUtils import JSONMessagePart
from Options import Choice, FreeText, Option, OptionSet, PerGameCommonOptions, Range, Toggle from Options import Choice, FreeText, Option, OptionSet, PerGameCommonOptions, Toggle
from rule_builder.cached_world import CachedRuleBuilderWorld from rule_builder.cached_world import CachedRuleBuilderWorld
from rule_builder.field_resolvers import FieldResolver, FromOption, FromWorldAttr, resolve_field
from rule_builder.options import Operator, OptionFilter from rule_builder.options import Operator, OptionFilter
from rule_builder.rules import ( from rule_builder.rules import (
And, And,
@@ -60,20 +59,12 @@ class SetOption(OptionSet):
valid_keys: ClassVar[set[str]] = {"one", "two", "three"} # pyright: ignore[reportIncompatibleVariableOverride] valid_keys: ClassVar[set[str]] = {"one", "two", "three"} # pyright: ignore[reportIncompatibleVariableOverride]
class RangeOption(Range):
auto_display_name = True
range_start = 1
range_end = 10
default = 5
@dataclass @dataclass
class RuleBuilderOptions(PerGameCommonOptions): class RuleBuilderOptions(PerGameCommonOptions):
toggle_option: ToggleOption toggle_option: ToggleOption
choice_option: ChoiceOption choice_option: ChoiceOption
text_option: FreeTextOption text_option: FreeTextOption
set_option: SetOption set_option: SetOption
range_option: RangeOption
GAME_NAME = "Rule Builder Test Game" GAME_NAME = "Rule Builder Test Game"
@@ -242,14 +233,6 @@ class CachedRuleBuilderTestCase(RuleBuilderTestCase):
Or(Has("A"), HasAny("B", "C"), HasAnyCount({"D": 1, "E": 1})), Or(Has("A"), HasAny("B", "C"), HasAnyCount({"D": 1, "E": 1})),
HasAny.Resolved(("A", "B", "C", "D", "E"), player=1), HasAny.Resolved(("A", "B", "C", "D", "E"), player=1),
), ),
(
And(HasAllCounts({"A": 1, "B": 2}), HasAllCounts({"A": 2, "B": 2})),
HasAllCounts.Resolved((("A", 2), ("B", 2)), player=1),
),
(
Or(HasAnyCount({"A": 1, "B": 2}), HasAnyCount({"A": 2, "B": 2})),
HasAnyCount.Resolved((("A", 1), ("B", 2)), player=1),
),
) )
) )
class TestSimplify(RuleBuilderTestCase): class TestSimplify(RuleBuilderTestCase):
@@ -668,15 +651,14 @@ class TestRules(RuleBuilderTestCase):
self.assertFalse(resolved_rule(self.state)) self.assertFalse(resolved_rule(self.state))
def test_has_any_count(self) -> None: def test_has_any_count(self) -> None:
item_counts: dict[str, int | FieldResolver] = {"Item 1": 1, "Item 2": 2} item_counts = {"Item 1": 1, "Item 2": 2}
rule = HasAnyCount(item_counts) rule = HasAnyCount(item_counts)
resolved_rule = rule.resolve(self.world) resolved_rule = rule.resolve(self.world)
self.world.register_rule_dependencies(resolved_rule) self.world.register_rule_dependencies(resolved_rule)
for item_name, count in item_counts.items(): for item_name, count in item_counts.items():
item = self.world.create_item(item_name) item = self.world.create_item(item_name)
num_items = resolve_field(count, self.world, int) for _ in range(count):
for _ in range(num_items):
self.assertFalse(resolved_rule(self.state)) self.assertFalse(resolved_rule(self.state))
self.state.collect(item) self.state.collect(item)
self.assertTrue(resolved_rule(self.state)) self.assertTrue(resolved_rule(self.state))
@@ -773,7 +755,7 @@ class TestSerialization(RuleBuilderTestCase):
rule: ClassVar[Rule[Any]] = And( rule: ClassVar[Rule[Any]] = And(
Or( Or(
Has("i1", count=FromOption(RangeOption)), Has("i1", count=4),
HasFromList("i2", "i3", "i4", count=2), HasFromList("i2", "i3", "i4", count=2),
HasAnyCount({"i5": 2, "i6": 3}), HasAnyCount({"i5": 2, "i6": 3}),
options=[OptionFilter(ToggleOption, 0)], options=[OptionFilter(ToggleOption, 0)],
@@ -781,7 +763,7 @@ class TestSerialization(RuleBuilderTestCase):
Or( Or(
HasAll("i7", "i8"), HasAll("i7", "i8"),
HasAllCounts( HasAllCounts(
{"i9": 1, "i10": FromWorldAttr("instance_data.i10_count")}, {"i9": 1, "i10": 5},
options=[OptionFilter(ToggleOption, 1, operator="ne")], options=[OptionFilter(ToggleOption, 1, operator="ne")],
filtered_resolution=True, filtered_resolution=True,
), ),
@@ -821,14 +803,7 @@ class TestSerialization(RuleBuilderTestCase):
"rule": "Has", "rule": "Has",
"options": [], "options": [],
"filtered_resolution": False, "filtered_resolution": False,
"args": { "args": {"item_name": "i1", "count": 4},
"item_name": "i1",
"count": {
"resolver": "FromOption",
"option": "test.general.test_rule_builder.RangeOption",
"field": "value",
},
},
}, },
{ {
"rule": "HasFromList", "rule": "HasFromList",
@@ -865,12 +840,7 @@ class TestSerialization(RuleBuilderTestCase):
}, },
], ],
"filtered_resolution": True, "filtered_resolution": True,
"args": { "args": {"item_counts": {"i9": 1, "i10": 5}},
"item_counts": {
"i9": 1,
"i10": {"resolver": "FromWorldAttr", "name": "instance_data.i10_count"},
}
},
}, },
{ {
"rule": "CanReachRegion", "rule": "CanReachRegion",
@@ -945,7 +915,7 @@ class TestSerialization(RuleBuilderTestCase):
multiworld = setup_solo_multiworld(self.world_cls, steps=(), seed=0) multiworld = setup_solo_multiworld(self.world_cls, steps=(), seed=0)
world = multiworld.worlds[1] world = multiworld.worlds[1]
deserialized_rule = world.rule_from_dict(self.rule_dict) deserialized_rule = world.rule_from_dict(self.rule_dict)
self.assertEqual(deserialized_rule, self.rule, f"\n{deserialized_rule}\n{self.rule}") self.assertEqual(deserialized_rule, self.rule, str(deserialized_rule))
class TestExplain(RuleBuilderTestCase): class TestExplain(RuleBuilderTestCase):
@@ -1364,32 +1334,3 @@ class TestExplain(RuleBuilderTestCase):
"& False)", "& False)",
) )
assert str(self.resolved_rule) == " ".join(expected) assert str(self.resolved_rule) == " ".join(expected)
@classvar_matrix(
rules=(
(
Has("A", FromOption(RangeOption)),
Has.Resolved("A", count=5, player=1),
),
(
Has("B", FromWorldAttr("pre_calculated")),
Has.Resolved("B", count=3, player=1),
),
(
Has("C", FromWorldAttr("instance_data.key")),
Has.Resolved("C", count=7, player=1),
),
)
)
class TestFieldResolvers(RuleBuilderTestCase):
rules: ClassVar[tuple[Rule[Any], Rule.Resolved]]
def test_simplify(self) -> None:
multiworld = setup_solo_multiworld(self.world_cls, steps=("generate_early",), seed=0)
world = multiworld.worlds[1]
world.pre_calculated = 3 # pyright: ignore[reportAttributeAccessIssue]
world.instance_data = {"key": 7} # pyright: ignore[reportAttributeAccessIssue]
rule, expected = self.rules
resolved_rule = rule.resolve(world)
self.assertEqual(resolved_rule, expected, f"\n{resolved_rule}\n{expected}")

View File

@@ -269,9 +269,8 @@ if not is_frozen():
from Launcher import open_folder from Launcher import open_folder
import argparse import argparse
parser = argparse.ArgumentParser(prog="Build APWorlds", description="Build script for APWorlds") parser = argparse.ArgumentParser("Build script for APWorlds")
parser.add_argument("worlds", type=str, default=(), nargs="*", help="names of APWorlds to build") parser.add_argument("worlds", type=str, default=(), nargs="*", help="Names of APWorlds to build.")
parser.add_argument("--skip_open_folder", action="store_true", help="don't open the output build folder")
args = parser.parse_args(launch_args) args = parser.parse_args(launch_args)
if args.worlds: if args.worlds:
@@ -321,8 +320,6 @@ if not is_frozen():
zf.write(pathlib.Path(world_directory, file), pathlib.Path(file_name, file)) zf.write(pathlib.Path(world_directory, file), pathlib.Path(file_name, file))
zf.writestr(apworld.manifest_path, json.dumps(manifest)) zf.writestr(apworld.manifest_path, json.dumps(manifest))
if not args.skip_open_folder:
open_folder(apworlds_folder) open_folder(apworlds_folder)
components.append(Component("Build APWorlds", func=_build_apworlds, cli=True, components.append(Component("Build APWorlds", func=_build_apworlds, cli=True,

View File

@@ -3,10 +3,10 @@ from Options import PerGameCommonOptions
from .Locations import location_table, AdventureLocation, dragon_room_to_region from .Locations import location_table, AdventureLocation, dragon_room_to_region
def connect(multiworld: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True, def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
one_way=False, name=None): one_way=False, name=None):
source_region = multiworld.get_region(source, player) source_region = world.get_region(source, player)
target_region = multiworld.get_region(target, player) target_region = world.get_region(target, player)
if name is None: if name is None:
name = source + " to " + target name = source + " to " + target
@@ -22,7 +22,7 @@ def connect(multiworld: MultiWorld, player: int, source: str, target: str, rule:
source_region.exits.append(connection) source_region.exits.append(connection)
connection.connect(target_region) connection.connect(target_region)
if not one_way: if not one_way:
connect(multiworld, player, target, source, rule, True) connect(world, player, target, source, rule, True)
def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None: def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:

View File

@@ -3,47 +3,47 @@ from worlds.generic.Rules import add_rule, set_rule, forbid_item
def set_rules(self) -> None: def set_rules(self) -> None:
multiworld = self.multiworld world = self.multiworld
use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic
set_rule(multiworld.get_entrance("YellowCastlePort", self.player), set_rule(world.get_entrance("YellowCastlePort", self.player),
lambda state: state.has("Yellow Key", self.player)) lambda state: state.has("Yellow Key", self.player))
set_rule(multiworld.get_entrance("BlackCastlePort", self.player), set_rule(world.get_entrance("BlackCastlePort", self.player),
lambda state: state.has("Black Key", self.player)) lambda state: state.has("Black Key", self.player))
set_rule(multiworld.get_entrance("WhiteCastlePort", self.player), set_rule(world.get_entrance("WhiteCastlePort", self.player),
lambda state: state.has("White Key", self.player)) lambda state: state.has("White Key", self.player))
# a future thing would be to make the bat an actual item, or at least allow it to # a future thing would be to make the bat an actual item, or at least allow it to
# be placed in a castle, which would require some additions to the rules when # be placed in a castle, which would require some additions to the rules when
# use_bat_logic is true # use_bat_logic is true
if not use_bat_logic: if not use_bat_logic:
set_rule(multiworld.get_entrance("WhiteCastleSecretPassage", self.player), set_rule(world.get_entrance("WhiteCastleSecretPassage", self.player),
lambda state: state.has("Bridge", self.player)) lambda state: state.has("Bridge", self.player))
set_rule(multiworld.get_entrance("WhiteCastlePeekPassage", self.player), set_rule(world.get_entrance("WhiteCastlePeekPassage", self.player),
lambda state: state.has("Bridge", self.player) or lambda state: state.has("Bridge", self.player) or
state.has("Magnet", self.player)) state.has("Magnet", self.player))
set_rule(multiworld.get_entrance("BlackCastleVaultEntrance", self.player), set_rule(world.get_entrance("BlackCastleVaultEntrance", self.player),
lambda state: state.has("Bridge", self.player) or lambda state: state.has("Bridge", self.player) or
state.has("Magnet", self.player)) state.has("Magnet", self.player))
dragon_slay_check = self.options.dragon_slay_check.value dragon_slay_check = self.options.dragon_slay_check.value
if dragon_slay_check: if dragon_slay_check:
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item: if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
set_rule(multiworld.get_location("Slay Yorgle", self.player), set_rule(world.get_location("Slay Yorgle", self.player),
lambda state: state.has("Sword", self.player) and lambda state: state.has("Sword", self.player) and
state.has("Right Difficulty Switch", self.player)) state.has("Right Difficulty Switch", self.player))
set_rule(multiworld.get_location("Slay Grundle", self.player), set_rule(world.get_location("Slay Grundle", self.player),
lambda state: state.has("Sword", self.player) and lambda state: state.has("Sword", self.player) and
state.has("Right Difficulty Switch", self.player)) state.has("Right Difficulty Switch", self.player))
set_rule(multiworld.get_location("Slay Rhindle", self.player), set_rule(world.get_location("Slay Rhindle", self.player),
lambda state: state.has("Sword", self.player) and lambda state: state.has("Sword", self.player) and
state.has("Right Difficulty Switch", self.player)) state.has("Right Difficulty Switch", self.player))
else: else:
set_rule(multiworld.get_location("Slay Yorgle", self.player), set_rule(world.get_location("Slay Yorgle", self.player),
lambda state: state.has("Sword", self.player)) lambda state: state.has("Sword", self.player))
set_rule(multiworld.get_location("Slay Grundle", self.player), set_rule(world.get_location("Slay Grundle", self.player),
lambda state: state.has("Sword", self.player)) lambda state: state.has("Sword", self.player))
set_rule(multiworld.get_location("Slay Rhindle", self.player), set_rule(world.get_location("Slay Rhindle", self.player),
lambda state: state.has("Sword", self.player)) lambda state: state.has("Sword", self.player))
# really this requires getting the dot item, and having another item or enemy # really this requires getting the dot item, and having another item or enemy
@@ -51,37 +51,37 @@ def set_rules(self) -> None:
# to actually make randomized, since it is invisible. May add some options # to actually make randomized, since it is invisible. May add some options
# for how that works in the distant future, but for now, just say you need # for how that works in the distant future, but for now, just say you need
# the bridge and black key to get to it, as that simplifies things a lot # the bridge and black key to get to it, as that simplifies things a lot
set_rule(multiworld.get_entrance("CreditsWall", self.player), set_rule(world.get_entrance("CreditsWall", self.player),
lambda state: state.has("Bridge", self.player) and lambda state: state.has("Bridge", self.player) and
state.has("Black Key", self.player)) state.has("Black Key", self.player))
if not use_bat_logic: if not use_bat_logic:
set_rule(multiworld.get_entrance("CreditsToFarSide", self.player), set_rule(world.get_entrance("CreditsToFarSide", self.player),
lambda state: state.has("Magnet", self.player)) lambda state: state.has("Magnet", self.player))
# bridge literally does not fit in this space, I think. I'll just exclude it # bridge literally does not fit in this space, I think. I'll just exclude it
forbid_item(multiworld.get_location("Dungeon Vault", self.player), "Bridge", self.player) forbid_item(world.get_location("Dungeon Vault", self.player), "Bridge", self.player)
# don't put magnet in locations that can pull in-logic items out of reach unless the bat is in play # don't put magnet in locations that can pull in-logic items out of reach unless the bat is in play
if not use_bat_logic: if not use_bat_logic:
forbid_item(multiworld.get_location("Dungeon Vault", self.player), "Magnet", self.player) forbid_item(world.get_location("Dungeon Vault", self.player), "Magnet", self.player)
forbid_item(multiworld.get_location("Red Maze Vault Entrance", self.player), "Magnet", self.player) forbid_item(world.get_location("Red Maze Vault Entrance", self.player), "Magnet", self.player)
forbid_item(multiworld.get_location("Credits Right Side", self.player), "Magnet", self.player) forbid_item(world.get_location("Credits Right Side", self.player), "Magnet", self.player)
# and obviously we don't want to start with the game already won # and obviously we don't want to start with the game already won
forbid_item(multiworld.get_location("Inside Yellow Castle", self.player), "Chalice", self.player) forbid_item(world.get_location("Inside Yellow Castle", self.player), "Chalice", self.player)
overworld = multiworld.get_region("Overworld", self.player) overworld = world.get_region("Overworld", self.player)
for loc in overworld.locations: for loc in overworld.locations:
forbid_item(loc, "Chalice", self.player) forbid_item(loc, "Chalice", self.player)
add_rule(multiworld.get_location("Chalice Home", self.player), add_rule(world.get_location("Chalice Home", self.player),
lambda state: state.has("Chalice", self.player) and state.has("Yellow Key", self.player)) lambda state: state.has("Chalice", self.player) and state.has("Yellow Key", self.player))
# multiworld.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY # world.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY
# all_locations = multiworld.get_locations(self.player).copy() # all_locations = world.get_locations(self.player).copy()
# while priority_count < get_num_items(): # while priority_count < get_num_items():
# loc = multiworld.random.choice(all_locations) # loc = world.random.choice(all_locations)
# if loc.progress_type == LocationProgressType.DEFAULT: # if loc.progress_type == LocationProgressType.DEFAULT:
# loc.progress_type = LocationProgressType.PRIORITY # loc.progress_type = LocationProgressType.PRIORITY
# priority_count += 1 # priority_count += 1

View File

@@ -105,8 +105,8 @@ class AdventureWorld(World):
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()} location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
required_client_version: Tuple[int, int, int] = (0, 3, 9) required_client_version: Tuple[int, int, int] = (0, 3, 9)
def __init__(self, multiworld: MultiWorld, player: int): def __init__(self, world: MultiWorld, player: int):
super().__init__(multiworld, player) super().__init__(world, player)
self.rom_name: Optional[bytearray] = bytearray("", "utf8" ) self.rom_name: Optional[bytearray] = bytearray("", "utf8" )
self.dragon_rooms: [int] = [0x14, 0x19, 0x4] self.dragon_rooms: [int] = [0x14, 0x19, 0x4]
self.dragon_slay_check: Optional[int] = 0 self.dragon_slay_check: Optional[int] = 0

View File

@@ -1,2 +1,2 @@
maseya-z3pr==1.0.0rc1 maseya-z3pr>=1.0.0rc1
xxtea==3.7.0 xxtea>=3.0.0

View File

@@ -30,10 +30,7 @@
C to fire available Confetti Cannons C to fire available Confetti Cannons
Number Keys + Backspace for Math Trap\n Number Keys + Backspace for Math Trap\n
[b]Click to move also works![/b] Rebinding controls might be added in the future :)"""
Click/tap Confetti Cannon to fire it
Submit Math Trap solution in the command line at the bottom"""
<VolumeSliderView>: <VolumeSliderView>:
orientation: "horizontal" orientation: "horizontal"

View File

@@ -4,9 +4,8 @@ from argparse import Namespace
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from CommonClient import ClientCommandProcessor, CommonContext, logger, server_loop from CommonClient import CommonContext, gui_enabled, logger, server_loop
from NetUtils import ClientStatus from NetUtils import ClientStatus
from Utils import gui_enabled
from ..game.events import ConfettiFired, LocationClearedEvent, MathProblemSolved, MathProblemStarted, VictoryEvent from ..game.events import ConfettiFired, LocationClearedEvent, MathProblemSolved, MathProblemStarted, VictoryEvent
from ..game.game import Game from ..game.game import Game
@@ -42,16 +41,6 @@ class ConnectionStatus(Enum):
GAME_RUNNING = 3 GAME_RUNNING = 3
class APQuestClientCommandProcessor(ClientCommandProcessor):
ctx: "APQuestContext"
def default(self, raw: str) -> None:
if self.ctx.external_math_trap_input(raw):
return
super().default(raw)
class APQuestContext(CommonContext): class APQuestContext(CommonContext):
game = "APQuest" game = "APQuest"
items_handling = 0b111 # full remote items_handling = 0b111 # full remote
@@ -76,7 +65,6 @@ class APQuestContext(CommonContext):
delay_intro_song: bool delay_intro_song: bool
ui: APQuestManager ui: APQuestManager
command_processor = APQuestClientCommandProcessor
def __init__( def __init__(
self, server_address: str | None = None, password: str | None = None, delay_intro_song: bool = False self, server_address: str | None = None, password: str | None = None, delay_intro_song: bool = False
@@ -184,6 +172,7 @@ class APQuestContext(CommonContext):
assert self.ap_quest_game is not None assert self.ap_quest_game is not None
self.ap_quest_game.gameboard.fill_remote_location_content(remote_item_graphic_overrides) self.ap_quest_game.gameboard.fill_remote_location_content(remote_item_graphic_overrides)
self.render() self.render()
self.ui.game_view.bind_keyboard()
self.connection_status = ConnectionStatus.GAME_RUNNING self.connection_status = ConnectionStatus.GAME_RUNNING
self.ui.game_started() self.ui.game_started()
@@ -255,59 +244,6 @@ class APQuestContext(CommonContext):
self.ap_quest_game.input(input_key) self.ap_quest_game.input(input_key)
self.render() self.render()
def queue_auto_move(self, target_x: int, target_y: int) -> None:
if self.ap_quest_game is None:
return
if not self.ap_quest_game.gameboard.ready:
return
if not self.ui.game_view.focused > 1: # Must already be in focus
return
self.ap_quest_game.queue_auto_move(target_x, target_y)
self.ui.start_auto_move()
def do_auto_move_and_rerender(self) -> None:
if self.ap_quest_game is None:
return
if not self.ap_quest_game.gameboard.ready:
return
changed = self.ap_quest_game.do_auto_move()
if changed:
self.render()
def confetti_and_rerender(self) -> None:
# Used by tap mode
if self.ap_quest_game is None:
return
if not self.ap_quest_game.gameboard.ready:
return
if self.ap_quest_game.attempt_fire_confetti_cannon():
self.render()
def external_math_trap_input(self, raw: str) -> bool:
if self.ap_quest_game is None:
return False
if not self.ap_quest_game.gameboard.ready:
return False
if not self.ap_quest_game.active_math_problem:
return False
raw = raw.strip()
if not raw:
return False
if not raw.isnumeric():
return False
self.ap_quest_game.math_problem_replace([int(digit) for digit in raw])
if not self.ap_quest_game.active_math_problem:
self.ui.game_view.force_focus()
self.render()
return True
def make_gui(self) -> "type[kvui.GameManager]": def make_gui(self) -> "type[kvui.GameManager]":
self.load_kv() self.load_kv()
return APQuestManager return APQuestManager

View File

@@ -4,26 +4,29 @@ from math import sqrt
from random import choice, random from random import choice, random
from typing import Any from typing import Any
from kivy.core.window import Window from kivy.core.window import Keyboard, Window
from kivy.graphics import Color, Triangle from kivy.graphics import Color, Triangle
from kivy.graphics.instructions import Canvas from kivy.graphics.instructions import Canvas
from kivy.uix.behaviors import ButtonBehavior from kivy.input import MotionEvent
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivymd.uix.recycleview import MDRecycleView from kivymd.uix.recycleview import MDRecycleView
from CommonClient import logger from CommonClient import logger
from ..game.inputs import Input from ..game.inputs import Input
INPUT_MAP_STR = {
INPUT_MAP = {
"up": Input.UP,
"w": Input.UP, "w": Input.UP,
"down": Input.DOWN,
"s": Input.DOWN, "s": Input.DOWN,
"right": Input.RIGHT,
"d": Input.RIGHT, "d": Input.RIGHT,
"left": Input.LEFT,
"a": Input.LEFT, "a": Input.LEFT,
" ": Input.ACTION, "spacebar": Input.ACTION,
"c": Input.CONFETTI, "c": Input.CONFETTI,
"0": Input.ZERO, "0": Input.ZERO,
"1": Input.ONE, "1": Input.ONE,
@@ -35,52 +38,38 @@ INPUT_MAP_STR = {
"7": Input.SEVEN, "7": Input.SEVEN,
"8": Input.EIGHT, "8": Input.EIGHT,
"9": Input.NINE, "9": Input.NINE,
} "backspace": Input.BACKSPACE,
INPUT_MAP_SPECIAL_INT = {
# Arrow Keys and Backspace
273: Input.UP,
274: Input.DOWN,
275: Input.RIGHT,
276: Input.LEFT,
8: Input.BACKSPACE,
} }
class APQuestGameView(MDRecycleView): class APQuestGameView(MDRecycleView):
focused: int = 1 _keyboard: Keyboard | None = None
input_function: Callable[[Input], None] input_function: Callable[[Input], None]
def __init__(self, input_function: Callable[[Input], None], **kwargs: Any) -> None: def __init__(self, input_function: Callable[[Input], None], **kwargs: Any) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.input_function = input_function self.input_function = input_function
Window.bind(on_key_down=self._on_keyboard_down) self.bind_keyboard()
Window.bind(on_touch_down=self.check_focus)
self.opacity = 0.5
def check_focus(self, _, touch, *args, **kwargs) -> None: def on_touch_down(self, touch: MotionEvent) -> None:
if self.parent.collide_point(*touch.pos): self.bind_keyboard()
self.focused += 1
self.opacity = 1 def bind_keyboard(self) -> None:
if self._keyboard is not None:
return return
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
self.focused = 0 def _keyboard_closed(self) -> None:
self.opacity = 0.5 if self._keyboard is None:
return
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard = None
def force_focus(self) -> None: def _on_keyboard_down(self, _: Any, keycode: tuple[int, str], _1: Any, _2: Any) -> bool:
Window.release_keyboard() if keycode[1] in INPUT_MAP:
self.focused = 1 self.input_function(INPUT_MAP[keycode[1]])
self.opacity = 1 return True
def _on_keyboard_down(self, _: Any, keycode_int: int, _2: Any, keycode: str, _4: Any) -> bool:
if not self.focused:
return False
if keycode in INPUT_MAP_STR:
self.input_function(INPUT_MAP_STR[keycode])
elif keycode_int in INPUT_MAP_SPECIAL_INT:
self.input_function(INPUT_MAP_SPECIAL_INT[keycode_int])
return False
class APQuestGrid(GridLayout): class APQuestGrid(GridLayout):
@@ -88,7 +77,7 @@ class APQuestGrid(GridLayout):
parent_width, parent_height = self.parent.size parent_width, parent_height = self.parent.size
self_width_according_to_parent_height = parent_height * 12 / 11 self_width_according_to_parent_height = parent_height * 12 / 11
self_height_according_to_parent_width = parent_width * 11 / 12 self_height_according_to_parent_width = parent_height * 11 / 12
if self_width_according_to_parent_height > parent_width: if self_width_according_to_parent_height > parent_width:
self.size = parent_width, self_height_according_to_parent_width self.size = parent_width, self_height_according_to_parent_width
@@ -214,23 +203,13 @@ class Confetti:
return True return True
class ConfettiView(Widget): class ConfettiView(MDRecycleView):
confetti: list[Confetti] confetti: list[Confetti]
def __init__(self, **kwargs: Any) -> None: def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self.confetti = [] self.confetti = []
# Don't eat tap events for the game grid under the confetti view
def on_touch_down(self, touch) -> bool:
return False
def on_touch_move(self, touch) -> bool:
return False
def on_touch_up(self, touch) -> bool:
return False
def check_resize(self, _: int, _1: int) -> None: def check_resize(self, _: int, _1: int) -> None:
parent_width, parent_height = self.parent.size parent_width, parent_height = self.parent.size
@@ -275,32 +254,3 @@ class VolumeSliderView(BoxLayout):
class APQuestControlsView(BoxLayout): class APQuestControlsView(BoxLayout):
pass pass
class TapImage(ButtonBehavior, Image):
callback: Callable[[], None]
def __init__(self, callback: Callable[[], None], **kwargs) -> None:
self.callback = callback
super().__init__(**kwargs)
def on_release(self) -> bool:
self.callback()
return True
class TapIfConfettiCannonImage(ButtonBehavior, Image):
callback: Callable[[], None]
is_confetti_cannon: bool = False
def __init__(self, callback: Callable[[], None], **kwargs: dict[str, Any]) -> None:
self.callback = callback
super().__init__(**kwargs)
def on_release(self) -> bool:
if self.is_confetti_cannon:
self.callback()
return True

View File

@@ -6,7 +6,6 @@ from kvui import GameManager, MDNavigationItemBase
# isort: on # isort: on
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from kivy._clock import ClockEvent
from kivy.clock import Clock from kivy.clock import Clock
from kivy.uix.gridlayout import GridLayout from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image from kivy.uix.image import Image
@@ -14,16 +13,7 @@ from kivy.uix.layout import Layout
from kivymd.uix.recycleview import MDRecycleView from kivymd.uix.recycleview import MDRecycleView
from ..game.game import Game from ..game.game import Game
from ..game.graphics import Graphic from .custom_views import APQuestControlsView, APQuestGameView, APQuestGrid, ConfettiView, VolumeSliderView
from .custom_views import (
APQuestControlsView,
APQuestGameView,
APQuestGrid,
ConfettiView,
TapIfConfettiCannonImage,
TapImage,
VolumeSliderView,
)
from .graphics import PlayerSprite, get_texture from .graphics import PlayerSprite, get_texture
from .sounds import SoundManager from .sounds import SoundManager
@@ -38,17 +28,15 @@ class APQuestManager(GameManager):
lower_game_grid: GridLayout lower_game_grid: GridLayout
upper_game_grid: GridLayout upper_game_grid: GridLayout
game_view: MDRecycleView | None = None game_view: MDRecycleView
game_view_tab: MDNavigationItemBase game_view_tab: MDNavigationItemBase
sound_manager: SoundManager sound_manager: SoundManager
bottom_image_grid: list[list[Image]] bottom_image_grid: list[list[Image]]
top_image_grid: list[list[TapImage]] top_image_grid: list[list[Image]]
confetti_view: ConfettiView confetti_view: ConfettiView
move_event: ClockEvent | None
bottom_grid_is_grass: bool bottom_grid_is_grass: bool
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
@@ -57,7 +45,6 @@ class APQuestManager(GameManager):
self.sound_manager.allow_intro_to_play = not self.ctx.delay_intro_song self.sound_manager.allow_intro_to_play = not self.ctx.delay_intro_song
self.top_image_grid = [] self.top_image_grid = []
self.bottom_image_grid = [] self.bottom_image_grid = []
self.move_event = None
self.bottom_grid_is_grass = False self.bottom_grid_is_grass = False
def allow_intro_song(self) -> None: def allow_intro_song(self) -> None:
@@ -84,12 +71,10 @@ class APQuestManager(GameManager):
def game_started(self) -> None: def game_started(self) -> None:
self.switch_to_game_tab() self.switch_to_game_tab()
if self.game_view is not None:
self.game_view.force_focus()
self.sound_manager.game_started = True self.sound_manager.game_started = True
def render(self, game: Game, player_sprite: PlayerSprite) -> None: def render(self, game: Game, player_sprite: PlayerSprite) -> None:
self.setup_game_grid_if_not_setup(game) self.setup_game_grid_if_not_setup(game.gameboard.size)
# This calls game.render(), which needs to happen to update the state of math traps # This calls game.render(), which needs to happen to update the state of math traps
self.render_gameboard(game, player_sprite) self.render_gameboard(game, player_sprite)
@@ -119,8 +104,6 @@ class APQuestManager(GameManager):
for item_graphic, image_row in zip(rendered_item_column, self.top_image_grid, strict=False): for item_graphic, image_row in zip(rendered_item_column, self.top_image_grid, strict=False):
image = image_row[-1] image = image_row[-1]
image.is_confetti_cannon = item_graphic == Graphic.CONFETTI_CANNON
texture = get_texture(item_graphic) texture = get_texture(item_graphic)
if texture is None: if texture is None:
image.opacity = 0 image.opacity = 0
@@ -153,25 +136,23 @@ class APQuestManager(GameManager):
self.bottom_grid_is_grass = grass self.bottom_grid_is_grass = grass
def setup_game_grid_if_not_setup(self, game: Game) -> None: def setup_game_grid_if_not_setup(self, size: tuple[int, int]) -> None:
if self.upper_game_grid.children: if self.upper_game_grid.children:
return return
self.top_image_grid = [] self.top_image_grid = []
self.bottom_image_grid = [] self.bottom_image_grid = []
size = game.gameboard.size for _row in range(size[1]):
for row in range(size[1]):
self.top_image_grid.append([]) self.top_image_grid.append([])
self.bottom_image_grid.append([]) self.bottom_image_grid.append([])
for column in range(size[0]): for _column in range(size[0]):
bottom_image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3)) bottom_image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
self.lower_game_grid.add_widget(bottom_image) self.lower_game_grid.add_widget(bottom_image)
self.bottom_image_grid[-1].append(bottom_image) self.bottom_image_grid[-1].append(bottom_image)
top_image = TapImage(lambda y=row, x=column: self.ctx.queue_auto_move(x, y), fit_mode="fill") top_image = Image(fit_mode="fill")
self.upper_game_grid.add_widget(top_image) self.upper_game_grid.add_widget(top_image)
self.top_image_grid[-1].append(top_image) self.top_image_grid[-1].append(top_image)
@@ -179,19 +160,11 @@ class APQuestManager(GameManager):
image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3)) image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
self.lower_game_grid.add_widget(image) self.lower_game_grid.add_widget(image)
image2 = TapIfConfettiCannonImage(lambda: self.ctx.confetti_and_rerender(), fit_mode="fill", opacity=0) image2 = Image(fit_mode="fill", opacity=0)
self.upper_game_grid.add_widget(image2) self.upper_game_grid.add_widget(image2)
self.top_image_grid[-1].append(image2) self.top_image_grid[-1].append(image2)
def start_auto_move(self) -> None:
if self.move_event is not None:
self.move_event.cancel()
self.ctx.do_auto_move_and_rerender()
self.move_event = Clock.schedule_interval(lambda _: self.ctx.do_auto_move_and_rerender(), 0.10)
def build(self) -> Layout: def build(self) -> Layout:
container = super().build() container = super().build()

View File

@@ -1,10 +1,10 @@
import pkgutil import pkgutil
from collections.abc import Buffer
from enum import Enum from enum import Enum
from io import BytesIO from io import BytesIO
from typing import Literal, NamedTuple, Protocol, cast from typing import Literal, NamedTuple, Protocol, cast
from kivy.uix.image import CoreImage from kivy.uix.image import CoreImage
from typing_extensions import Buffer
from CommonClient import logger from CommonClient import logger

View File

@@ -1,12 +1,12 @@
import asyncio import asyncio
import pkgutil import pkgutil
from asyncio import Task from asyncio import Task
from collections.abc import Buffer
from pathlib import Path from pathlib import Path
from typing import cast from typing import cast
from kivy import Config from kivy import Config
from kivy.core.audio import Sound, SoundLoader from kivy.core.audio import Sound, SoundLoader
from typing_extensions import Buffer
from CommonClient import logger from CommonClient import logger
@@ -85,7 +85,7 @@ class SoundManager:
def ensure_config(self) -> None: def ensure_config(self) -> None:
Config.adddefaultsection("APQuest") Config.adddefaultsection("APQuest")
Config.setdefault("APQuest", "volume", 30) Config.setdefault("APQuest", "volume", 50)
self.set_volume_percentage(Config.getint("APQuest", "volume")) self.set_volume_percentage(Config.getint("APQuest", "volume"))
async def sound_manager_loop(self) -> None: async def sound_manager_loop(self) -> None:
@@ -149,7 +149,6 @@ class SoundManager:
continue continue
if sound_name == audio_filename: if sound_name == audio_filename:
sound.volume = self.volume_percentage / 100
sound.play() sound.play()
self.update_background_music() self.update_background_music()
higher_priority_sound_is_playing = True higher_priority_sound_is_playing = True
@@ -214,7 +213,6 @@ class SoundManager:
# It ends up feeling better if this just always continues playing quietly after being started. # It ends up feeling better if this just always continues playing quietly after being started.
# Even "fading in at a random spot" is better than restarting the song after a jingle / math trap. # Even "fading in at a random spot" is better than restarting the song after a jingle / math trap.
if self.game_started and song.state == "stop": if self.game_started and song.state == "stop":
song.volume = self.current_background_music_volume * self.volume_percentage / 100
song.play() song.play()
song.seek(0) song.seek(0)
continue continue
@@ -230,7 +228,6 @@ class SoundManager:
if self.current_background_music_volume != 0: if self.current_background_music_volume != 0:
if song.state == "stop": if song.state == "stop":
song.volume = self.current_background_music_volume * self.volume_percentage / 100
song.play() song.play()
song.seek(0) song.seek(0)

View File

@@ -6,11 +6,6 @@
- Die [APQuest-apworld](https://github.com/NewSoupVi/Archipelago/releases), - Die [APQuest-apworld](https://github.com/NewSoupVi/Archipelago/releases),
falls diese nicht mit deiner Version von Archipelago gebündelt ist. falls diese nicht mit deiner Version von Archipelago gebündelt ist.
## Optionale Software
- [APQuest AP Tracker](https://github.com/palex00/ap-quest-tracker/releases/latest), zur Verwendung mit
[PopTracker](https://github.com/black-sliver/PopTracker/releases)
## Wie man spielt ## Wie man spielt
Zuerst brauchst du einen Raum, mit dem du dich verbinden kannst. Zuerst brauchst du einen Raum, mit dem du dich verbinden kannst.
@@ -46,15 +41,3 @@ Du solltest jetzt verbunden sein und kannst APQuest spielen.
Der APQuest Client kann zwischen verschiedenen Slots wechseln, ohne neugestartet werden zu müssen, Der APQuest Client kann zwischen verschiedenen Slots wechseln, ohne neugestartet werden zu müssen,
Klicke einfach den "Disconnect"-Knopf. Dann verbinde dich mit dem anderen Raum / Slot. Klicke einfach den "Disconnect"-Knopf. Dann verbinde dich mit dem anderen Raum / Slot.
## Automatisches Tracken
AP Quest verfügt über einen voll funktionsfähigen, automatischen Tracker mit Karten der Spielwelt.
1. Lade [APQuest AP Tracker](https://github.com/palex00/ap-quest-tracker/releases/latest) und
[PopTracker](https://github.com/black-sliver/PopTracker/releases) herunter.
2. Lege das Tracker-Pack im Ordner „packs/“ deiner PopTracker-Installation ab.
3. Öffne PopTracker und lade das APQuest-Pack.
4. Für das automatische Tracking klick oben auf das „AP“-Symbol.
5. Gib die Serveradresse von Archipelago (die, mit der du deinen Client verbunden hast), den Slot-Namen und das Passwort ein.

View File

@@ -6,11 +6,6 @@
- [The APQuest apworld](https://github.com/NewSoupVi/Archipelago/releases), - [The APQuest apworld](https://github.com/NewSoupVi/Archipelago/releases),
if not bundled with your version of Archipelago if not bundled with your version of Archipelago
## Optional Software
- [APQuest AP Tracker](https://github.com/palex00/ap-quest-tracker/releases/latest), for use with
[PopTracker](https://github.com/black-sliver/PopTracker/releases)
## How to play ## How to play
First, you need a room to connect to. For this, you or someone you know has to generate a game. First, you need a room to connect to. For this, you or someone you know has to generate a game.
@@ -45,14 +40,3 @@ You should now be connected and able to play APQuest.
The APQuest Client can seamlessly switch rooms without restarting. The APQuest Client can seamlessly switch rooms without restarting.
Simply click the "Disconnect" button, then connect to a different slot/room. Simply click the "Disconnect" button, then connect to a different slot/room.
## Auto-Tracking
AP Quest has a fully functional map tracker that supports auto-tracking.
1. Download [APQuest AP Tracker](https://github.com/palex00/ap-quest-tracker/releases/latest) and
[PopTracker](https://github.com/black-sliver/PopTracker/releases).
2. Put the tracker pack into packs/ in your PopTracker install.
3. Open PopTracker, and load the APQuest pack.
4. For autotracking, click on the "AP" symbol at the top.
5. Enter the Archipelago server address (the one you connected your client to), slot name, and password.

View File

@@ -17,10 +17,8 @@ class Entity:
class InteractableMixin: class InteractableMixin:
auto_move_attempt_passing_through = False
@abstractmethod @abstractmethod
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
pass pass
@@ -91,16 +89,15 @@ class Chest(Entity, InteractableMixin, LocationMixin):
self.is_open = True self.is_open = True
self.update_solidity() self.update_solidity()
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
if self.has_given_content: if self.has_given_content:
return False return
if self.is_open: if self.is_open:
self.give_content(player) self.give_content(player)
return True return
self.open() self.open()
return True
def content_success(self) -> None: def content_success(self) -> None:
self.update_solidity() self.update_solidity()
@@ -138,59 +135,47 @@ class Door(Entity):
class KeyDoor(Door, InteractableMixin): class KeyDoor(Door, InteractableMixin):
auto_move_attempt_passing_through = True
closed_graphic = Graphic.KEY_DOOR closed_graphic = Graphic.KEY_DOOR
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
if self.is_open: if self.is_open:
return False return
if not player.has_item(Item.KEY): if not player.has_item(Item.KEY):
return False return
player.remove_item(Item.KEY) player.remove_item(Item.KEY)
self.open() self.open()
return True
class BreakableBlock(Door, InteractableMixin): class BreakableBlock(Door, InteractableMixin):
auto_move_attempt_passing_through = True
closed_graphic = Graphic.BREAKABLE_BLOCK closed_graphic = Graphic.BREAKABLE_BLOCK
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
if self.is_open: if self.is_open:
return False return
if not player.has_item(Item.HAMMER): if not player.has_item(Item.HAMMER):
return False return
player.remove_item(Item.HAMMER) player.remove_item(Item.HAMMER)
self.open() self.open()
return True
class Bush(Door, InteractableMixin): class Bush(Door, InteractableMixin):
auto_move_attempt_passing_through = True
closed_graphic = Graphic.BUSH closed_graphic = Graphic.BUSH
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
if self.is_open: if self.is_open:
return False return
if not player.has_item(Item.SWORD): if not player.has_item(Item.SWORD):
return False return
self.open() self.open()
return True
class Button(Entity, InteractableMixin): class Button(Entity, InteractableMixin):
solid = True solid = True
@@ -201,13 +186,12 @@ class Button(Entity, InteractableMixin):
def __init__(self, activates: ActivatableMixin) -> None: def __init__(self, activates: ActivatableMixin) -> None:
self.activates = activates self.activates = activates
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
if self.activated: if self.activated:
return False return
self.activated = True self.activated = True
self.activates.activate(player) self.activates.activate(player)
return True
@property @property
def graphic(self) -> Graphic: def graphic(self) -> Graphic:
@@ -256,9 +240,9 @@ class Enemy(Entity, InteractableMixin):
return return
self.current_health = self.max_health self.current_health = self.max_health
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
if self.dead: if self.dead:
return False return
if player.has_item(Item.SWORD): if player.has_item(Item.SWORD):
self.current_health = max(0, self.current_health - 1) self.current_health = max(0, self.current_health - 1)
@@ -266,10 +250,9 @@ class Enemy(Entity, InteractableMixin):
if self.current_health == 0: if self.current_health == 0:
if not self.dead: if not self.dead:
self.die() self.die()
return True return
player.damage(2) player.damage(2)
return True
@property @property
def graphic(self) -> Graphic: def graphic(self) -> Graphic:
@@ -287,15 +270,13 @@ class EnemyWithLoot(Enemy, LocationMixin):
self.dead = True self.dead = True
self.solid = not self.has_given_content self.solid = not self.has_given_content
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
if self.dead: if self.dead:
if not self.has_given_content: if not self.has_given_content:
self.give_content(player) self.give_content(player)
return True return
return False
super().interact(player) super().interact(player)
return True
@property @property
def graphic(self) -> Graphic: def graphic(self) -> Graphic:
@@ -322,12 +303,10 @@ class FinalBoss(Enemy):
} }
enemy_default_graphic = Graphic.BOSS_1_HEALTH enemy_default_graphic = Graphic.BOSS_1_HEALTH
def interact(self, player: Player) -> bool: def interact(self, player: Player) -> None:
dead_before = self.dead dead_before = self.dead
changed = super().interact(player) super().interact(player)
if not dead_before and self.dead: if not dead_before and self.dead:
player.victory() player.victory()
return changed

View File

@@ -23,8 +23,6 @@ class Game:
active_math_problem: MathProblem | None active_math_problem: MathProblem | None
active_math_problem_input: list[int] | None active_math_problem_input: list[int] | None
auto_target_path: list[tuple[int, int]] = []
remotely_received_items: set[tuple[int, int, int]] remotely_received_items: set[tuple[int, int, int]]
def __init__( def __init__(
@@ -34,7 +32,6 @@ class Game:
self.gameboard = create_gameboard(hard_mode, hammer_exists, extra_chest) self.gameboard = create_gameboard(hard_mode, hammer_exists, extra_chest)
self.player = Player(self.gameboard, self.queued_events.append) self.player = Player(self.gameboard, self.queued_events.append)
self.active_math_problem = None self.active_math_problem = None
self.active_math_problem_input = None
self.remotely_received_items = set() self.remotely_received_items = set()
if random_object is None: if random_object is None:
@@ -97,40 +94,29 @@ class Game:
return tuple(graphics_array) return tuple(graphics_array)
def attempt_player_movement(self, direction: Direction, cancel_auto_move: bool = True) -> bool: def attempt_player_movement(self, direction: Direction) -> None:
if cancel_auto_move:
self.cancel_auto_move()
self.player.facing = direction self.player.facing = direction
delta_x, delta_y = direction.value delta_x, delta_y = direction.value
new_x, new_y = self.player.current_x + delta_x, self.player.current_y + delta_y new_x, new_y = self.player.current_x + delta_x, self.player.current_y + delta_y
if self.gameboard.get_entity_at(new_x, new_y).solid: if not self.gameboard.get_entity_at(new_x, new_y).solid:
return False
self.player.current_x = new_x self.player.current_x = new_x
self.player.current_y = new_y self.player.current_y = new_y
return True
def attempt_interact(self) -> bool: def attempt_interact(self) -> None:
delta_x, delta_y = self.player.facing.value delta_x, delta_y = self.player.facing.value
entity_x, entity_y = self.player.current_x + delta_x, self.player.current_y + delta_y entity_x, entity_y = self.player.current_x + delta_x, self.player.current_y + delta_y
entity = self.gameboard.get_entity_at(entity_x, entity_y) entity = self.gameboard.get_entity_at(entity_x, entity_y)
if isinstance(entity, InteractableMixin): if isinstance(entity, InteractableMixin):
return entity.interact(self.player) entity.interact(self.player)
return False
def attempt_fire_confetti_cannon(self) -> bool:
if not self.player.has_item(Item.CONFETTI_CANNON):
return False
def attempt_fire_confetti_cannon(self) -> None:
if self.player.has_item(Item.CONFETTI_CANNON):
self.player.remove_item(Item.CONFETTI_CANNON) self.player.remove_item(Item.CONFETTI_CANNON)
self.queued_events.append(ConfettiFired(self.player.current_x, self.player.current_y)) self.queued_events.append(ConfettiFired(self.player.current_x, self.player.current_y))
return True
def math_problem_success(self) -> None: def math_problem_success(self) -> None:
self.active_math_problem = None self.active_math_problem = None
@@ -168,12 +154,6 @@ class Game:
self.active_math_problem_input.pop() self.active_math_problem_input.pop()
self.check_math_problem_result() self.check_math_problem_result()
def math_problem_replace(self, input: list[int]) -> None:
if self.active_math_problem_input is None:
return
self.active_math_problem_input = input[:2]
self.check_math_problem_result()
def input(self, input_key: Input) -> None: def input(self, input_key: Input) -> None:
if not self.gameboard.ready: if not self.gameboard.ready:
return return
@@ -221,47 +201,3 @@ class Game:
def force_clear_location(self, location_id: int) -> None: def force_clear_location(self, location_id: int) -> None:
location = Location(location_id) location = Location(location_id)
self.gameboard.force_clear_location(location) self.gameboard.force_clear_location(location)
def cancel_auto_move(self) -> None:
self.auto_target_path = []
def queue_auto_move(self, target_x: int, target_y: int) -> None:
self.cancel_auto_move()
path = self.gameboard.calculate_shortest_path(self.player.current_x, self.player.current_y, target_x, target_y)
self.auto_target_path = path
def do_auto_move(self) -> bool:
if not self.auto_target_path:
return False
target_x, target_y = self.auto_target_path.pop(0)
movement = target_x - self.player.current_x, target_y - self.player.current_y
direction = Direction(movement)
moved = self.attempt_player_movement(direction, cancel_auto_move=False)
if moved:
return True
# We are attempting to interact with something on the path.
# First, make the player face it.
if self.player.facing != direction:
self.player.facing = direction
self.auto_target_path.insert(0, (target_x, target_y))
return True
# If we are facing it, attempt to interact with it.
changed = self.attempt_interact()
if not changed:
self.cancel_auto_move()
return False
# If the interaction was successful, and this was the end of the path, stop
# (i.e. don't try to attack the attacked enemy over and over until it's dead)
if not self.auto_target_path:
self.cancel_auto_move()
return True
# If there is more to go, keep going along the path
self.auto_target_path.insert(0, (target_x, target_y))
return True

View File

@@ -15,7 +15,6 @@ from .entities import (
EnemyWithLoot, EnemyWithLoot,
Entity, Entity,
FinalBoss, FinalBoss,
InteractableMixin,
KeyDoor, KeyDoor,
LocationMixin, LocationMixin,
Wall, Wall,
@@ -24,7 +23,6 @@ from .generate_math_problem import MathProblem
from .graphics import DIGIT_TO_GRAPHIC, DIGIT_TO_GRAPHIC_ZERO_EMPTY, MATH_PROBLEM_TYPE_TO_GRAPHIC, Graphic from .graphics import DIGIT_TO_GRAPHIC, DIGIT_TO_GRAPHIC_ZERO_EMPTY, MATH_PROBLEM_TYPE_TO_GRAPHIC, Graphic
from .items import Item from .items import Item
from .locations import DEFAULT_CONTENT, Location from .locations import DEFAULT_CONTENT, Location
from .path_finding import find_path_or_closest
if TYPE_CHECKING: if TYPE_CHECKING:
from .player import Player from .player import Player
@@ -109,21 +107,6 @@ class Gameboard:
return tuple(graphics) return tuple(graphics)
def as_traversability_bools(self) -> tuple[tuple[bool, ...], ...]:
traversability = []
for y, row in enumerate(self.gameboard):
traversable_row = []
for x, entity in enumerate(row):
traversable_row.append(
not entity.solid
or (isinstance(entity, InteractableMixin) and entity.auto_move_attempt_passing_through)
)
traversability.append(tuple(traversable_row))
return tuple(traversability)
def render_math_problem( def render_math_problem(
self, problem: MathProblem, current_input_digits: list[int], current_input_int: int | None self, problem: MathProblem, current_input_digits: list[int], current_input_int: int | None
) -> tuple[tuple[Graphic, ...], ...]: ) -> tuple[tuple[Graphic, ...], ...]:
@@ -203,23 +186,6 @@ class Gameboard:
entity = self.remote_entity_by_location_id[location] entity = self.remote_entity_by_location_id[location]
entity.force_clear() entity.force_clear()
def calculate_shortest_path(
self, source_x: int, source_y: int, target_x: int, target_y: int
) -> list[tuple[int, int]]:
gameboard_traversability = self.as_traversability_bools()
path = find_path_or_closest(gameboard_traversability, source_x, source_y, target_x, target_y)
if not path:
return path
# If the path stops just short of target, attempt interacting with it at the end
if abs(path[-1][0] - target_x) + abs(path[-1][1] - target_y) == 1:
if isinstance(self.gameboard[target_y][target_x], InteractableMixin):
path.append((target_x, target_y))
return path[1:] # Cut off starting tile
@property @property
def ready(self) -> bool: def ready(self) -> bool:
return self.content_filled return self.content_filled

View File

@@ -6,7 +6,6 @@ from typing import NamedTuple
_random = random.Random() _random = random.Random()
class NumberChoiceConstraints(NamedTuple): class NumberChoiceConstraints(NamedTuple):
num_1_min: int num_1_min: int
num_1_max: int num_1_max: int

View File

@@ -1,84 +0,0 @@
import heapq
from collections.abc import Generator
Point = tuple[int, int]
def heuristic(a: Point, b: Point) -> int:
# Manhattan distance (good for 4-directional grids)
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def reconstruct_path(came_from: dict[Point, Point], current: Point) -> list[Point]:
path = [current]
while current in came_from:
current = came_from[current]
path.append(current)
path.reverse()
return path
def find_path_or_closest(
grid: tuple[tuple[bool, ...], ...], source_x: int, source_y: int, target_x: int, target_y: int
) -> list[Point]:
start = source_x, source_y
goal = target_x, target_y
rows, cols = len(grid), len(grid[0])
def in_bounds(p: Point) -> bool:
return 0 <= p[0] < rows and 0 <= p[1] < cols
def passable(p: Point) -> bool:
return grid[p[1]][p[0]]
def neighbors(p: Point) -> Generator[Point, None, None]:
x, y = p
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
np = (x + dx, y + dy)
if in_bounds(np) and passable(np):
yield np
open_heap: list[tuple[int, tuple[int, int]]] = []
heapq.heappush(open_heap, (0, start))
came_from: dict[Point, Point] = {}
g_score = {start: 0}
# Track best fallback node
best_node = start
best_dist = heuristic(start, goal)
visited = set()
while open_heap:
_, current = heapq.heappop(open_heap)
if current in visited:
continue
visited.add(current)
# Check if we reached the goal
if current == goal:
return reconstruct_path(came_from, current)
# Update "closest node" fallback
dist = heuristic(current, goal)
if dist < best_dist or (dist == best_dist and g_score[current] < g_score.get(best_node, float("inf"))):
best_node = current
best_dist = dist
for neighbor in neighbors(current):
tentative_g = g_score[current] + 1 # cost is 1 per move
if tentative_g < g_score.get(neighbor, float("inf")):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score = tentative_g + heuristic(neighbor, goal)
heapq.heappush(open_heap, (f_score, neighbor))
# Goal not reachable → return path to closest node
if best_node is not None:
return reconstruct_path(came_from, best_node)
return []

View File

@@ -0,0 +1,34 @@
from typing import Dict
from BaseClasses import Tutorial
from ..AutoWorld import WebWorld, World
class AP_SudokuWebWorld(WebWorld):
options_page = False
theme = 'partyTime'
setup_en = Tutorial(
tutorial_name='Setup Guide',
description='A guide to playing APSudoku',
language='English',
file_name='setup_en.md',
link='setup/en',
authors=['EmilyV']
)
tutorials = [setup_en]
class AP_SudokuWorld(World):
"""
Play a little Sudoku while you're in BK mode to maybe get some useful hints
"""
game = "Sudoku"
web = AP_SudokuWebWorld()
item_name_to_id: Dict[str, int] = {}
location_name_to_id: Dict[str, int] = {}
@classmethod
def stage_assert_generate(cls, multiworld):
raise Exception("APSudoku cannot be used for generating worlds, the client can instead connect to any slot from any world")

View File

@@ -0,0 +1,15 @@
# APSudoku
## Hint Games
HintGames do not need to be added at the start of a seed, and do not create a 'slot'- instead, you connect the HintGame client to a different game's slot. By playing a HintGame, you can earn hints for the connected slot.
## What is this game?
Play Sudoku puzzles of varying difficulties, earning a hint for each puzzle correctly solved. Harder puzzles are more likely to grant a hint towards a Progression item, though otherwise what hint is granted is random.
## Where is the options page?
There is no options page; this game cannot be used in your .yamls. Instead, the client can connect to any slot in a multiworld.
By using the connected room's Admin Password on the Admin Panel tab, you can configure some settings at any time to affect the entire room. This allows disabling hints entirely, as well as altering the hint odds for each difficulty.

View File

@@ -0,0 +1,55 @@
# APSudoku Setup Guide
## Required Software
- [APSudoku](https://github.com/APSudoku/APSudoku)
## General Concept
This is a HintGame client, which can connect to any multiworld slot, allowing you to play Sudoku to unlock random hints for that slot's locations.
Does not need to be added at the start of a seed, as it does not create any slots of its own, nor does it have any YAML files.
## Installation Procedures
### Windows / Linux
Go to the latest release from the [github APSudoku Releases page](https://github.com/APSudoku/APSudoku/releases/latest). Download and extract the appropriate file for your platform.
### Web
Go to the [github pages](apsudoku.github.io) or [itch.io](https://emilyv99.itch.io/apsudoku) site, and play in the browser.
## Joining a MultiWorld Game
1. Run the APSudoku executable.
2. Under `Settings` &rarr; `Connection` at the top-right:
- Enter the server address and port number
- Enter the name of the slot you wish to connect to
- Enter the room password (optional)
- Select DeathLink related settings (optional)
- Press `Connect`
4. Under the `Sudoku` tab
- Choose puzzle difficulty
- Click `Start` to generate a puzzle
5. Try to solve the Sudoku. Click `Check` when done
- A correct solution rewards you with 1 hint for a location in the world you are connected to
- An incorrect solution has no penalty, unless DeathLink is enabled (see below)
Info:
- You can set various settings under `Settings` &rarr; `Sudoku`, and can change the colors used under `Settings` &rarr; `Theme`.
- While connected, you can view the `Console` and `Hints` tabs for standard TextClient-like features
- You can also use the `Tracking` tab to view either a basic tracker or a valid [GodotAP tracker pack](https://github.com/EmilyV99/GodotAP/blob/main/tracker_packs/GET_PACKS.md)
- While connected, the number of "unhinted" locations for your slot is shown in the upper-left of the the `Sudoku` tab. (If this reads 0, no further hints can be earned for this slot, as every locations is already hinted)
- Click the various `?` buttons for information on controls/how to play
## Admin Settings
By using the connected room's Admin Password on the Admin Panel tab, you can configure some settings at any time to affect the entire room.
- You can disable APSudoku for the entire room, preventing any hints from being granted.
- You can customize the reward weights for each difficulty, making progression hints more or less likely, and/or adding a chance to get "no hint" after a solve.
## DeathLink Support
If `DeathLink` is enabled when you click `Connect`:
- Lose a life if you check an incorrect puzzle (not an _incomplete_ puzzle- if any cells are empty, you get off with a warning), or if you quit a puzzle without solving it (including disconnecting).
- Your life count is customizable (default 0). Dying with 0 lives left kills linked players AND resets your puzzle.
- On receiving a DeathLink from another player, your puzzle resets.

View File

@@ -271,7 +271,7 @@ item_table = {
ItemNames.TRIDENT: ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head ItemNames.TRIDENT: ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head
ItemNames.TURTLE_EGG: ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg ItemNames.TURTLE_EGG: ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg
ItemNames.JELLY_EGG: ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed ItemNames.JELLY_EGG: ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed
ItemNames.URCHIN_COSTUME: ItemData(698034, 1, ItemType.PROGRESSION, ItemGroup.COLLECTIBLE), # collectible_urchin_costume ItemNames.URCHIN_COSTUME: ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
ItemNames.BABY_WALKER: ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker ItemNames.BABY_WALKER: ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker
ItemNames.VEDHA_S_CURE_ALL: ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All ItemNames.VEDHA_S_CURE_ALL: ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All
ItemNames.ZUUNA_S_PEROGI: ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi ItemNames.ZUUNA_S_PEROGI: ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi
@@ -384,8 +384,8 @@ four_gods_excludes = [ItemNames.ANEMONE, ItemNames.ARNASSI_STATUE, ItemNames.BIG
ItemNames.MITHALAS_BANNER, ItemNames.MITHALAS_POT, ItemNames.MUTANT_COSTUME, ItemNames.SEED_BAG, ItemNames.MITHALAS_BANNER, ItemNames.MITHALAS_POT, ItemNames.MUTANT_COSTUME, ItemNames.SEED_BAG,
ItemNames.KING_S_SKULL, ItemNames.SONG_PLANT_SPORE, ItemNames.STONE_HEAD, ItemNames.SUN_KEY, ItemNames.KING_S_SKULL, ItemNames.SONG_PLANT_SPORE, ItemNames.STONE_HEAD, ItemNames.SUN_KEY,
ItemNames.GIRL_COSTUME, ItemNames.ODD_CONTAINER, ItemNames.TRIDENT, ItemNames.TURTLE_EGG, ItemNames.GIRL_COSTUME, ItemNames.ODD_CONTAINER, ItemNames.TRIDENT, ItemNames.TURTLE_EGG,
ItemNames.JELLY_EGG, ItemNames.BABY_WALKER, ItemNames.RAINBOW_MUSHROOM, ItemNames.JELLY_EGG, ItemNames.URCHIN_COSTUME, ItemNames.BABY_WALKER,
ItemNames.RAINBOW_MUSHROOM, ItemNames.RAINBOW_MUSHROOM, ItemNames.FISH_OIL, ItemNames.RAINBOW_MUSHROOM, ItemNames.RAINBOW_MUSHROOM, ItemNames.RAINBOW_MUSHROOM,
ItemNames.LEAF_POULTICE, ItemNames.LEAF_POULTICE, ItemNames.LEAF_POULTICE, ItemNames.LEAF_POULTICE, ItemNames.LEAF_POULTICE, ItemNames.LEAF_POULTICE,
ItemNames.LEECHING_POULTICE, ItemNames.LEECHING_POULTICE, ItemNames.ARCANE_POULTICE, ItemNames.LEECHING_POULTICE, ItemNames.LEECHING_POULTICE, ItemNames.ARCANE_POULTICE,
ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT,

View File

@@ -37,7 +37,7 @@ def _has_li(state: CollectionState, player: int) -> bool:
DAMAGING_ITEMS:Iterable[str] = [ DAMAGING_ITEMS:Iterable[str] = [
ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM, ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM,
ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA, ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA,
ItemNames.BABY_BLASTER, ItemNames.URCHIN_COSTUME ItemNames.BABY_BLASTER
] ]
def _has_damaging_item(state: CollectionState, player: int, damaging_items:Iterable[str] = DAMAGING_ITEMS) -> bool: def _has_damaging_item(state: CollectionState, player: int, damaging_items:Iterable[str] = DAMAGING_ITEMS) -> bool:

View File

@@ -76,7 +76,7 @@ class AquariaWorld(World):
item_name_groups = { item_name_groups = {
"Damage": {ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM, "Damage": {ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM,
ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA, ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA,
ItemNames.BABY_BLASTER, ItemNames.URCHIN_COSTUME}, ItemNames.BABY_BLASTER},
"Light": {ItemNames.SUN_FORM, ItemNames.BABY_DUMBO} "Light": {ItemNames.SUN_FORM, ItemNames.BABY_DUMBO}
} }
"""Grouping item make it easier to find them""" """Grouping item make it easier to find them"""

View File

@@ -116,7 +116,7 @@ def versum_hill_rave(state: CollectionState, player: int, limit: bool, glitched:
else: else:
return ( return (
graffitiL(state, player, limit, 85) graffitiL(state, player, limit, 85)
and graffitiXL(state, player, limit, 49) and graffitiXL(state, player, limit, 48)
) )
else: else:
return ( return (

View File

@@ -1,5 +0,0 @@
{
"game": "Bomb Rush Cyberfunk",
"world_version": "1.0.6",
"authors": ["TRPG"]
}

View File

@@ -16,213 +16,213 @@ from .Locations import (
) )
def create_regions(multiworld: MultiWorld, options: CCCharlesOptions, player: int) -> None: def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
menu_region = Region("Menu", player, multiworld, "Aranearum") menu_region = Region("Menu", player, world, "Aranearum")
multiworld.regions.append(menu_region) world.regions.append(menu_region)
start_camp_region = Region("Start Camp", player, multiworld) start_camp_region = Region("Start Camp", player, world)
start_camp_region.add_locations(loc_start_camp, CCCharlesLocation) start_camp_region.add_locations(loc_start_camp, CCCharlesLocation)
multiworld.regions.append(start_camp_region) world.regions.append(start_camp_region)
tony_tiddle_mission_region = Region("Tony Tiddle Mission", player, multiworld) tony_tiddle_mission_region = Region("Tony Tiddle Mission", player, world)
tony_tiddle_mission_region.add_locations(loc_tony_tiddle_mission, CCCharlesLocation) tony_tiddle_mission_region.add_locations(loc_tony_tiddle_mission, CCCharlesLocation)
multiworld.regions.append(tony_tiddle_mission_region) world.regions.append(tony_tiddle_mission_region)
barn_region = Region("Barn", player, multiworld) barn_region = Region("Barn", player, world)
barn_region.add_locations(loc_barn, CCCharlesLocation) barn_region.add_locations(loc_barn, CCCharlesLocation)
multiworld.regions.append(barn_region) world.regions.append(barn_region)
candice_mission_region = Region("Candice Mission", player, multiworld) candice_mission_region = Region("Candice Mission", player, world)
candice_mission_region.add_locations(loc_candice_mission, CCCharlesLocation) candice_mission_region.add_locations(loc_candice_mission, CCCharlesLocation)
multiworld.regions.append(candice_mission_region) world.regions.append(candice_mission_region)
tutorial_house_region = Region("Tutorial House", player, multiworld) tutorial_house_region = Region("Tutorial House", player, world)
tutorial_house_region.add_locations(loc_tutorial_house, CCCharlesLocation) tutorial_house_region.add_locations(loc_tutorial_house, CCCharlesLocation)
multiworld.regions.append(tutorial_house_region) world.regions.append(tutorial_house_region)
swamp_edges_region = Region("Swamp Edges", player, multiworld) swamp_edges_region = Region("Swamp Edges", player, world)
swamp_edges_region.add_locations(loc_swamp_edges, CCCharlesLocation) swamp_edges_region.add_locations(loc_swamp_edges, CCCharlesLocation)
multiworld.regions.append(swamp_edges_region) world.regions.append(swamp_edges_region)
swamp_mission_region = Region("Swamp Mission", player, multiworld) swamp_mission_region = Region("Swamp Mission", player, world)
swamp_mission_region.add_locations(loc_swamp_mission, CCCharlesLocation) swamp_mission_region.add_locations(loc_swamp_mission, CCCharlesLocation)
multiworld.regions.append(swamp_mission_region) world.regions.append(swamp_mission_region)
junkyard_area_region = Region("Junkyard Area", player, multiworld) junkyard_area_region = Region("Junkyard Area", player, world)
junkyard_area_region.add_locations(loc_junkyard_area, CCCharlesLocation) junkyard_area_region.add_locations(loc_junkyard_area, CCCharlesLocation)
multiworld.regions.append(junkyard_area_region) world.regions.append(junkyard_area_region)
south_house_region = Region("South House", player, multiworld) south_house_region = Region("South House", player, world)
south_house_region.add_locations(loc_south_house, CCCharlesLocation) south_house_region.add_locations(loc_south_house, CCCharlesLocation)
multiworld.regions.append(south_house_region) world.regions.append(south_house_region)
junkyard_shed_region = Region("Junkyard Shed", player, multiworld) junkyard_shed_region = Region("Junkyard Shed", player, world)
junkyard_shed_region.add_locations(loc_junkyard_shed, CCCharlesLocation) junkyard_shed_region.add_locations(loc_junkyard_shed, CCCharlesLocation)
multiworld.regions.append(junkyard_shed_region) world.regions.append(junkyard_shed_region)
military_base_region = Region("Military Base", player, multiworld) military_base_region = Region("Military Base", player, world)
military_base_region.add_locations(loc_military_base, CCCharlesLocation) military_base_region.add_locations(loc_military_base, CCCharlesLocation)
multiworld.regions.append(military_base_region) world.regions.append(military_base_region)
south_mine_outside_region = Region("South Mine Outside", player, multiworld) south_mine_outside_region = Region("South Mine Outside", player, world)
south_mine_outside_region.add_locations(loc_south_mine_outside, CCCharlesLocation) south_mine_outside_region.add_locations(loc_south_mine_outside, CCCharlesLocation)
multiworld.regions.append(south_mine_outside_region) world.regions.append(south_mine_outside_region)
south_mine_inside_region = Region("South Mine Inside", player, multiworld) south_mine_inside_region = Region("South Mine Inside", player, world)
south_mine_inside_region.add_locations(loc_south_mine_inside, CCCharlesLocation) south_mine_inside_region.add_locations(loc_south_mine_inside, CCCharlesLocation)
multiworld.regions.append(south_mine_inside_region) world.regions.append(south_mine_inside_region)
middle_station_region = Region("Middle Station", player, multiworld) middle_station_region = Region("Middle Station", player, world)
middle_station_region.add_locations(loc_middle_station, CCCharlesLocation) middle_station_region.add_locations(loc_middle_station, CCCharlesLocation)
multiworld.regions.append(middle_station_region) world.regions.append(middle_station_region)
canyon_region = Region("Canyon", player, multiworld) canyon_region = Region("Canyon", player, world)
canyon_region.add_locations(loc_canyon, CCCharlesLocation) canyon_region.add_locations(loc_canyon, CCCharlesLocation)
multiworld.regions.append(canyon_region) world.regions.append(canyon_region)
watchtower_region = Region("Watchtower", player, multiworld) watchtower_region = Region("Watchtower", player, world)
watchtower_region.add_locations(loc_watchtower, CCCharlesLocation) watchtower_region.add_locations(loc_watchtower, CCCharlesLocation)
multiworld.regions.append(watchtower_region) world.regions.append(watchtower_region)
boulder_field_region = Region("Boulder Field", player, multiworld) boulder_field_region = Region("Boulder Field", player, world)
boulder_field_region.add_locations(loc_boulder_field, CCCharlesLocation) boulder_field_region.add_locations(loc_boulder_field, CCCharlesLocation)
multiworld.regions.append(boulder_field_region) world.regions.append(boulder_field_region)
haunted_house_region = Region("Haunted House", player, multiworld) haunted_house_region = Region("Haunted House", player, world)
haunted_house_region.add_locations(loc_haunted_house, CCCharlesLocation) haunted_house_region.add_locations(loc_haunted_house, CCCharlesLocation)
multiworld.regions.append(haunted_house_region) world.regions.append(haunted_house_region)
santiago_house_region = Region("Santiago House", player, multiworld) santiago_house_region = Region("Santiago House", player, world)
santiago_house_region.add_locations(loc_santiago_house, CCCharlesLocation) santiago_house_region.add_locations(loc_santiago_house, CCCharlesLocation)
multiworld.regions.append(santiago_house_region) world.regions.append(santiago_house_region)
port_region = Region("Port", player, multiworld) port_region = Region("Port", player, world)
port_region.add_locations(loc_port, CCCharlesLocation) port_region.add_locations(loc_port, CCCharlesLocation)
multiworld.regions.append(port_region) world.regions.append(port_region)
trench_house_region = Region("Trench House", player, multiworld) trench_house_region = Region("Trench House", player, world)
trench_house_region.add_locations(loc_trench_house, CCCharlesLocation) trench_house_region.add_locations(loc_trench_house, CCCharlesLocation)
multiworld.regions.append(trench_house_region) world.regions.append(trench_house_region)
doll_woods_region = Region("Doll Woods", player, multiworld) doll_woods_region = Region("Doll Woods", player, world)
doll_woods_region.add_locations(loc_doll_woods, CCCharlesLocation) doll_woods_region.add_locations(loc_doll_woods, CCCharlesLocation)
multiworld.regions.append(doll_woods_region) world.regions.append(doll_woods_region)
lost_stairs_region = Region("Lost Stairs", player, multiworld) lost_stairs_region = Region("Lost Stairs", player, world)
lost_stairs_region.add_locations(loc_lost_stairs, CCCharlesLocation) lost_stairs_region.add_locations(loc_lost_stairs, CCCharlesLocation)
multiworld.regions.append(lost_stairs_region) world.regions.append(lost_stairs_region)
east_house_region = Region("East House", player, multiworld) east_house_region = Region("East House", player, world)
east_house_region.add_locations(loc_east_house, CCCharlesLocation) east_house_region.add_locations(loc_east_house, CCCharlesLocation)
multiworld.regions.append(east_house_region) world.regions.append(east_house_region)
rockets_testing_ground_region = Region("Rockets Testing Ground", player, multiworld) rockets_testing_ground_region = Region("Rockets Testing Ground", player, world)
rockets_testing_ground_region.add_locations(loc_rockets_testing_ground, CCCharlesLocation) rockets_testing_ground_region.add_locations(loc_rockets_testing_ground, CCCharlesLocation)
multiworld.regions.append(rockets_testing_ground_region) world.regions.append(rockets_testing_ground_region)
rockets_testing_bunker_region = Region("Rockets Testing Bunker", player, multiworld) rockets_testing_bunker_region = Region("Rockets Testing Bunker", player, world)
rockets_testing_bunker_region.add_locations(loc_rockets_testing_bunker, CCCharlesLocation) rockets_testing_bunker_region.add_locations(loc_rockets_testing_bunker, CCCharlesLocation)
multiworld.regions.append(rockets_testing_bunker_region) world.regions.append(rockets_testing_bunker_region)
workshop_region = Region("Workshop", player, multiworld) workshop_region = Region("Workshop", player, world)
workshop_region.add_locations(loc_workshop, CCCharlesLocation) workshop_region.add_locations(loc_workshop, CCCharlesLocation)
multiworld.regions.append(workshop_region) world.regions.append(workshop_region)
east_tower_region = Region("East Tower", player, multiworld) east_tower_region = Region("East Tower", player, world)
east_tower_region.add_locations(loc_east_tower, CCCharlesLocation) east_tower_region.add_locations(loc_east_tower, CCCharlesLocation)
multiworld.regions.append(east_tower_region) world.regions.append(east_tower_region)
lighthouse_region = Region("Lighthouse", player, multiworld) lighthouse_region = Region("Lighthouse", player, world)
lighthouse_region.add_locations(loc_lighthouse, CCCharlesLocation) lighthouse_region.add_locations(loc_lighthouse, CCCharlesLocation)
multiworld.regions.append(lighthouse_region) world.regions.append(lighthouse_region)
north_mine_outside_region = Region("North Mine Outside", player, multiworld) north_mine_outside_region = Region("North Mine Outside", player, world)
north_mine_outside_region.add_locations(loc_north_mine_outside, CCCharlesLocation) north_mine_outside_region.add_locations(loc_north_mine_outside, CCCharlesLocation)
multiworld.regions.append(north_mine_outside_region) world.regions.append(north_mine_outside_region)
north_mine_inside_region = Region("North Mine Inside", player, multiworld) north_mine_inside_region = Region("North Mine Inside", player, world)
north_mine_inside_region.add_locations(loc_north_mine_inside, CCCharlesLocation) north_mine_inside_region.add_locations(loc_north_mine_inside, CCCharlesLocation)
multiworld.regions.append(north_mine_inside_region) world.regions.append(north_mine_inside_region)
wood_bridge_region = Region("Wood Bridge", player, multiworld) wood_bridge_region = Region("Wood Bridge", player, world)
wood_bridge_region.add_locations(loc_wood_bridge, CCCharlesLocation) wood_bridge_region.add_locations(loc_wood_bridge, CCCharlesLocation)
multiworld.regions.append(wood_bridge_region) world.regions.append(wood_bridge_region)
museum_region = Region("Museum", player, multiworld) museum_region = Region("Museum", player, world)
museum_region.add_locations(loc_museum, CCCharlesLocation) museum_region.add_locations(loc_museum, CCCharlesLocation)
multiworld.regions.append(museum_region) world.regions.append(museum_region)
barbed_shelter_region = Region("Barbed Shelter", player, multiworld) barbed_shelter_region = Region("Barbed Shelter", player, world)
barbed_shelter_region.add_locations(loc_barbed_shelter, CCCharlesLocation) barbed_shelter_region.add_locations(loc_barbed_shelter, CCCharlesLocation)
multiworld.regions.append(barbed_shelter_region) world.regions.append(barbed_shelter_region)
west_beach_region = Region("West Beach", player, multiworld) west_beach_region = Region("West Beach", player, world)
west_beach_region.add_locations(loc_west_beach, CCCharlesLocation) west_beach_region.add_locations(loc_west_beach, CCCharlesLocation)
multiworld.regions.append(west_beach_region) world.regions.append(west_beach_region)
church_region = Region("Church", player, multiworld) church_region = Region("Church", player, world)
church_region.add_locations(loc_church, CCCharlesLocation) church_region.add_locations(loc_church, CCCharlesLocation)
multiworld.regions.append(church_region) world.regions.append(church_region)
west_cottage_region = Region("West Cottage", player, multiworld) west_cottage_region = Region("West Cottage", player, world)
west_cottage_region.add_locations(loc_west_cottage, CCCharlesLocation) west_cottage_region.add_locations(loc_west_cottage, CCCharlesLocation)
multiworld.regions.append(west_cottage_region) world.regions.append(west_cottage_region)
caravan_region = Region("Caravan", player, multiworld) caravan_region = Region("Caravan", player, world)
caravan_region.add_locations(loc_caravan, CCCharlesLocation) caravan_region.add_locations(loc_caravan, CCCharlesLocation)
multiworld.regions.append(caravan_region) world.regions.append(caravan_region)
trailer_cabin_region = Region("Trailer Cabin", player, multiworld) trailer_cabin_region = Region("Trailer Cabin", player, world)
trailer_cabin_region.add_locations(loc_trailer_cabin, CCCharlesLocation) trailer_cabin_region.add_locations(loc_trailer_cabin, CCCharlesLocation)
multiworld.regions.append(trailer_cabin_region) world.regions.append(trailer_cabin_region)
towers_region = Region("Towers", player, multiworld) towers_region = Region("Towers", player, world)
towers_region.add_locations(loc_towers, CCCharlesLocation) towers_region.add_locations(loc_towers, CCCharlesLocation)
multiworld.regions.append(towers_region) world.regions.append(towers_region)
north_beach_region = Region("North beach", player, multiworld) north_beach_region = Region("North beach", player, world)
north_beach_region.add_locations(loc_north_beach, CCCharlesLocation) north_beach_region.add_locations(loc_north_beach, CCCharlesLocation)
multiworld.regions.append(north_beach_region) world.regions.append(north_beach_region)
mine_shaft_region = Region("Mine Shaft", player, multiworld) mine_shaft_region = Region("Mine Shaft", player, world)
mine_shaft_region.add_locations(loc_mine_shaft, CCCharlesLocation) mine_shaft_region.add_locations(loc_mine_shaft, CCCharlesLocation)
multiworld.regions.append(mine_shaft_region) world.regions.append(mine_shaft_region)
mob_camp_region = Region("Mob Camp", player, multiworld) mob_camp_region = Region("Mob Camp", player, world)
mob_camp_region.add_locations(loc_mob_camp, CCCharlesLocation) mob_camp_region.add_locations(loc_mob_camp, CCCharlesLocation)
multiworld.regions.append(mob_camp_region) world.regions.append(mob_camp_region)
mob_camp_locked_room_region = Region("Mob Camp Locked Room", player, multiworld) mob_camp_locked_room_region = Region("Mob Camp Locked Room", player, world)
mob_camp_locked_room_region.add_locations(loc_mob_camp_locked_room, CCCharlesLocation) mob_camp_locked_room_region.add_locations(loc_mob_camp_locked_room, CCCharlesLocation)
multiworld.regions.append(mob_camp_locked_room_region) world.regions.append(mob_camp_locked_room_region)
mine_elevator_exit_region = Region("Mine Elevator Exit", player, multiworld) mine_elevator_exit_region = Region("Mine Elevator Exit", player, world)
mine_elevator_exit_region.add_locations(loc_mine_elevator_exit, CCCharlesLocation) mine_elevator_exit_region.add_locations(loc_mine_elevator_exit, CCCharlesLocation)
multiworld.regions.append(mine_elevator_exit_region) world.regions.append(mine_elevator_exit_region)
mountain_ruin_outside_region = Region("Mountain Ruin Outside", player, multiworld) mountain_ruin_outside_region = Region("Mountain Ruin Outside", player, world)
mountain_ruin_outside_region.add_locations(loc_mountain_ruin_outside, CCCharlesLocation) mountain_ruin_outside_region.add_locations(loc_mountain_ruin_outside, CCCharlesLocation)
multiworld.regions.append(mountain_ruin_outside_region) world.regions.append(mountain_ruin_outside_region)
mountain_ruin_inside_region = Region("Mountain Ruin Inside", player, multiworld) mountain_ruin_inside_region = Region("Mountain Ruin Inside", player, world)
mountain_ruin_inside_region.add_locations(loc_mountain_ruin_inside, CCCharlesLocation) mountain_ruin_inside_region.add_locations(loc_mountain_ruin_inside, CCCharlesLocation)
multiworld.regions.append(mountain_ruin_inside_region) world.regions.append(mountain_ruin_inside_region)
prism_temple_region = Region("Prism Temple", player, multiworld) prism_temple_region = Region("Prism Temple", player, world)
prism_temple_region.add_locations(loc_prism_temple, CCCharlesLocation) prism_temple_region.add_locations(loc_prism_temple, CCCharlesLocation)
multiworld.regions.append(prism_temple_region) world.regions.append(prism_temple_region)
pickle_val_region = Region("Pickle Val", player, multiworld) pickle_val_region = Region("Pickle Val", player, world)
pickle_val_region.add_locations(loc_pickle_val, CCCharlesLocation) pickle_val_region.add_locations(loc_pickle_val, CCCharlesLocation)
multiworld.regions.append(pickle_val_region) world.regions.append(pickle_val_region)
shrine_near_temple_region = Region("Shrine Near Temple", player, multiworld) shrine_near_temple_region = Region("Shrine Near Temple", player, world)
shrine_near_temple_region.add_locations(loc_shrine_near_temple, CCCharlesLocation) shrine_near_temple_region.add_locations(loc_shrine_near_temple, CCCharlesLocation)
multiworld.regions.append(shrine_near_temple_region) world.regions.append(shrine_near_temple_region)
morse_bunker_region = Region("Morse Bunker", player, multiworld) morse_bunker_region = Region("Morse Bunker", player, world)
morse_bunker_region.add_locations(loc_morse_bunker, CCCharlesLocation) morse_bunker_region.add_locations(loc_morse_bunker, CCCharlesLocation)
multiworld.regions.append(morse_bunker_region) world.regions.append(morse_bunker_region)
# Place "Victory" event at "Final Boss" location # Place "Victory" event at "Final Boss" location
loc_final_boss = CCCharlesLocation(player, "Final Boss", None, prism_temple_region) loc_final_boss = CCCharlesLocation(player, "Final Boss", None, prism_temple_region)

View File

@@ -4,212 +4,212 @@ from .Options import CCCharlesOptions
# Go mode: Green Egg + Blue Egg + Red Egg + Temple Key + Bug Spray (+ Remote Explosive x8 but the base game ignores it) # Go mode: Green Egg + Blue Egg + Red Egg + Temple Key + Bug Spray (+ Remote Explosive x8 but the base game ignores it)
def set_rules(multiworld: MultiWorld, options: CCCharlesOptions, player: int) -> None: def set_rules(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
# Tony Tiddle # Tony Tiddle
set_rule(multiworld.get_entrance("Barn Door", player), set_rule(world.get_entrance("Barn Door", player),
lambda state: state.has("Barn Key", player)) lambda state: state.has("Barn Key", player))
# Candice # Candice
set_rule(multiworld.get_entrance("Tutorial House Door", player), set_rule(world.get_entrance("Tutorial House Door", player),
lambda state: state.has("Candice's Key", player)) lambda state: state.has("Candice's Key", player))
# Lizbeth Murkwater # Lizbeth Murkwater
set_rule(multiworld.get_location("Swamp Lizbeth Murkwater Mission End", player), set_rule(world.get_location("Swamp Lizbeth Murkwater Mission End", player),
lambda state: state.has("Dead Fish", player)) lambda state: state.has("Dead Fish", player))
# Daryl # Daryl
set_rule(multiworld.get_location("Junkyard Area Chest Ancient Tablet", player), set_rule(world.get_location("Junkyard Area Chest Ancient Tablet", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Junkyard Area Daryl Mission End", player), set_rule(world.get_location("Junkyard Area Daryl Mission End", player),
lambda state: state.has("Ancient Tablet", player)) lambda state: state.has("Ancient Tablet", player))
# South House # South House
set_rule(multiworld.get_location("South House Chest Scraps 1", player), set_rule(world.get_location("South House Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("South House Chest Scraps 2", player), set_rule(world.get_location("South House Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("South House Chest Scraps 3", player), set_rule(world.get_location("South House Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("South House Chest Scraps 4", player), set_rule(world.get_location("South House Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("South House Chest Scraps 5", player), set_rule(world.get_location("South House Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("South House Chest Scraps 6", player), set_rule(world.get_location("South House Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# South Mine # South Mine
set_rule(multiworld.get_entrance("South Mine Gate", player), set_rule(world.get_entrance("South Mine Gate", player),
lambda state: state.has("South Mine Key", player)) lambda state: state.has("South Mine Key", player))
set_rule(multiworld.get_location("South Mine Inside Green Paint Can", player), set_rule(world.get_location("South Mine Inside Green Paint Can", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Theodore # Theodore
set_rule(multiworld.get_location("Middle Station Theodore Mission End", player), set_rule(world.get_location("Middle Station Theodore Mission End", player),
lambda state: state.has("Blue Box", player)) lambda state: state.has("Blue Box", player))
# Watchtower # Watchtower
set_rule(multiworld.get_location("Watchtower Pink Paint Can", player), set_rule(world.get_location("Watchtower Pink Paint Can", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Sasha # Sasha
set_rule(multiworld.get_location("Haunted House Sasha Mission End", player), set_rule(world.get_location("Haunted House Sasha Mission End", player),
lambda state: state.has("Page Drawing", player, 8)) lambda state: state.has("Page Drawing", player, 8))
# Santiago # Santiago
set_rule(multiworld.get_location("Port Santiago Mission End", player), set_rule(world.get_location("Port Santiago Mission End", player),
lambda state: state.has("Journal", player)) lambda state: state.has("Journal", player))
# Trench House # Trench House
set_rule(multiworld.get_location("Trench House Chest Scraps 1", player), set_rule(world.get_location("Trench House Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Trench House Chest Scraps 2", player), set_rule(world.get_location("Trench House Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Trench House Chest Scraps 3", player), set_rule(world.get_location("Trench House Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Trench House Chest Scraps 4", player), set_rule(world.get_location("Trench House Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Trench House Chest Scraps 5", player), set_rule(world.get_location("Trench House Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Trench House Chest Scraps 6", player), set_rule(world.get_location("Trench House Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# East House # East House
set_rule(multiworld.get_location("East House Chest Scraps 1", player), set_rule(world.get_location("East House Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("East House Chest Scraps 2", player), set_rule(world.get_location("East House Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("East House Chest Scraps 3", player), set_rule(world.get_location("East House Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("East House Chest Scraps 4", player), set_rule(world.get_location("East House Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("East House Chest Scraps 5", player), set_rule(world.get_location("East House Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Rocket Testing Bunker # Rocket Testing Bunker
set_rule(multiworld.get_entrance("Stuck Bunker Door", player), set_rule(world.get_entrance("Stuck Bunker Door", player),
lambda state: state.has("Timed Dynamite", player)) lambda state: state.has("Timed Dynamite", player))
# John Smith # John Smith
set_rule(multiworld.get_location("Workshop John Smith Mission End", player), set_rule(world.get_location("Workshop John Smith Mission End", player),
lambda state: state.has("Box of Rockets", player)) lambda state: state.has("Box of Rockets", player))
# Claire # Claire
set_rule(multiworld.get_location("Lighthouse Claire Mission End", player), set_rule(world.get_location("Lighthouse Claire Mission End", player),
lambda state: state.has("Breaker", player, 4)) lambda state: state.has("Breaker", player, 4))
# North Mine # North Mine
set_rule(multiworld.get_entrance("North Mine Gate", player), set_rule(world.get_entrance("North Mine Gate", player),
lambda state: state.has("North Mine Key", player)) lambda state: state.has("North Mine Key", player))
set_rule(multiworld.get_location("North Mine Inside Blue Paint Can", player), set_rule(world.get_location("North Mine Inside Blue Paint Can", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Paul # Paul
set_rule(multiworld.get_location("Museum Paul Mission End", player), set_rule(world.get_location("Museum Paul Mission End", player),
lambda state: state.has("Remote Explosive x8", player)) lambda state: state.has("Remote Explosive x8", player))
# lambda state: state.has("Remote Explosive", player, 8)) # TODO: Add an option to split remote explosives # lambda state: state.has("Remote Explosive", player, 8)) # TODO: Add an option to split remote explosives
# West Beach # West Beach
set_rule(multiworld.get_location("West Beach Chest Scraps 1", player), set_rule(world.get_location("West Beach Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("West Beach Chest Scraps 2", player), set_rule(world.get_location("West Beach Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("West Beach Chest Scraps 3", player), set_rule(world.get_location("West Beach Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("West Beach Chest Scraps 4", player), set_rule(world.get_location("West Beach Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("West Beach Chest Scraps 5", player), set_rule(world.get_location("West Beach Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("West Beach Chest Scraps 6", player), set_rule(world.get_location("West Beach Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Caravan # Caravan
set_rule(multiworld.get_location("Caravan Chest Scraps 1", player), set_rule(world.get_location("Caravan Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Caravan Chest Scraps 2", player), set_rule(world.get_location("Caravan Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Caravan Chest Scraps 3", player), set_rule(world.get_location("Caravan Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Caravan Chest Scraps 4", player), set_rule(world.get_location("Caravan Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Caravan Chest Scraps 5", player), set_rule(world.get_location("Caravan Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Ronny # Ronny
set_rule(multiworld.get_location("Towers Ronny Mission End", player), set_rule(world.get_location("Towers Ronny Mission End", player),
lambda state: state.has("Employment Contracts", player)) lambda state: state.has("Employment Contracts", player))
# North Beach # North Beach
set_rule(multiworld.get_location("North Beach Chest Scraps 1", player), set_rule(world.get_location("North Beach Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("North Beach Chest Scraps 2", player), set_rule(world.get_location("North Beach Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("North Beach Chest Scraps 3", player), set_rule(world.get_location("North Beach Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("North Beach Chest Scraps 4", player), set_rule(world.get_location("North Beach Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Mine Shaft # Mine Shaft
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 1", player), set_rule(world.get_location("Mine Shaft Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 2", player), set_rule(world.get_location("Mine Shaft Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 3", player), set_rule(world.get_location("Mine Shaft Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 4", player), set_rule(world.get_location("Mine Shaft Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 5", player), set_rule(world.get_location("Mine Shaft Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 6", player), set_rule(world.get_location("Mine Shaft Chest Scraps 6", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 7", player), set_rule(world.get_location("Mine Shaft Chest Scraps 7", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Mob Camp # Mob Camp
set_rule(multiworld.get_entrance("Mob Camp Locked Door", player), set_rule(world.get_entrance("Mob Camp Locked Door", player),
lambda state: state.has("Mob Camp Key", player)) lambda state: state.has("Mob Camp Key", player))
set_rule(multiworld.get_location("Mob Camp Locked Room Stolen Bob", player), set_rule(world.get_location("Mob Camp Locked Room Stolen Bob", player),
lambda state: state.has("Broken Bob", player)) lambda state: state.has("Broken Bob", player))
# Mountain Ruin # Mountain Ruin
set_rule(multiworld.get_entrance("Mountain Ruin Gate", player), set_rule(world.get_entrance("Mountain Ruin Gate", player),
lambda state: state.has("Mountain Ruin Key", player)) lambda state: state.has("Mountain Ruin Key", player))
set_rule(multiworld.get_location("Mountain Ruin Inside Red Paint Can", player), set_rule(world.get_location("Mountain Ruin Inside Red Paint Can", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Prism Temple # Prism Temple
set_rule(multiworld.get_location("Prism Temple Chest Scraps 1", player), set_rule(world.get_location("Prism Temple Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Prism Temple Chest Scraps 2", player), set_rule(world.get_location("Prism Temple Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Prism Temple Chest Scraps 3", player), set_rule(world.get_location("Prism Temple Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Pickle Lady # Pickle Lady
set_rule(multiworld.get_location("Pickle Val Jar of Pickles", player), set_rule(world.get_location("Pickle Val Jar of Pickles", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Pickle Val Pickle Lady Mission End", player), set_rule(world.get_location("Pickle Val Pickle Lady Mission End", player),
lambda state: state.has("Jar of Pickles", player)) lambda state: state.has("Jar of Pickles", player))
# Morse Bunker # Morse Bunker
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 1", player), set_rule(world.get_location("Morse Bunker Chest Scraps 1", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 2", player), set_rule(world.get_location("Morse Bunker Chest Scraps 2", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 3", player), set_rule(world.get_location("Morse Bunker Chest Scraps 3", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 4", player), set_rule(world.get_location("Morse Bunker Chest Scraps 4", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 5", player), set_rule(world.get_location("Morse Bunker Chest Scraps 5", player),
lambda state: state.has("Lockpicks", player)) lambda state: state.has("Lockpicks", player))
# Add rules to reach the "Go mode" # Add rules to reach the "Go mode"
set_rule(multiworld.get_location("Final Boss", player), set_rule(world.get_location("Final Boss", player),
lambda state: state.has("Temple Key", player) lambda state: state.has("Temple Key", player)
and state.has("Green Egg", player) and state.has("Green Egg", player)
and state.has("Blue Egg", player) and state.has("Blue Egg", player)
and state.has("Red Egg", player)) and state.has("Red Egg", player))
multiworld.completion_condition[player] = lambda state: state.has("Victory", player) world.completion_condition[player] = lambda state: state.has("Victory", player)

47
worlds/dkc3/CHANGELOG.md Normal file
View File

@@ -0,0 +1,47 @@
# Donkey Kong Country 3 - Changelog
## v1.1
### Features:
- KONGsanity option (Collect all KONG letters in each level for a check)
- Autosave option
- Difficulty option
- MERRY option
- Handle collected/co-op locations
### Bug Fixes:
- Fixed Mekanos softlock
- Prevent Brothers Bear giving extra Banana Birds
- Fixed Banana Bird Mother check sending prematurely
- Fix Logic bug with Krematoa level costs
## v1.0
### Features:
- Goal
- Knautilus
- Scuttle the Knautilus in Krematoa and defeat Baron K. Roolenstein to win
- Banana Bird Hunt
- Find the Banana Birds and rescue their mother to win
- Locations included:
- Level Flags
- Bonuses
- DK Coins
- Banana Bird Caves
- Items included:
- Progressive Boat Upgrade
- Three are placed into the item pool (Patch -> First Ski -> Second Ski)
- Bonus Coins
- DK Coins
- Krematoa Cogs
- Bear Coins
- 1-Up Balloons
- Level Shuffle is supported
- Music Shuffle is supported
- Kong Palette options are supported
- Starting life count can be set

229
worlds/dkc3/Client.py Normal file
View File

@@ -0,0 +1,229 @@
import logging
from NetUtils import ClientStatus, color
from worlds.AutoSNIClient import SNIClient
snes_logger = logging.getLogger("SNES")
# FXPAK Pro protocol memory mapping used by SNI
ROM_START = 0x000000
WRAM_START = 0xF50000
WRAM_SIZE = 0x20000
SRAM_START = 0xE00000
DKC3_ROMNAME_START = 0x00FFC0
DKC3_ROMHASH_START = 0x7FC0
ROMNAME_SIZE = 0x15
ROMHASH_SIZE = 0x15
DKC3_RECV_PROGRESS_ADDR = WRAM_START + 0x632
DKC3_FILE_NAME_ADDR = WRAM_START + 0x5D9
DEATH_LINK_ACTIVE_ADDR = DKC3_ROMNAME_START + 0x15 # DKC3_TODO: Find a permanent home for this
class DKC3SNIClient(SNIClient):
game = "Donkey Kong Country 3"
patch_suffix = ".apdkc3"
async def deathlink_kill_player(self, ctx):
pass
# DKC3_TODO: Handle Receiving Deathlink
async def validate_rom(self, ctx):
from SNIClient import snes_read
rom_name = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE)
if rom_name is None or rom_name == bytes([0] * ROMHASH_SIZE) or rom_name[:2] != b"D3":
return False
ctx.game = self.game
ctx.items_handling = 0b111 # remote items
ctx.rom = rom_name
#death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1)
## DKC3_TODO: Handle Deathlink
#if death_link:
# ctx.allow_collect = bool(death_link[0] & 0b100)
# await ctx.update_death_link(bool(death_link[0] & 0b1))
return True
async def game_watcher(self, ctx):
from SNIClient import snes_buffered_write, snes_flush_writes, snes_read
# DKC3_TODO: Handle Deathlink
save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5)
if save_file_name is None or save_file_name[0] == 0x00 or save_file_name == bytes([0x55] * 0x05):
# We haven't loaded a save file
return
new_checks = []
from .Rom import location_rom_data, item_rom_data, boss_location_ids, level_unlock_map
location_ram_data = await snes_read(ctx, WRAM_START + 0x5FE, 0x81)
for loc_id, loc_data in location_rom_data.items():
if loc_id not in ctx.locations_checked:
data = location_ram_data[loc_data[0] - 0x5FE]
masked_data = data & (1 << loc_data[1])
bit_set = (masked_data != 0)
invert_bit = ((len(loc_data) >= 3) and loc_data[2])
if bit_set != invert_bit:
# DKC3_TODO: Handle non-included checks
new_checks.append(loc_id)
verify_save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5)
if verify_save_file_name is None or verify_save_file_name[0] == 0x00 or verify_save_file_name == bytes([0x55] * 0x05) or verify_save_file_name != save_file_name:
# We have somehow exited the save file (or worse)
ctx.rom = None
return
rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE)
if rom != ctx.rom:
ctx.rom = None
# We have somehow loaded a different ROM
return
for new_check_id in new_checks:
ctx.locations_checked.add(new_check_id)
location = ctx.location_names.lookup_in_game(new_check_id)
snes_logger.info(
f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})')
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}])
# DKC3_TODO: Make this actually visually display new things received (ASM Hook required)
recv_count = await snes_read(ctx, DKC3_RECV_PROGRESS_ADDR, 1)
recv_index = recv_count[0]
if recv_index < len(ctx.items_received):
item = ctx.items_received[recv_index]
recv_index += 1
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
color(ctx.item_names.lookup_in_game(item.item), 'red', 'bold'),
color(ctx.player_names[item.player], 'yellow'),
ctx.location_names.lookup_in_slot(item.location, item.player), recv_index, len(ctx.items_received)))
snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index]))
if item.item in item_rom_data:
item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1)
new_item_count = item_count[0] + 1
for address in item_rom_data[item.item]:
snes_buffered_write(ctx, WRAM_START + address, bytes([new_item_count]))
# Handle Coin Displays
current_level = await snes_read(ctx, WRAM_START + 0x5E3, 0x5)
overworld_locked = ((await snes_read(ctx, WRAM_START + 0x5FC, 0x1))[0] == 0x01)
if item.item == 0xDC3002 and not overworld_locked and (current_level[0] == 0x0A and current_level[2] == 0x00 and current_level[4] == 0x03):
# Bazaar and Barter
item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1)
new_item_count = item_count[0] + 1
snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count]))
elif item.item == 0xDC3002 and not overworld_locked and current_level[0] == 0x04:
# Swanky
item_count = await snes_read(ctx, WRAM_START + 0xA26, 0x1)
new_item_count = item_count[0] + 1
snes_buffered_write(ctx, WRAM_START + 0xA26, bytes([new_item_count]))
elif item.item == 0xDC3003 and not overworld_locked and (current_level[0] == 0x0A and current_level[2] == 0x08 and current_level[4] == 0x01):
# Boomer
item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1)
new_item_count = item_count[0] + 1
snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count]))
else:
# Handle Patch and Skis
if item.item == 0xDC3007:
num_upgrades = 1
inventory = await snes_read(ctx, WRAM_START + 0x605, 0xF)
if (inventory[0] & 0x02):
num_upgrades = 3
elif (inventory[13] & 0x08) or (inventory[0] & 0x01):
num_upgrades = 2
if num_upgrades == 1:
snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x01]))
if inventory[4] == 0:
snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x01]))
elif inventory[6] == 0:
snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x01]))
elif inventory[8] == 0:
snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x01]))
elif inventory[10] == 0:
snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x01]))
cove_mekanos_progress = await snes_read(ctx, WRAM_START + 0x691, 0x2)
snes_buffered_write(ctx, WRAM_START + 0x691, bytes([cove_mekanos_progress[0] | 0x01]))
snes_buffered_write(ctx, WRAM_START + 0x692, bytes([cove_mekanos_progress[1] | 0x01]))
elif num_upgrades == 2:
snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x02]))
if inventory[4] == 0:
snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x02]))
elif inventory[6] == 0:
snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x02]))
elif inventory[8] == 0:
snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x02]))
elif inventory[10] == 0:
snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x02]))
elif num_upgrades == 3:
snes_buffered_write(ctx, WRAM_START + 0x606, bytes([inventory[1] | 0x20]))
k3_ridge_progress = await snes_read(ctx, WRAM_START + 0x693, 0x2)
snes_buffered_write(ctx, WRAM_START + 0x693, bytes([k3_ridge_progress[0] | 0x01]))
snes_buffered_write(ctx, WRAM_START + 0x694, bytes([k3_ridge_progress[1] | 0x01]))
elif item.item == 0xDC3000:
# Handle Victory
if not ctx.finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
else:
print("Item Not Recognized: ", item.item)
pass
await snes_flush_writes(ctx)
# Handle Collected Locations
levels_to_tiles = await snes_read(ctx, ROM_START + 0x3FF800, 0x60)
tiles_to_levels = await snes_read(ctx, ROM_START + 0x3FF860, 0x60)
for loc_id in ctx.checked_locations:
if loc_id not in ctx.locations_checked and loc_id not in boss_location_ids:
loc_data = location_rom_data[loc_id]
data = await snes_read(ctx, WRAM_START + loc_data[0], 1)
invert_bit = ((len(loc_data) >= 3) and loc_data[2])
if not invert_bit:
masked_data = data[0] | (1 << loc_data[1])
snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
if (loc_data[1] == 1):
# Make the next levels accessible
level_id = loc_data[0] - 0x632
tile_id = levels_to_tiles[level_id] if levels_to_tiles[level_id] != 0xFF else level_id
tile_id = tile_id + 0x632
if tile_id in level_unlock_map:
for next_level_address in level_unlock_map[tile_id]:
next_level_id = next_level_address - 0x632
next_tile_id = tiles_to_levels[next_level_id] if tiles_to_levels[next_level_id] != 0xFF else next_level_id
next_tile_id = next_tile_id + 0x632
next_data = await snes_read(ctx, WRAM_START + next_tile_id, 1)
snes_buffered_write(ctx, WRAM_START + next_tile_id, bytes([next_data[0] | 0x01]))
await snes_flush_writes(ctx)
else:
masked_data = data[0] & ~(1 << loc_data[1])
snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data]))
await snes_flush_writes(ctx)
ctx.locations_checked.add(loc_id)
# Calculate Boomer Cost Text
boomer_cost_text = await snes_read(ctx, WRAM_START + 0xAAFD, 2)
if boomer_cost_text[0] == 0x31 and boomer_cost_text[1] == 0x35:
boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1)
boomer_cost_tens = int(boomer_cost[0]) // 10
boomer_cost_ones = int(boomer_cost[0]) % 10
snes_buffered_write(ctx, WRAM_START + 0xAAFD, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones]))
await snes_flush_writes(ctx)
boomer_final_cost_text = await snes_read(ctx, WRAM_START + 0xAB9B, 2)
if boomer_final_cost_text[0] == 0x32 and boomer_final_cost_text[1] == 0x35:
boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1)
boomer_cost_tens = boomer_cost[0] // 10
boomer_cost_ones = boomer_cost[0] % 10
snes_buffered_write(ctx, WRAM_START + 0xAB9B, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones]))
await snes_flush_writes(ctx)

52
worlds/dkc3/Items.py Normal file
View File

@@ -0,0 +1,52 @@
import typing
from BaseClasses import Item
from .Names import ItemName
class ItemData(typing.NamedTuple):
code: typing.Optional[int]
progression: bool
quantity: int = 1
event: bool = False
class DKC3Item(Item):
game: str = "Donkey Kong Country 3"
# Separate tables for each type of item.
junk_table = {
ItemName.one_up_balloon: ItemData(0xDC3001, False),
ItemName.bear_coin: ItemData(0xDC3002, False),
}
collectable_table = {
ItemName.bonus_coin: ItemData(0xDC3003, True),
ItemName.dk_coin: ItemData(0xDC3004, True),
ItemName.banana_bird: ItemData(0xDC3005, True),
ItemName.krematoa_cog: ItemData(0xDC3006, True),
ItemName.progressive_boat: ItemData(0xDC3007, True),
}
inventory_table = {
ItemName.present: ItemData(0xDC3008, True),
ItemName.bowling_ball: ItemData(0xDC3009, True),
ItemName.shell: ItemData(0xDC300A, True),
ItemName.mirror: ItemData(0xDC300B, True),
ItemName.flower: ItemData(0xDC300C, True),
ItemName.wrench: ItemData(0xDC300D, True),
}
event_table = {
ItemName.victory: ItemData(0xDC3000, True),
}
# Complete item table.
item_table = {
**junk_table,
**collectable_table,
**event_table,
}
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}

27
worlds/dkc3/LICENSE Normal file
View File

@@ -0,0 +1,27 @@
Modified MIT License
Copyright (c) 2025 PoryGone
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, and/or distribute copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to
the following conditions:
No copy or substantial portion of the Software shall be sublicensed or relicensed
without the express written permission of the copyright holder(s)
No copy or substantial portion of the Software shall be sold without the express
written permission of the copyright holder(s)
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

115
worlds/dkc3/Levels.py Normal file
View File

@@ -0,0 +1,115 @@
from .Names import LocationName
class DKC3Level():
nameIDAddress: int
levelIDAddress: int
nameID: int
levelID: int
def __init__(self, nameIDAddress: int, levelIDAddress: int, nameID: int, levelID: int):
self.nameIDAddress = nameIDAddress
self.levelIDAddress = levelIDAddress
self.nameID = nameID
self.levelID = levelID
level_dict = {
LocationName.lakeside_limbo_region: DKC3Level(0x34D19C, 0x34D19D, 0x01, 0x25),
LocationName.doorstop_dash_region: DKC3Level(0x34D1A7, 0x34D1A8, 0x02, 0x28),
LocationName.tidal_trouble_region: DKC3Level(0x34D1BD, 0x34D1BE, 0x04, 0x27),
LocationName.skiddas_row_region: DKC3Level(0x34D1C8, 0x34D1C9, 0x05, 0x2B),
LocationName.murky_mill_region: DKC3Level(0x34D1D3, 0x34D1D4, 0x0D, 0x2A),
LocationName.barrel_shield_bust_up_region: DKC3Level(0x34D217, 0x34D218, 0x0B, 0x30),
LocationName.riverside_race_region: DKC3Level(0x34D22D, 0x34D22E, 0x0C, 0x32),
LocationName.squeals_on_wheels_region: DKC3Level(0x34D238, 0x34D239, 0x06, 0x29),
LocationName.springin_spiders_region: DKC3Level(0x34D24E, 0x34D24F, 0x0E, 0x2F),
LocationName.bobbing_barrel_brawl_region: DKC3Level(0x34D264, 0x34D265, 0x37, 0x34),
LocationName.bazzas_blockade_region: DKC3Level(0x34D29D, 0x34D29E, 0x14, 0x35),
LocationName.rocket_barrel_ride_region: DKC3Level(0x34D2A8, 0x34D2A9, 0x15, 0x38),
LocationName.kreeping_klasps_region: DKC3Level(0x34D2BE, 0x34D2BF, 0x16, 0x26),
LocationName.tracker_barrel_trek_region: DKC3Level(0x34D2D4, 0x34D2D5, 0x17, 0x39),
LocationName.fish_food_frenzy_region: DKC3Level(0x34D2DF, 0x34D2E0, 0x18, 0x36),
LocationName.fire_ball_frenzy_region: DKC3Level(0x34D30D, 0x34D30E, 0x1B, 0x3B),
LocationName.demolition_drain_pipe_region: DKC3Level(0x34D323, 0x34D324, 0x1D, 0x40),
LocationName.ripsaw_rage_region: DKC3Level(0x34D339, 0x34D33A, 0x1E, 0x2E),
LocationName.blazing_bazookas_region: DKC3Level(0x34D34F, 0x34D350, 0x1F, 0x3C),
LocationName.low_g_labyrinth_region: DKC3Level(0x34D35A, 0x34D35B, 0x20, 0x3E),
LocationName.krevice_kreepers_region: DKC3Level(0x34D388, 0x34D389, 0x23, 0x41),
LocationName.tearaway_toboggan_region: DKC3Level(0x34D393, 0x34D394, 0x24, 0x2D),
LocationName.barrel_drop_bounce_region: DKC3Level(0x34D39E, 0x34D39F, 0x25, 0x3A),
LocationName.krack_shot_kroc_region: DKC3Level(0x34D3A9, 0x34D3AA, 0x26, 0x3D),
LocationName.lemguin_lunge_region: DKC3Level(0x34D3B4, 0x34D3B5, 0x27, 0x2C),
LocationName.buzzer_barrage_region: DKC3Level(0x34D40E, 0x34D40F, 0x2B, 0x44),
LocationName.kong_fused_cliffs_region: DKC3Level(0x34D424, 0x34D425, 0x2D, 0x42),
LocationName.floodlit_fish_region: DKC3Level(0x34D42F, 0x34D430, 0x2E, 0x37),
LocationName.pothole_panic_region: DKC3Level(0x34D43A, 0x34D43B, 0x2F, 0x45),
LocationName.ropey_rumpus_region: DKC3Level(0x34D450, 0x34D451, 0x30, 0x43),
LocationName.konveyor_rope_clash_region: DKC3Level(0x34D489, 0x34D48A, 0x38, 0x48),
LocationName.creepy_caverns_region: DKC3Level(0x34D49F, 0x34D4A0, 0x36, 0x46),
LocationName.lightning_lookout_region: DKC3Level(0x34D4AA, 0x34D4AB, 0x10, 0x33),
LocationName.koindozer_klamber_region: DKC3Level(0x34D4C0, 0x34D4C1, 0x34, 0x47),
LocationName.poisonous_pipeline_region: DKC3Level(0x34D4D6, 0x34D4D7, 0x39, 0x3F),
LocationName.stampede_sprint_region: DKC3Level(0x34D51A, 0x34D51B, 0x3D, 0x49),
LocationName.criss_cross_cliffs_region: DKC3Level(0x34D525, 0x34D526, 0x3E, 0x4A),
LocationName.tyrant_twin_tussle_region: DKC3Level(0x34D530, 0x34D531, 0x3F, 0x4B),
LocationName.swoopy_salvo_region: DKC3Level(0x34D53B, 0x34D53C, 0x40, 0x31),
#LocationName.rocket_rush_region: DKC3Level(0x34D546, 0x34D547, 0x05, 0x4C), # Rocket Rush is not getting shuffled
}
level_list = [
LocationName.lakeside_limbo_region,
LocationName.doorstop_dash_region,
LocationName.tidal_trouble_region,
LocationName.skiddas_row_region,
LocationName.murky_mill_region,
LocationName.barrel_shield_bust_up_region,
LocationName.riverside_race_region,
LocationName.squeals_on_wheels_region,
LocationName.springin_spiders_region,
LocationName.bobbing_barrel_brawl_region,
LocationName.bazzas_blockade_region,
LocationName.rocket_barrel_ride_region,
LocationName.kreeping_klasps_region,
LocationName.tracker_barrel_trek_region,
LocationName.fish_food_frenzy_region,
LocationName.fire_ball_frenzy_region,
LocationName.demolition_drain_pipe_region,
LocationName.ripsaw_rage_region,
LocationName.blazing_bazookas_region,
LocationName.low_g_labyrinth_region,
LocationName.krevice_kreepers_region,
LocationName.tearaway_toboggan_region,
LocationName.barrel_drop_bounce_region,
LocationName.krack_shot_kroc_region,
LocationName.lemguin_lunge_region,
LocationName.buzzer_barrage_region,
LocationName.kong_fused_cliffs_region,
LocationName.floodlit_fish_region,
LocationName.pothole_panic_region,
LocationName.ropey_rumpus_region,
LocationName.konveyor_rope_clash_region,
LocationName.creepy_caverns_region,
LocationName.lightning_lookout_region,
LocationName.koindozer_klamber_region,
LocationName.poisonous_pipeline_region,
LocationName.stampede_sprint_region,
LocationName.criss_cross_cliffs_region,
LocationName.tyrant_twin_tussle_region,
LocationName.swoopy_salvo_region,
#LocationName.rocket_rush_region,
]

337
worlds/dkc3/Locations.py Normal file
View File

@@ -0,0 +1,337 @@
import typing
from BaseClasses import Location
from .Names import LocationName
from worlds.AutoWorld import World
class DKC3Location(Location):
game: str = "Donkey Kong Country 3"
progress_byte: int = 0x000000
progress_bit: int = 0
inverted_bit: bool = False
def __init__(self, player: int, name: str = '', address: int = None, parent=None, prog_byte: int = None, prog_bit: int = None, invert: bool = False):
super().__init__(player, name, address, parent)
self.progress_byte = prog_byte
self.progress_bit = prog_bit
self.inverted_bit = invert
level_location_table = {
LocationName.lakeside_limbo_flag: 0xDC3000,
LocationName.lakeside_limbo_bonus_1: 0xDC3001,
LocationName.lakeside_limbo_bonus_2: 0xDC3002,
LocationName.lakeside_limbo_dk: 0xDC3003,
LocationName.doorstop_dash_flag: 0xDC3004,
LocationName.doorstop_dash_bonus_1: 0xDC3005,
LocationName.doorstop_dash_bonus_2: 0xDC3006,
LocationName.doorstop_dash_dk: 0xDC3007,
LocationName.tidal_trouble_flag: 0xDC3008,
LocationName.tidal_trouble_bonus_1: 0xDC3009,
LocationName.tidal_trouble_bonus_2: 0xDC300A,
LocationName.tidal_trouble_dk: 0xDC300B,
LocationName.skiddas_row_flag: 0xDC300C,
LocationName.skiddas_row_bonus_1: 0xDC300D,
LocationName.skiddas_row_bonus_2: 0xDC300E,
LocationName.skiddas_row_dk: 0xDC300F,
LocationName.murky_mill_flag: 0xDC3010,
LocationName.murky_mill_bonus_1: 0xDC3011,
LocationName.murky_mill_bonus_2: 0xDC3012,
LocationName.murky_mill_dk: 0xDC3013,
LocationName.barrel_shield_bust_up_flag: 0xDC3014,
LocationName.barrel_shield_bust_up_bonus_1: 0xDC3015,
LocationName.barrel_shield_bust_up_bonus_2: 0xDC3016,
LocationName.barrel_shield_bust_up_dk: 0xDC3017,
LocationName.riverside_race_flag: 0xDC3018,
LocationName.riverside_race_bonus_1: 0xDC3019,
LocationName.riverside_race_bonus_2: 0xDC301A,
LocationName.riverside_race_dk: 0xDC301B,
LocationName.squeals_on_wheels_flag: 0xDC301C,
LocationName.squeals_on_wheels_bonus_1: 0xDC301D,
LocationName.squeals_on_wheels_bonus_2: 0xDC301E,
LocationName.squeals_on_wheels_dk: 0xDC301F,
LocationName.springin_spiders_flag: 0xDC3020,
LocationName.springin_spiders_bonus_1: 0xDC3021,
LocationName.springin_spiders_bonus_2: 0xDC3022,
LocationName.springin_spiders_dk: 0xDC3023,
LocationName.bobbing_barrel_brawl_flag: 0xDC3024,
LocationName.bobbing_barrel_brawl_bonus_1: 0xDC3025,
LocationName.bobbing_barrel_brawl_bonus_2: 0xDC3026,
LocationName.bobbing_barrel_brawl_dk: 0xDC3027,
LocationName.bazzas_blockade_flag: 0xDC3028,
LocationName.bazzas_blockade_bonus_1: 0xDC3029,
LocationName.bazzas_blockade_bonus_2: 0xDC302A,
LocationName.bazzas_blockade_dk: 0xDC302B,
LocationName.rocket_barrel_ride_flag: 0xDC302C,
LocationName.rocket_barrel_ride_bonus_1: 0xDC302D,
LocationName.rocket_barrel_ride_bonus_2: 0xDC302E,
LocationName.rocket_barrel_ride_dk: 0xDC302F,
LocationName.kreeping_klasps_flag: 0xDC3030,
LocationName.kreeping_klasps_bonus_1: 0xDC3031,
LocationName.kreeping_klasps_bonus_2: 0xDC3032,
LocationName.kreeping_klasps_dk: 0xDC3033,
LocationName.tracker_barrel_trek_flag: 0xDC3034,
LocationName.tracker_barrel_trek_bonus_1: 0xDC3035,
LocationName.tracker_barrel_trek_bonus_2: 0xDC3036,
LocationName.tracker_barrel_trek_dk: 0xDC3037,
LocationName.fish_food_frenzy_flag: 0xDC3038,
LocationName.fish_food_frenzy_bonus_1: 0xDC3039,
LocationName.fish_food_frenzy_bonus_2: 0xDC303A,
LocationName.fish_food_frenzy_dk: 0xDC303B,
LocationName.fire_ball_frenzy_flag: 0xDC303C,
LocationName.fire_ball_frenzy_bonus_1: 0xDC303D,
LocationName.fire_ball_frenzy_bonus_2: 0xDC303E,
LocationName.fire_ball_frenzy_dk: 0xDC303F,
LocationName.demolition_drain_pipe_flag: 0xDC3040,
LocationName.demolition_drain_pipe_bonus_1: 0xDC3041,
LocationName.demolition_drain_pipe_bonus_2: 0xDC3042,
LocationName.demolition_drain_pipe_dk: 0xDC3043,
LocationName.ripsaw_rage_flag: 0xDC3044,
LocationName.ripsaw_rage_bonus_1: 0xDC3045,
LocationName.ripsaw_rage_bonus_2: 0xDC3046,
LocationName.ripsaw_rage_dk: 0xDC3047,
LocationName.blazing_bazookas_flag: 0xDC3048,
LocationName.blazing_bazookas_bonus_1: 0xDC3049,
LocationName.blazing_bazookas_bonus_2: 0xDC304A,
LocationName.blazing_bazookas_dk: 0xDC304B,
LocationName.low_g_labyrinth_flag: 0xDC304C,
LocationName.low_g_labyrinth_bonus_1: 0xDC304D,
LocationName.low_g_labyrinth_bonus_2: 0xDC304E,
LocationName.low_g_labyrinth_dk: 0xDC304F,
LocationName.krevice_kreepers_flag: 0xDC3050,
LocationName.krevice_kreepers_bonus_1: 0xDC3051,
LocationName.krevice_kreepers_bonus_2: 0xDC3052,
LocationName.krevice_kreepers_dk: 0xDC3053,
LocationName.tearaway_toboggan_flag: 0xDC3054,
LocationName.tearaway_toboggan_bonus_1: 0xDC3055,
LocationName.tearaway_toboggan_bonus_2: 0xDC3056,
LocationName.tearaway_toboggan_dk: 0xDC3057,
LocationName.barrel_drop_bounce_flag: 0xDC3058,
LocationName.barrel_drop_bounce_bonus_1: 0xDC3059,
LocationName.barrel_drop_bounce_bonus_2: 0xDC305A,
LocationName.barrel_drop_bounce_dk: 0xDC305B,
LocationName.krack_shot_kroc_flag: 0xDC305C,
LocationName.krack_shot_kroc_bonus_1: 0xDC305D,
LocationName.krack_shot_kroc_bonus_2: 0xDC305E,
LocationName.krack_shot_kroc_dk: 0xDC305F,
LocationName.lemguin_lunge_flag: 0xDC3060,
LocationName.lemguin_lunge_bonus_1: 0xDC3061,
LocationName.lemguin_lunge_bonus_2: 0xDC3062,
LocationName.lemguin_lunge_dk: 0xDC3063,
LocationName.buzzer_barrage_flag: 0xDC3064,
LocationName.buzzer_barrage_bonus_1: 0xDC3065,
LocationName.buzzer_barrage_bonus_2: 0xDC3066,
LocationName.buzzer_barrage_dk: 0xDC3067,
LocationName.kong_fused_cliffs_flag: 0xDC3068,
LocationName.kong_fused_cliffs_bonus_1: 0xDC3069,
LocationName.kong_fused_cliffs_bonus_2: 0xDC306A,
LocationName.kong_fused_cliffs_dk: 0xDC306B,
LocationName.floodlit_fish_flag: 0xDC306C,
LocationName.floodlit_fish_bonus_1: 0xDC306D,
LocationName.floodlit_fish_bonus_2: 0xDC306E,
LocationName.floodlit_fish_dk: 0xDC306F,
LocationName.pothole_panic_flag: 0xDC3070,
LocationName.pothole_panic_bonus_1: 0xDC3071,
LocationName.pothole_panic_bonus_2: 0xDC3072,
LocationName.pothole_panic_dk: 0xDC3073,
LocationName.ropey_rumpus_flag: 0xDC3074,
LocationName.ropey_rumpus_bonus_1: 0xDC3075,
LocationName.ropey_rumpus_bonus_2: 0xDC3076,
LocationName.ropey_rumpus_dk: 0xDC3077,
LocationName.konveyor_rope_clash_flag: 0xDC3078,
LocationName.konveyor_rope_clash_bonus_1: 0xDC3079,
LocationName.konveyor_rope_clash_bonus_2: 0xDC307A,
LocationName.konveyor_rope_clash_dk: 0xDC307B,
LocationName.creepy_caverns_flag: 0xDC307C,
LocationName.creepy_caverns_bonus_1: 0xDC307D,
LocationName.creepy_caverns_bonus_2: 0xDC307E,
LocationName.creepy_caverns_dk: 0xDC307F,
LocationName.lightning_lookout_flag: 0xDC3080,
LocationName.lightning_lookout_bonus_1: 0xDC3081,
LocationName.lightning_lookout_bonus_2: 0xDC3082,
LocationName.lightning_lookout_dk: 0xDC3083,
LocationName.koindozer_klamber_flag: 0xDC3084,
LocationName.koindozer_klamber_bonus_1: 0xDC3085,
LocationName.koindozer_klamber_bonus_2: 0xDC3086,
LocationName.koindozer_klamber_dk: 0xDC3087,
LocationName.poisonous_pipeline_flag: 0xDC3088,
LocationName.poisonous_pipeline_bonus_1: 0xDC3089,
LocationName.poisonous_pipeline_bonus_2: 0xDC308A,
LocationName.poisonous_pipeline_dk: 0xDC308B,
LocationName.stampede_sprint_flag: 0xDC308C,
LocationName.stampede_sprint_bonus_1: 0xDC308D,
LocationName.stampede_sprint_bonus_2: 0xDC308E,
LocationName.stampede_sprint_bonus_3: 0xDC308F,
LocationName.stampede_sprint_dk: 0xDC3090,
LocationName.criss_cross_cliffs_flag: 0xDC3091,
LocationName.criss_cross_cliffs_bonus_1: 0xDC3092,
LocationName.criss_cross_cliffs_bonus_2: 0xDC3093,
LocationName.criss_cross_cliffs_dk: 0xDC3094,
LocationName.tyrant_twin_tussle_flag: 0xDC3095,
LocationName.tyrant_twin_tussle_bonus_1: 0xDC3096,
LocationName.tyrant_twin_tussle_bonus_2: 0xDC3097,
LocationName.tyrant_twin_tussle_bonus_3: 0xDC3098,
LocationName.tyrant_twin_tussle_dk: 0xDC3099,
LocationName.swoopy_salvo_flag: 0xDC309A,
LocationName.swoopy_salvo_bonus_1: 0xDC309B,
LocationName.swoopy_salvo_bonus_2: 0xDC309C,
LocationName.swoopy_salvo_bonus_3: 0xDC309D,
LocationName.swoopy_salvo_dk: 0xDC309E,
LocationName.rocket_rush_flag: 0xDC309F,
LocationName.rocket_rush_dk: 0xDC30A0,
}
kong_location_table = {
LocationName.lakeside_limbo_kong: 0xDC3100,
LocationName.doorstop_dash_kong: 0xDC3104,
LocationName.tidal_trouble_kong: 0xDC3108,
LocationName.skiddas_row_kong: 0xDC310C,
LocationName.murky_mill_kong: 0xDC3110,
LocationName.barrel_shield_bust_up_kong: 0xDC3114,
LocationName.riverside_race_kong: 0xDC3118,
LocationName.squeals_on_wheels_kong: 0xDC311C,
LocationName.springin_spiders_kong: 0xDC3120,
LocationName.bobbing_barrel_brawl_kong: 0xDC3124,
LocationName.bazzas_blockade_kong: 0xDC3128,
LocationName.rocket_barrel_ride_kong: 0xDC312C,
LocationName.kreeping_klasps_kong: 0xDC3130,
LocationName.tracker_barrel_trek_kong: 0xDC3134,
LocationName.fish_food_frenzy_kong: 0xDC3138,
LocationName.fire_ball_frenzy_kong: 0xDC313C,
LocationName.demolition_drain_pipe_kong: 0xDC3140,
LocationName.ripsaw_rage_kong: 0xDC3144,
LocationName.blazing_bazookas_kong: 0xDC3148,
LocationName.low_g_labyrinth_kong: 0xDC314C,
LocationName.krevice_kreepers_kong: 0xDC3150,
LocationName.tearaway_toboggan_kong: 0xDC3154,
LocationName.barrel_drop_bounce_kong: 0xDC3158,
LocationName.krack_shot_kroc_kong: 0xDC315C,
LocationName.lemguin_lunge_kong: 0xDC3160,
LocationName.buzzer_barrage_kong: 0xDC3164,
LocationName.kong_fused_cliffs_kong: 0xDC3168,
LocationName.floodlit_fish_kong: 0xDC316C,
LocationName.pothole_panic_kong: 0xDC3170,
LocationName.ropey_rumpus_kong: 0xDC3174,
LocationName.konveyor_rope_clash_kong: 0xDC3178,
LocationName.creepy_caverns_kong: 0xDC317C,
LocationName.lightning_lookout_kong: 0xDC3180,
LocationName.koindozer_klamber_kong: 0xDC3184,
LocationName.poisonous_pipeline_kong: 0xDC3188,
LocationName.stampede_sprint_kong: 0xDC318C,
LocationName.criss_cross_cliffs_kong: 0xDC3191,
LocationName.tyrant_twin_tussle_kong: 0xDC3195,
LocationName.swoopy_salvo_kong: 0xDC319A,
}
boss_location_table = {
LocationName.belchas_barn: 0xDC30A1,
LocationName.arichs_ambush: 0xDC30A2,
LocationName.squirts_showdown: 0xDC30A3,
LocationName.kaos_karnage: 0xDC30A4,
LocationName.bleaks_house: 0xDC30A5,
LocationName.barboss_barrier: 0xDC30A6,
LocationName.kastle_kaos: 0xDC30A7,
LocationName.knautilus: 0xDC30A8,
}
secret_cave_location_table = {
LocationName.belchas_burrow: 0xDC30A9,
LocationName.kong_cave: 0xDC30AA,
LocationName.undercover_cove: 0xDC30AB,
LocationName.ks_cache: 0xDC30AC,
LocationName.hill_top_hoard: 0xDC30AD,
LocationName.bounty_beach: 0xDC30AE,
LocationName.smugglers_cove: 0xDC30AF,
LocationName.arichs_hoard: 0xDC30B0,
LocationName.bounty_bay: 0xDC30B1,
LocationName.sky_high_secret: 0xDC30B2,
LocationName.glacial_grotto: 0xDC30B3,
LocationName.cifftop_cache: 0xDC30B4,
LocationName.sewer_stockpile: 0xDC30B5,
LocationName.banana_bird_mother: 0xDC30B6,
}
brothers_bear_location_table = {
LocationName.bazaars_general_store_1: 0xDC30B7,
LocationName.bazaars_general_store_2: 0xDC30B8,
LocationName.brambles_bungalow: 0xDC30B9,
LocationName.flower_spot: 0xDC30BA,
LocationName.barters_swap_shop: 0xDC30BB,
LocationName.barnacles_island: 0xDC30BC,
LocationName.blues_beach_hut: 0xDC30BD,
LocationName.blizzards_basecamp: 0xDC30BE,
}
all_locations = {
**level_location_table,
**boss_location_table,
**secret_cave_location_table,
**brothers_bear_location_table,
**kong_location_table,
}
location_table = {}
def setup_locations(world: World):
location_table = {**level_location_table, **boss_location_table, **secret_cave_location_table}
if False:#world.options.include_trade_sequence:
location_table.update({**brothers_bear_location_table})
if world.options.kongsanity:
location_table.update({**kong_location_table})
return location_table
lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in all_locations.items()}

View File

@@ -0,0 +1,21 @@
# Junk Definitions
one_up_balloon = "1-Up Balloon"
bear_coin = "Bear Coin"
# Collectable Definitions
bonus_coin = "Bonus Coin"
dk_coin = "DK Coin"
banana_bird = "Banana Bird"
krematoa_cog = "Krematoa Cog"
# Inventory Definitions
progressive_boat = "Progressive Boat Upgrade"
present = "Present"
bowling_ball = "Bowling Ball"
shell = "Shell"
mirror = "Mirror"
flower = "Flupperius Petallus Pongus"
wrench = "No. 6 Wrench"
# Other Definitions
victory = "Donkey Kong"

View File

@@ -0,0 +1,375 @@
# Level Definitions
lakeside_limbo_flag = "Lakeside Limbo - Flag"
lakeside_limbo_kong = "Lakeside Limbo - KONG"
lakeside_limbo_bonus_1 = "Lakeside Limbo - Bonus 1"
lakeside_limbo_bonus_2 = "Lakeside Limbo - Bonus 2"
lakeside_limbo_dk = "Lakeside Limbo - DK Coin"
doorstop_dash_flag = "Doorstop Dash - Flag"
doorstop_dash_kong = "Doorstop Dash - KONG"
doorstop_dash_bonus_1 = "Doorstop Dash - Bonus 1"
doorstop_dash_bonus_2 = "Doorstop Dash - Bonus 2"
doorstop_dash_dk = "Doorstop Dash - DK Coin"
tidal_trouble_flag = "Tidal Trouble - Flag"
tidal_trouble_kong = "Tidal Trouble - KONG"
tidal_trouble_bonus_1 = "Tidal Trouble - Bonus 1"
tidal_trouble_bonus_2 = "Tidal Trouble - Bonus 2"
tidal_trouble_dk = "Tidal Trouble - DK Coin"
skiddas_row_flag = "Skidda's Row - Flag"
skiddas_row_kong = "Skidda's Row - KONG"
skiddas_row_bonus_1 = "Skidda's Row - Bonus 1"
skiddas_row_bonus_2 = "Skidda's Row - Bonus 2"
skiddas_row_dk = "Skidda's Row - DK Coin"
murky_mill_flag = "Murky Mill - Flag"
murky_mill_kong = "Murky Mill - KONG"
murky_mill_bonus_1 = "Murky Mill - Bonus 1"
murky_mill_bonus_2 = "Murky Mill - Bonus 2"
murky_mill_dk = "Murky Mill - DK Coin"
barrel_shield_bust_up_flag = "Barrel Shield Bust-Up - Flag"
barrel_shield_bust_up_kong = "Barrel Shield Bust-Up - KONG"
barrel_shield_bust_up_bonus_1 = "Barrel Shield Bust-Up - Bonus 1"
barrel_shield_bust_up_bonus_2 = "Barrel Shield Bust-Up - Bonus 2"
barrel_shield_bust_up_dk = "Barrel Shield Bust-Up - DK Coin"
riverside_race_flag = "Riverside Race - Flag"
riverside_race_kong = "Riverside Race - KONG"
riverside_race_bonus_1 = "Riverside Race - Bonus 1"
riverside_race_bonus_2 = "Riverside Race - Bonus 2"
riverside_race_dk = "Riverside Race - DK Coin"
squeals_on_wheels_flag = "Squeals On Wheels - Flag"
squeals_on_wheels_kong = "Squeals On Wheels - KONG"
squeals_on_wheels_bonus_1 = "Squeals On Wheels - Bonus 1"
squeals_on_wheels_bonus_2 = "Squeals On Wheels - Bonus 2"
squeals_on_wheels_dk = "Squeals On Wheels - DK Coin"
springin_spiders_flag = "Springin' Spiders - Flag"
springin_spiders_kong = "Springin' Spiders - KONG"
springin_spiders_bonus_1 = "Springin' Spiders - Bonus 1"
springin_spiders_bonus_2 = "Springin' Spiders - Bonus 2"
springin_spiders_dk = "Springin' Spiders - DK Coin"
bobbing_barrel_brawl_flag = "Bobbing Barrel Brawl - Flag"
bobbing_barrel_brawl_kong = "Bobbing Barrel Brawl - KONG"
bobbing_barrel_brawl_bonus_1 = "Bobbing Barrel Brawl - Bonus 1"
bobbing_barrel_brawl_bonus_2 = "Bobbing Barrel Brawl - Bonus 2"
bobbing_barrel_brawl_dk = "Bobbing Barrel Brawl - DK Coin"
bazzas_blockade_flag = "Bazza's Blockade - Flag"
bazzas_blockade_kong = "Bazza's Blockade - KONG"
bazzas_blockade_bonus_1 = "Bazza's Blockade - Bonus 1"
bazzas_blockade_bonus_2 = "Bazza's Blockade - Bonus 2"
bazzas_blockade_dk = "Bazza's Blockade - DK Coin"
rocket_barrel_ride_flag = "Rocket Barrel Ride - Flag"
rocket_barrel_ride_kong = "Rocket Barrel Ride - KONG"
rocket_barrel_ride_bonus_1 = "Rocket Barrel Ride - Bonus 1"
rocket_barrel_ride_bonus_2 = "Rocket Barrel Ride - Bonus 2"
rocket_barrel_ride_dk = "Rocket Barrel Ride - DK Coin"
kreeping_klasps_flag = "Kreeping Klasps - Flag"
kreeping_klasps_kong = "Kreeping Klasps - KONG"
kreeping_klasps_bonus_1 = "Kreeping Klasps - Bonus 1"
kreeping_klasps_bonus_2 = "Kreeping Klasps - Bonus 2"
kreeping_klasps_dk = "Kreeping Klasps - DK Coin"
tracker_barrel_trek_flag = "Tracker Barrel Trek - Flag"
tracker_barrel_trek_kong = "Tracker Barrel Trek - KONG"
tracker_barrel_trek_bonus_1 = "Tracker Barrel Trek - Bonus 1"
tracker_barrel_trek_bonus_2 = "Tracker Barrel Trek - Bonus 2"
tracker_barrel_trek_dk = "Tracker Barrel Trek - DK Coin"
fish_food_frenzy_flag = "Fish Food Frenzy - Flag"
fish_food_frenzy_kong = "Fish Food Frenzy - KONG"
fish_food_frenzy_bonus_1 = "Fish Food Frenzy - Bonus 1"
fish_food_frenzy_bonus_2 = "Fish Food Frenzy - Bonus 2"
fish_food_frenzy_dk = "Fish Food Frenzy - DK Coin"
fire_ball_frenzy_flag = "Fire-Ball Frenzy - Flag"
fire_ball_frenzy_kong = "Fire-Ball Frenzy - KONG"
fire_ball_frenzy_bonus_1 = "Fire-Ball Frenzy - Bonus 1"
fire_ball_frenzy_bonus_2 = "Fire-Ball Frenzy - Bonus 2"
fire_ball_frenzy_dk = "Fire-Ball Frenzy - DK Coin"
demolition_drain_pipe_flag = "Demolition Drain-Pipe - Flag"
demolition_drain_pipe_kong = "Demolition Drain-Pipe - KONG"
demolition_drain_pipe_bonus_1 = "Demolition Drain-Pipe - Bonus 1"
demolition_drain_pipe_bonus_2 = "Demolition Drain-Pipe - Bonus 2"
demolition_drain_pipe_dk = "Demolition Drain-Pipe - DK Coin"
ripsaw_rage_flag = "Ripsaw Rage - Flag"
ripsaw_rage_kong = "Ripsaw Rage - KONG"
ripsaw_rage_bonus_1 = "Ripsaw Rage - Bonus 1"
ripsaw_rage_bonus_2 = "Ripsaw Rage - Bonus 2"
ripsaw_rage_dk = "Ripsaw Rage - DK Coin"
blazing_bazookas_flag = "Blazing Bazukas - Flag"
blazing_bazookas_kong = "Blazing Bazukas - KONG"
blazing_bazookas_bonus_1 = "Blazing Bazukas - Bonus 1"
blazing_bazookas_bonus_2 = "Blazing Bazukas - Bonus 2"
blazing_bazookas_dk = "Blazing Bazukas - DK Coin"
low_g_labyrinth_flag = "Low-G Labyrinth - Flag"
low_g_labyrinth_kong = "Low-G Labyrinth - KONG"
low_g_labyrinth_bonus_1 = "Low-G Labyrinth - Bonus 1"
low_g_labyrinth_bonus_2 = "Low-G Labyrinth - Bonus 2"
low_g_labyrinth_dk = "Low-G Labyrinth - DK Coin"
krevice_kreepers_flag = "Krevice Kreepers - Flag"
krevice_kreepers_kong = "Krevice Kreepers - KONG"
krevice_kreepers_bonus_1 = "Krevice Kreepers - Bonus 1"
krevice_kreepers_bonus_2 = "Krevice Kreepers - Bonus 2"
krevice_kreepers_dk = "Krevice Kreepers - DK Coin"
tearaway_toboggan_flag = "Tearaway Toboggan - Flag"
tearaway_toboggan_kong = "Tearaway Toboggan - KONG"
tearaway_toboggan_bonus_1 = "Tearaway Toboggan - Bonus 1"
tearaway_toboggan_bonus_2 = "Tearaway Toboggan - Bonus 2"
tearaway_toboggan_dk = "Tearaway Toboggan - DK Coin"
barrel_drop_bounce_flag = "Barrel Drop Bounce - Flag"
barrel_drop_bounce_kong = "Barrel Drop Bounce - KONG"
barrel_drop_bounce_bonus_1 = "Barrel Drop Bounce - Bonus 1"
barrel_drop_bounce_bonus_2 = "Barrel Drop Bounce - Bonus 2"
barrel_drop_bounce_dk = "Barrel Drop Bounce - DK Coin"
krack_shot_kroc_flag = "Krack-Shot Kroc - Flag"
krack_shot_kroc_kong = "Krack-Shot Kroc - KONG"
krack_shot_kroc_bonus_1 = "Krack-Shot Kroc - Bonus 1"
krack_shot_kroc_bonus_2 = "Krack-Shot Kroc - Bonus 2"
krack_shot_kroc_dk = "Krack-Shot Kroc - DK Coin"
lemguin_lunge_flag = "Lemguin Lunge - Flag"
lemguin_lunge_kong = "Lemguin Lunge - KONG"
lemguin_lunge_bonus_1 = "Lemguin Lunge - Bonus 1"
lemguin_lunge_bonus_2 = "Lemguin Lunge - Bonus 2"
lemguin_lunge_dk = "Lemguin Lunge - DK Coin"
buzzer_barrage_flag = "Buzzer Barrage - Flag"
buzzer_barrage_kong = "Buzzer Barrage - KONG"
buzzer_barrage_bonus_1 = "Buzzer Barrage - Bonus 1"
buzzer_barrage_bonus_2 = "Buzzer Barrage - Bonus 2"
buzzer_barrage_dk = "Buzzer Barrage - DK Coin"
kong_fused_cliffs_flag = "Kong-Fused Cliffs - Flag"
kong_fused_cliffs_kong = "Kong-Fused Cliffs - KONG"
kong_fused_cliffs_bonus_1 = "Kong-Fused Cliffs - Bonus 1"
kong_fused_cliffs_bonus_2 = "Kong-Fused Cliffs - Bonus 2"
kong_fused_cliffs_dk = "Kong-Fused Cliffs - DK Coin"
floodlit_fish_flag = "Floodlit Fish - Flag"
floodlit_fish_kong = "Floodlit Fish - KONG"
floodlit_fish_bonus_1 = "Floodlit Fish - Bonus 1"
floodlit_fish_bonus_2 = "Floodlit Fish - Bonus 2"
floodlit_fish_dk = "Floodlit Fish - DK Coin"
pothole_panic_flag = "Pothole Panic - Flag"
pothole_panic_kong = "Pothole Panic - KONG"
pothole_panic_bonus_1 = "Pothole Panic - Bonus 1"
pothole_panic_bonus_2 = "Pothole Panic - Bonus 2"
pothole_panic_dk = "Pothole Panic - DK Coin"
ropey_rumpus_flag = "Ropey Rumpus - Flag"
ropey_rumpus_kong = "Ropey Rumpus - KONG"
ropey_rumpus_bonus_1 = "Ropey Rumpus - Bonus 1"
ropey_rumpus_bonus_2 = "Ropey Rumpus - Bonus 2"
ropey_rumpus_dk = "Ropey Rumpus - DK Coin"
konveyor_rope_clash_flag = "Konveyor Rope Klash - Flag"
konveyor_rope_clash_kong = "Konveyor Rope Klash - KONG"
konveyor_rope_clash_bonus_1 = "Konveyor Rope Klash - Bonus 1"
konveyor_rope_clash_bonus_2 = "Konveyor Rope Klash - Bonus 2"
konveyor_rope_clash_dk = "Konveyor Rope Klash - DK Coin"
creepy_caverns_flag = "Creepy Caverns - Flag"
creepy_caverns_kong = "Creepy Caverns - KONG"
creepy_caverns_bonus_1 = "Creepy Caverns - Bonus 1"
creepy_caverns_bonus_2 = "Creepy Caverns - Bonus 2"
creepy_caverns_dk = "Creepy Caverns - DK Coin"
lightning_lookout_flag = "Lightning Lookout - Flag"
lightning_lookout_kong = "Lightning Lookout - KONG"
lightning_lookout_bonus_1 = "Lightning Lookout - Bonus 1"
lightning_lookout_bonus_2 = "Lightning Lookout - Bonus 2"
lightning_lookout_dk = "Lightning Lookout - DK Coin"
koindozer_klamber_flag = "Koindozer Klamber - Flag"
koindozer_klamber_kong = "Koindozer Klamber - KONG"
koindozer_klamber_bonus_1 = "Koindozer Klamber - Bonus 1"
koindozer_klamber_bonus_2 = "Koindozer Klamber - Bonus 2"
koindozer_klamber_dk = "Koindozer Klamber - DK Coin"
poisonous_pipeline_flag = "Poisonous Pipeline - Flag"
poisonous_pipeline_kong = "Poisonous Pipeline - KONG"
poisonous_pipeline_bonus_1 = "Poisonous Pipeline - Bonus 1"
poisonous_pipeline_bonus_2 = "Poisonous Pipeline - Bonus 2"
poisonous_pipeline_dk = "Poisonous Pipeline - DK Coin"
stampede_sprint_flag = "Stampede Sprint - Flag"
stampede_sprint_kong = "Stampede Sprint - KONG"
stampede_sprint_bonus_1 = "Stampede Sprint - Bonus 1"
stampede_sprint_bonus_2 = "Stampede Sprint - Bonus 2"
stampede_sprint_bonus_3 = "Stampede Sprint - Bonus 3"
stampede_sprint_dk = "Stampede Sprint - DK Coin"
criss_cross_cliffs_flag = "Criss Kross Cliffs - Flag"
criss_cross_cliffs_kong = "Criss Kross Cliffs - KONG"
criss_cross_cliffs_bonus_1 = "Criss Kross Cliffs - Bonus 1"
criss_cross_cliffs_bonus_2 = "Criss Kross Cliffs - Bonus 2"
criss_cross_cliffs_dk = "Criss Kross Cliffs - DK Coin"
tyrant_twin_tussle_flag = "Tyrant Twin Tussle - Flag"
tyrant_twin_tussle_kong = "Tyrant Twin Tussle - KONG"
tyrant_twin_tussle_bonus_1 = "Tyrant Twin Tussle - Bonus 1"
tyrant_twin_tussle_bonus_2 = "Tyrant Twin Tussle - Bonus 2"
tyrant_twin_tussle_bonus_3 = "Tyrant Twin Tussle - Bonus 3"
tyrant_twin_tussle_dk = "Tyrant Twin Tussle - DK Coin"
swoopy_salvo_flag = "Swoopy Salvo - Flag"
swoopy_salvo_kong = "Swoopy Salvo - KONG"
swoopy_salvo_bonus_1 = "Swoopy Salvo - Bonus 1"
swoopy_salvo_bonus_2 = "Swoopy Salvo - Bonus 2"
swoopy_salvo_bonus_3 = "Swoopy Salvo - Bonus 3"
swoopy_salvo_dk = "Swoopy Salvo - DK Coin"
rocket_rush_flag = "Rocket Rush - Flag"
rocket_rush_dk = "Rocket Rush - DK Coin"
# Boss Definitions
belchas_barn = "Belcha's Barn"
arichs_ambush = "Arich's Ambush"
squirts_showdown = "Squirt's Showdown"
kaos_karnage = "KAOS Karnage"
bleaks_house = "Bleak's House"
barboss_barrier = "Barbos's Barrier"
kastle_kaos = "Kastle KAOS"
knautilus = "Knautilus"
# Banana Bird Cave Definitions
belchas_burrow = "Belcha's Burrow"
kong_cave = "Kong Cave"
undercover_cove = "Undercover Cove"
ks_cache = "K's Cache"
hill_top_hoard = "Hill-Top Hoard"
bounty_beach = "Bounty Beach"
smugglers_cove = "Smuggler's Cove"
arichs_hoard = "Arich's Hoard"
bounty_bay = "Bounty Bay"
sky_high_secret = "Sky-High Secret"
glacial_grotto = "Glacial Grotto"
cifftop_cache = "Clifftop Cache"
sewer_stockpile = "Sewer Stockpile"
banana_bird_mother = "Banana Bird Mother"
# Brothers Bear Definitions
bazaars_general_store_1 = "Bazaar's General Store - 1"
bazaars_general_store_2 = "Bazaar's General Store - 2"
brambles_bungalow = "Bramble's Bungalow"
flower_spot = "Flower Spot"
barters_swap_shop = "Barter's Swap Shop"
barnacles_island = "Barnacle's Island"
blues_beach_hut = "Blue's Beach Hut"
blizzards_basecamp = "Bizzard's Basecamp"
# Region Definitions
menu_region = "Menu"
overworld_1_region = "Overworld 1"
overworld_2_region = "Overworld 2"
overworld_3_region = "Overworld 3"
overworld_4_region = "Overworld 4"
bazaar_region = "Bazaar's General Store Region"
bramble_region = "Bramble's Bungalow Region"
flower_spot_region = "Flower Spot Region"
barter_region = "Barter's Swap Shop Region"
barnacle_region = "Barnacle's Island Region"
blue_region = "Blue's Beach Hut Region"
blizzard_region = "Bizzard's Basecamp Region"
lake_orangatanga_region = "Lake Orangatanga"
kremwood_forest_region = "Kremwood Forest"
cotton_top_cove_region = "Cotton-Top Cove"
mekanos_region = "Mekanos"
k3_region = "K3"
razor_ridge_region = "Razor Ridge"
kaos_kore_region = "KAOS Kore"
krematoa_region = "Krematoa"
belchas_barn_region = "Belcha's Barn Region"
arichs_ambush_region = "Arich's Ambush Region"
squirts_showdown_region = "Squirt's Showdown Region"
kaos_karnage_region = "KAOS Karnage Region"
bleaks_house_region = "Bleak's House Region"
barboss_barrier_region = "Barbos's Barrier Region"
kastle_kaos_region = "Kastle KAOS Region"
knautilus_region = "Knautilus Region"
belchas_burrow_region = "Belcha's Burrow Region"
kong_cave_region = "Kong Cave Region"
undercover_cove_region = "Undercover Cove Region"
ks_cache_region = "K's Cache Region"
hill_top_hoard_region = "Hill-Top Hoard Region"
bounty_beach_region = "Bounty Beach Region"
smugglers_cove_region = "Smuggler's Cove Region"
arichs_hoard_region = "Arich's Hoard Region"
bounty_bay_region = "Bounty Bay Region"
sky_high_secret_region = "Sky-High Secret Region"
glacial_grotto_region = "Glacial Grotto Region"
cifftop_cache_region = "Clifftop Cache Region"
sewer_stockpile_region = "Sewer Stockpile Region"
lakeside_limbo_region = "Lakeside Limbo"
doorstop_dash_region = "Doorstop Dash"
tidal_trouble_region = "Tidal Trouble"
skiddas_row_region = "Skidda's Row"
murky_mill_region = "Murky Mill"
barrel_shield_bust_up_region = "Barrel Shield Bust-Up"
riverside_race_region = "Riverside Race"
squeals_on_wheels_region = "Squeals On Wheels"
springin_spiders_region = "Springin' Spiders"
bobbing_barrel_brawl_region = "Bobbing Barrel Brawl"
bazzas_blockade_region = "Bazza's Blockade"
rocket_barrel_ride_region = "Rocket Barrel Ride"
kreeping_klasps_region = "Kreeping Klasps"
tracker_barrel_trek_region = "Tracker Barrel Trek"
fish_food_frenzy_region = "Fish Food Frenzy"
fire_ball_frenzy_region = "Fire-Ball Frenzy"
demolition_drain_pipe_region = "Demolition Drain-Pipe"
ripsaw_rage_region = "Ripsaw Rage"
blazing_bazookas_region = "Blazing Bazukas"
low_g_labyrinth_region = "Low-G Labyrinth"
krevice_kreepers_region = "Krevice Kreepers"
tearaway_toboggan_region = "Tearaway Toboggan"
barrel_drop_bounce_region = "Barrel Drop Bounce"
krack_shot_kroc_region = "Krack-Shot Kroc"
lemguin_lunge_region = "Lemguin Lunge"
buzzer_barrage_region = "Buzzer Barrage"
kong_fused_cliffs_region = "Kong-Fused Cliffs"
floodlit_fish_region = "Floodlit Fish"
pothole_panic_region = "Pothole Panic"
ropey_rumpus_region = "Ropey Rumpus"
konveyor_rope_clash_region = "Konveyor Rope Klash"
creepy_caverns_region = "Creepy Caverns"
lightning_lookout_region = "Lightning Lookout"
koindozer_klamber_region = "Koindozer Klamber"
poisonous_pipeline_region = "Poisonous Pipeline"
stampede_sprint_region = "Stampede Sprint"
criss_cross_cliffs_region = "Criss Kross Cliffs"
tyrant_twin_tussle_region = "Tyrant Twin Tussle"
swoopy_salvo_region = "Swoopy Salvo"
rocket_rush_region = "Rocket Rush"

View File

203
worlds/dkc3/Options.py Normal file
View File

@@ -0,0 +1,203 @@
from dataclasses import dataclass
from Options import Choice, Range, Toggle, DefaultOnToggle, OptionGroup, PerGameCommonOptions
class Goal(Choice):
"""
Determines the goal of the seed
Knautilus: Scuttle the Knautilus in Krematoa and defeat Baron K. Roolenstein
Banana Bird Hunt: Find a certain number of Banana Birds and rescue their mother
"""
display_name = "Goal"
option_knautilus = 0
option_banana_bird_hunt = 1
default = 0
class IncludeTradeSequence(Toggle):
"""
Allows logic to place items at the various steps of the trade sequence
"""
display_name = "Include Trade Sequence"
class DKCoinsForGyrocopter(Range):
"""
How many DK Coins are needed to unlock the Gyrocopter
Note: Achieving this number before unlocking the Turbo Ski will cause the game to grant you a
one-time upgrade to the next non-unlocked boat, until you return to Funky. Logic does not assume
that you will use this.
"""
display_name = "DK Coins for Gyrocopter"
range_start = 10
range_end = 41
default = 30
class KrematoaBonusCoinCost(Range):
"""
How many Bonus Coins are needed to unlock each level in Krematoa
"""
display_name = "Krematoa Bonus Coins Cost"
range_start = 1
range_end = 17
default = 15
class PercentageOfExtraBonusCoins(Range):
"""
What Percentage of unneeded Bonus Coins are included in the item pool
"""
display_name = "Percentage of Extra Bonus Coins"
range_start = 0
range_end = 100
default = 100
class NumberOfBananaBirds(Range):
"""
How many Banana Birds are put into the item pool
"""
display_name = "Number of Banana Birds"
range_start = 5
range_end = 15
default = 15
class PercentageOfBananaBirds(Range):
"""
What Percentage of Banana Birds in the item pool are required for Banana Bird Hunt
"""
display_name = "Percentage of Banana Birds"
range_start = 20
range_end = 100
default = 100
class KONGsanity(Toggle):
"""
Whether collecting all four KONG letters in each level grants a check
"""
display_name = "KONGsanity"
class LevelShuffle(Toggle):
"""
Whether levels are shuffled
"""
display_name = "Level Shuffle"
class Difficulty(Choice):
"""
Which Difficulty Level to use
NORML: The Normal Difficulty
HARDR: Many DK Barrels are removed
TUFST: Most DK Barrels and all Midway Barrels are removed
"""
display_name = "Difficulty"
option_norml = 0
option_hardr = 1
option_tufst = 2
default = 0
@classmethod
def get_option_name(cls, value) -> str:
if cls.auto_display_name:
return cls.name_lookup[value].upper()
else:
return cls.name_lookup[value]
class Autosave(DefaultOnToggle):
"""
Whether the game should autosave after each level
"""
display_name = "Autosave"
class MERRY(Toggle):
"""
Whether the Bonus Barrels will be Christmas-themed
"""
display_name = "MERRY"
class MusicShuffle(Toggle):
"""
Whether music is shuffled
"""
display_name = "Music Shuffle"
class KongPaletteSwap(Choice):
"""
Which Palette to use for the Kongs
"""
display_name = "Kong Palette Swap"
option_default = 0
option_purple = 1
option_spooky = 2
option_dark = 3
option_chocolate = 4
option_shadow = 5
option_red_gold = 6
option_gbc = 7
option_halloween = 8
default = 0
class StartingLifeCount(Range):
"""
How many extra lives to start the game with
"""
display_name = "Starting Life Count"
range_start = 1
range_end = 99
default = 5
dkc3_option_groups = [
OptionGroup("Goal Options", [
Goal,
KrematoaBonusCoinCost,
PercentageOfExtraBonusCoins,
NumberOfBananaBirds,
PercentageOfBananaBirds,
]),
OptionGroup("Aesthetics", [
Autosave,
MERRY,
MusicShuffle,
KongPaletteSwap,
StartingLifeCount,
]),
]
@dataclass
class DKC3Options(PerGameCommonOptions):
#death_link: DeathLink # Disabled
#include_trade_sequence: IncludeTradeSequence # Disabled
goal: Goal
krematoa_bonus_coin_cost: KrematoaBonusCoinCost
percentage_of_extra_bonus_coins: PercentageOfExtraBonusCoins
number_of_banana_birds: NumberOfBananaBirds
percentage_of_banana_birds: PercentageOfBananaBirds
dk_coins_for_gyrocopter: DKCoinsForGyrocopter
kongsanity: KONGsanity
level_shuffle: LevelShuffle
difficulty: Difficulty
autosave: Autosave
merry: MERRY
music_shuffle: MusicShuffle
kong_palette_swap: KongPaletteSwap
starting_life_count: StartingLifeCount

954
worlds/dkc3/Regions.py Normal file
View File

@@ -0,0 +1,954 @@
import typing
from BaseClasses import Region, Entrance
from worlds.AutoWorld import World
from .Locations import DKC3Location
from .Names import LocationName, ItemName
def create_regions(world: World, active_locations):
menu_region = create_region(world, active_locations, 'Menu', None)
overworld_1_region_locations = {}
if world.options.goal != "knautilus":
overworld_1_region_locations.update({LocationName.banana_bird_mother: []})
overworld_1_region = create_region(world, active_locations, LocationName.overworld_1_region,
overworld_1_region_locations)
overworld_2_region_locations = {}
overworld_2_region = create_region(world, active_locations, LocationName.overworld_2_region,
overworld_2_region_locations)
overworld_3_region_locations = {}
overworld_3_region = create_region(world, active_locations, LocationName.overworld_3_region,
overworld_3_region_locations)
overworld_4_region_locations = {}
overworld_4_region = create_region(world, active_locations, LocationName.overworld_4_region,
overworld_4_region_locations)
lake_orangatanga_region = create_region(world, active_locations, LocationName.lake_orangatanga_region, None)
kremwood_forest_region = create_region(world, active_locations, LocationName.kremwood_forest_region, None)
cotton_top_cove_region = create_region(world, active_locations, LocationName.cotton_top_cove_region, None)
mekanos_region = create_region(world, active_locations, LocationName.mekanos_region, None)
k3_region = create_region(world, active_locations, LocationName.k3_region, None)
razor_ridge_region = create_region(world, active_locations, LocationName.razor_ridge_region, None)
kaos_kore_region = create_region(world, active_locations, LocationName.kaos_kore_region, None)
krematoa_region = create_region(world, active_locations, LocationName.krematoa_region, None)
lakeside_limbo_region_locations = {
LocationName.lakeside_limbo_flag : [0x657, 1],
LocationName.lakeside_limbo_bonus_1 : [0x657, 2],
LocationName.lakeside_limbo_bonus_2 : [0x657, 3],
LocationName.lakeside_limbo_dk : [0x657, 5],
}
if world.options.kongsanity:
lakeside_limbo_region_locations[LocationName.lakeside_limbo_kong] = []
lakeside_limbo_region = create_region(world, active_locations, LocationName.lakeside_limbo_region,
lakeside_limbo_region_locations)
doorstop_dash_region_locations = {
LocationName.doorstop_dash_flag : [0x65A, 1],
LocationName.doorstop_dash_bonus_1 : [0x65A, 2],
LocationName.doorstop_dash_bonus_2 : [0x65A, 3],
LocationName.doorstop_dash_dk : [0x65A, 5],
}
if world.options.kongsanity:
doorstop_dash_region_locations[LocationName.doorstop_dash_kong] = []
doorstop_dash_region = create_region(world, active_locations, LocationName.doorstop_dash_region,
doorstop_dash_region_locations)
tidal_trouble_region_locations = {
LocationName.tidal_trouble_flag : [0x659, 1],
LocationName.tidal_trouble_bonus_1 : [0x659, 2],
LocationName.tidal_trouble_bonus_2 : [0x659, 3],
LocationName.tidal_trouble_dk : [0x659, 5],
}
if world.options.kongsanity:
tidal_trouble_region_locations[LocationName.tidal_trouble_kong] = []
tidal_trouble_region = create_region(world, active_locations, LocationName.tidal_trouble_region,
tidal_trouble_region_locations)
skiddas_row_region_locations = {
LocationName.skiddas_row_flag : [0x65D, 1],
LocationName.skiddas_row_bonus_1 : [0x65D, 2],
LocationName.skiddas_row_bonus_2 : [0x65D, 3],
LocationName.skiddas_row_dk : [0x65D, 5],
}
if world.options.kongsanity:
skiddas_row_region_locations[LocationName.skiddas_row_kong] = []
skiddas_row_region = create_region(world, active_locations, LocationName.skiddas_row_region,
skiddas_row_region_locations)
murky_mill_region_locations = {
LocationName.murky_mill_flag : [0x65C, 1],
LocationName.murky_mill_bonus_1 : [0x65C, 2],
LocationName.murky_mill_bonus_2 : [0x65C, 3],
LocationName.murky_mill_dk : [0x65C, 5],
}
if world.options.kongsanity:
murky_mill_region_locations[LocationName.murky_mill_kong] = []
murky_mill_region = create_region(world, active_locations, LocationName.murky_mill_region,
murky_mill_region_locations)
barrel_shield_bust_up_region_locations = {
LocationName.barrel_shield_bust_up_flag : [0x662, 1],
LocationName.barrel_shield_bust_up_bonus_1 : [0x662, 2],
LocationName.barrel_shield_bust_up_bonus_2 : [0x662, 3],
LocationName.barrel_shield_bust_up_dk : [0x662, 5],
}
if world.options.kongsanity:
barrel_shield_bust_up_region_locations[LocationName.barrel_shield_bust_up_kong] = []
barrel_shield_bust_up_region = create_region(world, active_locations,
LocationName.barrel_shield_bust_up_region,
barrel_shield_bust_up_region_locations)
riverside_race_region_locations = {
LocationName.riverside_race_flag : [0x664, 1],
LocationName.riverside_race_bonus_1 : [0x664, 2],
LocationName.riverside_race_bonus_2 : [0x664, 3],
LocationName.riverside_race_dk : [0x664, 5],
}
if world.options.kongsanity:
riverside_race_region_locations[LocationName.riverside_race_kong] = []
riverside_race_region = create_region(world, active_locations, LocationName.riverside_race_region,
riverside_race_region_locations)
squeals_on_wheels_region_locations = {
LocationName.squeals_on_wheels_flag : [0x65B, 1],
LocationName.squeals_on_wheels_bonus_1 : [0x65B, 2],
LocationName.squeals_on_wheels_bonus_2 : [0x65B, 3],
LocationName.squeals_on_wheels_dk : [0x65B, 5],
}
if world.options.kongsanity:
squeals_on_wheels_region_locations[LocationName.squeals_on_wheels_kong] = []
squeals_on_wheels_region = create_region(world, active_locations, LocationName.squeals_on_wheels_region,
squeals_on_wheels_region_locations)
springin_spiders_region_locations = {
LocationName.springin_spiders_flag : [0x661, 1],
LocationName.springin_spiders_bonus_1 : [0x661, 2],
LocationName.springin_spiders_bonus_2 : [0x661, 3],
LocationName.springin_spiders_dk : [0x661, 5],
}
if world.options.kongsanity:
springin_spiders_region_locations[LocationName.springin_spiders_kong] = []
springin_spiders_region = create_region(world, active_locations, LocationName.springin_spiders_region,
springin_spiders_region_locations)
bobbing_barrel_brawl_region_locations = {
LocationName.bobbing_barrel_brawl_flag : [0x666, 1],
LocationName.bobbing_barrel_brawl_bonus_1 : [0x666, 2],
LocationName.bobbing_barrel_brawl_bonus_2 : [0x666, 3],
LocationName.bobbing_barrel_brawl_dk : [0x666, 5],
}
if world.options.kongsanity:
bobbing_barrel_brawl_region_locations[LocationName.bobbing_barrel_brawl_kong] = []
bobbing_barrel_brawl_region = create_region(world, active_locations,
LocationName.bobbing_barrel_brawl_region,
bobbing_barrel_brawl_region_locations)
bazzas_blockade_region_locations = {
LocationName.bazzas_blockade_flag : [0x667, 1],
LocationName.bazzas_blockade_bonus_1 : [0x667, 2],
LocationName.bazzas_blockade_bonus_2 : [0x667, 3],
LocationName.bazzas_blockade_dk : [0x667, 5],
}
if world.options.kongsanity:
bazzas_blockade_region_locations[LocationName.bazzas_blockade_kong] = []
bazzas_blockade_region = create_region(world, active_locations, LocationName.bazzas_blockade_region,
bazzas_blockade_region_locations)
rocket_barrel_ride_region_locations = {
LocationName.rocket_barrel_ride_flag : [0x66A, 1],
LocationName.rocket_barrel_ride_bonus_1 : [0x66A, 2],
LocationName.rocket_barrel_ride_bonus_2 : [0x66A, 3],
LocationName.rocket_barrel_ride_dk : [0x66A, 5],
}
if world.options.kongsanity:
rocket_barrel_ride_region_locations[LocationName.rocket_barrel_ride_kong] = []
rocket_barrel_ride_region = create_region(world, active_locations, LocationName.rocket_barrel_ride_region,
rocket_barrel_ride_region_locations)
kreeping_klasps_region_locations = {
LocationName.kreeping_klasps_flag : [0x658, 1],
LocationName.kreeping_klasps_bonus_1 : [0x658, 2],
LocationName.kreeping_klasps_bonus_2 : [0x658, 3],
LocationName.kreeping_klasps_dk : [0x658, 5],
}
if world.options.kongsanity:
kreeping_klasps_region_locations[LocationName.kreeping_klasps_kong] = []
kreeping_klasps_region = create_region(world, active_locations, LocationName.kreeping_klasps_region,
kreeping_klasps_region_locations)
tracker_barrel_trek_region_locations = {
LocationName.tracker_barrel_trek_flag : [0x66B, 1],
LocationName.tracker_barrel_trek_bonus_1 : [0x66B, 2],
LocationName.tracker_barrel_trek_bonus_2 : [0x66B, 3],
LocationName.tracker_barrel_trek_dk : [0x66B, 5],
}
if world.options.kongsanity:
tracker_barrel_trek_region_locations[LocationName.tracker_barrel_trek_kong] = []
tracker_barrel_trek_region = create_region(world, active_locations, LocationName.tracker_barrel_trek_region,
tracker_barrel_trek_region_locations)
fish_food_frenzy_region_locations = {
LocationName.fish_food_frenzy_flag : [0x668, 1],
LocationName.fish_food_frenzy_bonus_1 : [0x668, 2],
LocationName.fish_food_frenzy_bonus_2 : [0x668, 3],
LocationName.fish_food_frenzy_dk : [0x668, 5],
}
if world.options.kongsanity:
fish_food_frenzy_region_locations[LocationName.fish_food_frenzy_kong] = []
fish_food_frenzy_region = create_region(world, active_locations, LocationName.fish_food_frenzy_region,
fish_food_frenzy_region_locations)
fire_ball_frenzy_region_locations = {
LocationName.fire_ball_frenzy_flag : [0x66D, 1],
LocationName.fire_ball_frenzy_bonus_1 : [0x66D, 2],
LocationName.fire_ball_frenzy_bonus_2 : [0x66D, 3],
LocationName.fire_ball_frenzy_dk : [0x66D, 5],
}
if world.options.kongsanity:
fire_ball_frenzy_region_locations[LocationName.fire_ball_frenzy_kong] = []
fire_ball_frenzy_region = create_region(world, active_locations, LocationName.fire_ball_frenzy_region,
fire_ball_frenzy_region_locations)
demolition_drain_pipe_region_locations = {
LocationName.demolition_drain_pipe_flag : [0x672, 1],
LocationName.demolition_drain_pipe_bonus_1 : [0x672, 2],
LocationName.demolition_drain_pipe_bonus_2 : [0x672, 3],
LocationName.demolition_drain_pipe_dk : [0x672, 5],
}
if world.options.kongsanity:
demolition_drain_pipe_region_locations[LocationName.demolition_drain_pipe_kong] = []
demolition_drain_pipe_region = create_region(world, active_locations,
LocationName.demolition_drain_pipe_region,
demolition_drain_pipe_region_locations)
ripsaw_rage_region_locations = {
LocationName.ripsaw_rage_flag : [0x660, 1],
LocationName.ripsaw_rage_bonus_1 : [0x660, 2],
LocationName.ripsaw_rage_bonus_2 : [0x660, 3],
LocationName.ripsaw_rage_dk : [0x660, 5],
}
if world.options.kongsanity:
ripsaw_rage_region_locations[LocationName.ripsaw_rage_kong] = []
ripsaw_rage_region = create_region(world, active_locations, LocationName.ripsaw_rage_region,
ripsaw_rage_region_locations)
blazing_bazookas_region_locations = {
LocationName.blazing_bazookas_flag : [0x66E, 1],
LocationName.blazing_bazookas_bonus_1 : [0x66E, 2],
LocationName.blazing_bazookas_bonus_2 : [0x66E, 3],
LocationName.blazing_bazookas_dk : [0x66E, 5],
}
if world.options.kongsanity:
blazing_bazookas_region_locations[LocationName.blazing_bazookas_kong] = []
blazing_bazookas_region = create_region(world, active_locations, LocationName.blazing_bazookas_region,
blazing_bazookas_region_locations)
low_g_labyrinth_region_locations = {
LocationName.low_g_labyrinth_flag : [0x670, 1],
LocationName.low_g_labyrinth_bonus_1 : [0x670, 2],
LocationName.low_g_labyrinth_bonus_2 : [0x670, 3],
LocationName.low_g_labyrinth_dk : [0x670, 5],
}
if world.options.kongsanity:
low_g_labyrinth_region_locations[LocationName.low_g_labyrinth_kong] = []
low_g_labyrinth_region = create_region(world, active_locations, LocationName.low_g_labyrinth_region,
low_g_labyrinth_region_locations)
krevice_kreepers_region_locations = {
LocationName.krevice_kreepers_flag : [0x673, 1],
LocationName.krevice_kreepers_bonus_1 : [0x673, 2],
LocationName.krevice_kreepers_bonus_2 : [0x673, 3],
LocationName.krevice_kreepers_dk : [0x673, 5],
}
if world.options.kongsanity:
krevice_kreepers_region_locations[LocationName.krevice_kreepers_kong] = []
krevice_kreepers_region = create_region(world, active_locations, LocationName.krevice_kreepers_region,
krevice_kreepers_region_locations)
tearaway_toboggan_region_locations = {
LocationName.tearaway_toboggan_flag : [0x65F, 1],
LocationName.tearaway_toboggan_bonus_1 : [0x65F, 2],
LocationName.tearaway_toboggan_bonus_2 : [0x65F, 3],
LocationName.tearaway_toboggan_dk : [0x65F, 5],
}
if world.options.kongsanity:
tearaway_toboggan_region_locations[LocationName.tearaway_toboggan_kong] = []
tearaway_toboggan_region = create_region(world, active_locations, LocationName.tearaway_toboggan_region,
tearaway_toboggan_region_locations)
barrel_drop_bounce_region_locations = {
LocationName.barrel_drop_bounce_flag : [0x66C, 1],
LocationName.barrel_drop_bounce_bonus_1 : [0x66C, 2],
LocationName.barrel_drop_bounce_bonus_2 : [0x66C, 3],
LocationName.barrel_drop_bounce_dk : [0x66C, 5],
}
if world.options.kongsanity:
barrel_drop_bounce_region_locations[LocationName.barrel_drop_bounce_kong] = []
barrel_drop_bounce_region = create_region(world, active_locations, LocationName.barrel_drop_bounce_region,
barrel_drop_bounce_region_locations)
krack_shot_kroc_region_locations = {
LocationName.krack_shot_kroc_flag : [0x66F, 1],
LocationName.krack_shot_kroc_bonus_1 : [0x66F, 2],
LocationName.krack_shot_kroc_bonus_2 : [0x66F, 3],
LocationName.krack_shot_kroc_dk : [0x66F, 5],
}
if world.options.kongsanity:
krack_shot_kroc_region_locations[LocationName.krack_shot_kroc_kong] = []
krack_shot_kroc_region = create_region(world, active_locations, LocationName.krack_shot_kroc_region,
krack_shot_kroc_region_locations)
lemguin_lunge_region_locations = {
LocationName.lemguin_lunge_flag : [0x65E, 1],
LocationName.lemguin_lunge_bonus_1 : [0x65E, 2],
LocationName.lemguin_lunge_bonus_2 : [0x65E, 3],
LocationName.lemguin_lunge_dk : [0x65E, 5],
}
if world.options.kongsanity:
lemguin_lunge_region_locations[LocationName.lemguin_lunge_kong] = []
lemguin_lunge_region = create_region(world, active_locations, LocationName.lemguin_lunge_region,
lemguin_lunge_region_locations)
buzzer_barrage_region_locations = {
LocationName.buzzer_barrage_flag : [0x676, 1],
LocationName.buzzer_barrage_bonus_1 : [0x676, 2],
LocationName.buzzer_barrage_bonus_2 : [0x676, 3],
LocationName.buzzer_barrage_dk : [0x676, 5],
}
if world.options.kongsanity:
buzzer_barrage_region_locations[LocationName.buzzer_barrage_kong] = []
buzzer_barrage_region = create_region(world, active_locations, LocationName.buzzer_barrage_region,
buzzer_barrage_region_locations)
kong_fused_cliffs_region_locations = {
LocationName.kong_fused_cliffs_flag : [0x674, 1],
LocationName.kong_fused_cliffs_bonus_1 : [0x674, 2],
LocationName.kong_fused_cliffs_bonus_2 : [0x674, 3],
LocationName.kong_fused_cliffs_dk : [0x674, 5],
}
if world.options.kongsanity:
kong_fused_cliffs_region_locations[LocationName.kong_fused_cliffs_kong] = []
kong_fused_cliffs_region = create_region(world, active_locations, LocationName.kong_fused_cliffs_region,
kong_fused_cliffs_region_locations)
floodlit_fish_region_locations = {
LocationName.floodlit_fish_flag : [0x669, 1],
LocationName.floodlit_fish_bonus_1 : [0x669, 2],
LocationName.floodlit_fish_bonus_2 : [0x669, 3],
LocationName.floodlit_fish_dk : [0x669, 5],
}
if world.options.kongsanity:
floodlit_fish_region_locations[LocationName.floodlit_fish_kong] = []
floodlit_fish_region = create_region(world, active_locations, LocationName.floodlit_fish_region,
floodlit_fish_region_locations)
pothole_panic_region_locations = {
LocationName.pothole_panic_flag : [0x677, 1],
LocationName.pothole_panic_bonus_1 : [0x677, 2],
LocationName.pothole_panic_bonus_2 : [0x677, 3],
LocationName.pothole_panic_dk : [0x677, 5],
}
if world.options.kongsanity:
pothole_panic_region_locations[LocationName.pothole_panic_kong] = []
pothole_panic_region = create_region(world, active_locations, LocationName.pothole_panic_region,
pothole_panic_region_locations)
ropey_rumpus_region_locations = {
LocationName.ropey_rumpus_flag : [0x675, 1],
LocationName.ropey_rumpus_bonus_1 : [0x675, 2],
LocationName.ropey_rumpus_bonus_2 : [0x675, 3],
LocationName.ropey_rumpus_dk : [0x675, 5],
}
if world.options.kongsanity:
ropey_rumpus_region_locations[LocationName.ropey_rumpus_kong] = []
ropey_rumpus_region = create_region(world, active_locations, LocationName.ropey_rumpus_region,
ropey_rumpus_region_locations)
konveyor_rope_clash_region_locations = {
LocationName.konveyor_rope_clash_flag : [0x657, 1],
LocationName.konveyor_rope_clash_bonus_1 : [0x657, 2],
LocationName.konveyor_rope_clash_bonus_2 : [0x657, 3],
LocationName.konveyor_rope_clash_dk : [0x657, 5],
}
if world.options.kongsanity:
konveyor_rope_clash_region_locations[LocationName.konveyor_rope_clash_kong] = []
konveyor_rope_clash_region = create_region(world, active_locations, LocationName.konveyor_rope_clash_region,
konveyor_rope_clash_region_locations)
creepy_caverns_region_locations = {
LocationName.creepy_caverns_flag : [0x678, 1],
LocationName.creepy_caverns_bonus_1 : [0x678, 2],
LocationName.creepy_caverns_bonus_2 : [0x678, 3],
LocationName.creepy_caverns_dk : [0x678, 5],
}
if world.options.kongsanity:
creepy_caverns_region_locations[LocationName.creepy_caverns_kong] = []
creepy_caverns_region = create_region(world, active_locations, LocationName.creepy_caverns_region,
creepy_caverns_region_locations)
lightning_lookout_region_locations = {
LocationName.lightning_lookout_flag : [0x665, 1],
LocationName.lightning_lookout_bonus_1 : [0x665, 2],
LocationName.lightning_lookout_bonus_2 : [0x665, 3],
LocationName.lightning_lookout_dk : [0x665, 5],
}
if world.options.kongsanity:
lightning_lookout_region_locations[LocationName.lightning_lookout_kong] = []
lightning_lookout_region = create_region(world, active_locations, LocationName.lightning_lookout_region,
lightning_lookout_region_locations)
koindozer_klamber_region_locations = {
LocationName.koindozer_klamber_flag : [0x679, 1],
LocationName.koindozer_klamber_bonus_1 : [0x679, 2],
LocationName.koindozer_klamber_bonus_2 : [0x679, 3],
LocationName.koindozer_klamber_dk : [0x679, 5],
}
if world.options.kongsanity:
koindozer_klamber_region_locations[LocationName.koindozer_klamber_kong] = []
koindozer_klamber_region = create_region(world, active_locations, LocationName.koindozer_klamber_region,
koindozer_klamber_region_locations)
poisonous_pipeline_region_locations = {
LocationName.poisonous_pipeline_flag : [0x671, 1],
LocationName.poisonous_pipeline_bonus_1 : [0x671, 2],
LocationName.poisonous_pipeline_bonus_2 : [0x671, 3],
LocationName.poisonous_pipeline_dk : [0x671, 5],
}
if world.options.kongsanity:
poisonous_pipeline_region_locations[LocationName.poisonous_pipeline_kong] = []
poisonous_pipeline_region = create_region(world, active_locations, LocationName.poisonous_pipeline_region,
poisonous_pipeline_region_locations)
stampede_sprint_region_locations = {
LocationName.stampede_sprint_flag : [0x67B, 1],
LocationName.stampede_sprint_bonus_1 : [0x67B, 2],
LocationName.stampede_sprint_bonus_2 : [0x67B, 3],
LocationName.stampede_sprint_bonus_3 : [0x67B, 4],
LocationName.stampede_sprint_dk : [0x67B, 5],
}
if world.options.kongsanity:
stampede_sprint_region_locations[LocationName.stampede_sprint_kong] = []
stampede_sprint_region = create_region(world, active_locations, LocationName.stampede_sprint_region,
stampede_sprint_region_locations)
criss_cross_cliffs_region_locations = {
LocationName.criss_cross_cliffs_flag : [0x67C, 1],
LocationName.criss_cross_cliffs_bonus_1 : [0x67C, 2],
LocationName.criss_cross_cliffs_bonus_2 : [0x67C, 3],
LocationName.criss_cross_cliffs_dk : [0x67C, 5],
}
if world.options.kongsanity:
criss_cross_cliffs_region_locations[LocationName.criss_cross_cliffs_kong] = []
criss_cross_cliffs_region = create_region(world, active_locations, LocationName.criss_cross_cliffs_region,
criss_cross_cliffs_region_locations)
tyrant_twin_tussle_region_locations = {
LocationName.tyrant_twin_tussle_flag : [0x67D, 1],
LocationName.tyrant_twin_tussle_bonus_1 : [0x67D, 2],
LocationName.tyrant_twin_tussle_bonus_2 : [0x67D, 3],
LocationName.tyrant_twin_tussle_bonus_3 : [0x67D, 4],
LocationName.tyrant_twin_tussle_dk : [0x67D, 5],
}
if world.options.kongsanity:
tyrant_twin_tussle_region_locations[LocationName.tyrant_twin_tussle_kong] = []
tyrant_twin_tussle_region = create_region(world, active_locations, LocationName.tyrant_twin_tussle_region,
tyrant_twin_tussle_region_locations)
swoopy_salvo_region_locations = {
LocationName.swoopy_salvo_flag : [0x663, 1],
LocationName.swoopy_salvo_bonus_1 : [0x663, 2],
LocationName.swoopy_salvo_bonus_2 : [0x663, 3],
LocationName.swoopy_salvo_bonus_3 : [0x663, 4],
LocationName.swoopy_salvo_dk : [0x663, 5],
}
if world.options.kongsanity:
swoopy_salvo_region_locations[LocationName.swoopy_salvo_kong] = []
swoopy_salvo_region = create_region(world, active_locations, LocationName.swoopy_salvo_region,
swoopy_salvo_region_locations)
rocket_rush_region_locations = {
LocationName.rocket_rush_flag : [0x67E, 1],
LocationName.rocket_rush_dk : [0x67E, 5],
}
rocket_rush_region = create_region(world, active_locations, LocationName.rocket_rush_region,
rocket_rush_region_locations)
belchas_barn_region_locations = {
LocationName.belchas_barn: [0x64F, 1],
}
belchas_barn_region = create_region(world, active_locations, LocationName.belchas_barn_region,
belchas_barn_region_locations)
arichs_ambush_region_locations = {
LocationName.arichs_ambush: [0x650, 1],
}
arichs_ambush_region = create_region(world, active_locations, LocationName.arichs_ambush_region,
arichs_ambush_region_locations)
squirts_showdown_region_locations = {
LocationName.squirts_showdown: [0x651, 1],
}
squirts_showdown_region = create_region(world, active_locations, LocationName.squirts_showdown_region,
squirts_showdown_region_locations)
kaos_karnage_region_locations = {
LocationName.kaos_karnage: [0x652, 1],
}
kaos_karnage_region = create_region(world, active_locations, LocationName.kaos_karnage_region,
kaos_karnage_region_locations)
bleaks_house_region_locations = {
LocationName.bleaks_house: [0x653, 1],
}
bleaks_house_region = create_region(world, active_locations, LocationName.bleaks_house_region,
bleaks_house_region_locations)
barboss_barrier_region_locations = {
LocationName.barboss_barrier: [0x654, 1],
}
barboss_barrier_region = create_region(world, active_locations, LocationName.barboss_barrier_region,
barboss_barrier_region_locations)
kastle_kaos_region_locations = {
LocationName.kastle_kaos: [0x655, 1],
}
kastle_kaos_region = create_region(world, active_locations, LocationName.kastle_kaos_region,
kastle_kaos_region_locations)
knautilus_region_locations = {
LocationName.knautilus: [0x656, 1],
}
knautilus_region = create_region(world, active_locations, LocationName.knautilus_region,
knautilus_region_locations)
belchas_burrow_region_locations = {
LocationName.belchas_burrow: [0x647, 1],
}
belchas_burrow_region = create_region(world, active_locations, LocationName.belchas_burrow_region,
belchas_burrow_region_locations)
kong_cave_region_locations = {
LocationName.kong_cave: [0x645, 1],
}
kong_cave_region = create_region(world, active_locations, LocationName.kong_cave_region,
kong_cave_region_locations)
undercover_cove_region_locations = {
LocationName.undercover_cove: [0x644, 1],
}
undercover_cove_region = create_region(world, active_locations, LocationName.undercover_cove_region,
undercover_cove_region_locations)
ks_cache_region_locations = {
LocationName.ks_cache: [0x642, 1],
}
ks_cache_region = create_region(world, active_locations, LocationName.ks_cache_region,
ks_cache_region_locations)
hill_top_hoard_region_locations = {
LocationName.hill_top_hoard: [0x643, 1],
}
hill_top_hoard_region = create_region(world, active_locations, LocationName.hill_top_hoard_region,
hill_top_hoard_region_locations)
bounty_beach_region_locations = {
LocationName.bounty_beach: [0x646, 1],
}
bounty_beach_region = create_region(world, active_locations, LocationName.bounty_beach_region,
bounty_beach_region_locations)
smugglers_cove_region_locations = {
LocationName.smugglers_cove: [0x648, 1],
}
smugglers_cove_region = create_region(world, active_locations, LocationName.smugglers_cove_region,
smugglers_cove_region_locations)
arichs_hoard_region_locations = {
LocationName.arichs_hoard: [0x649, 1],
}
arichs_hoard_region = create_region(world, active_locations, LocationName.arichs_hoard_region,
arichs_hoard_region_locations)
bounty_bay_region_locations = {
LocationName.bounty_bay: [0x64A, 1],
}
bounty_bay_region = create_region(world, active_locations, LocationName.bounty_bay_region,
bounty_bay_region_locations)
sky_high_secret_region_locations = {}
if False:#world.options.include_trade_sequence:
sky_high_secret_region_locations[LocationName.sky_high_secret] = [0x64B, 1]
sky_high_secret_region = create_region(world, active_locations, LocationName.sky_high_secret_region,
sky_high_secret_region_locations)
glacial_grotto_region_locations = {
LocationName.glacial_grotto: [0x64C, 1],
}
glacial_grotto_region = create_region(world, active_locations, LocationName.glacial_grotto_region,
glacial_grotto_region_locations)
cifftop_cache_region_locations = {}
if False:#world.options.include_trade_sequence:
cifftop_cache_region_locations[LocationName.cifftop_cache] = [0x64D, 1]
cifftop_cache_region = create_region(world, active_locations, LocationName.cifftop_cache_region,
cifftop_cache_region_locations)
sewer_stockpile_region_locations = {
LocationName.sewer_stockpile: [0x64E, 1],
}
sewer_stockpile_region = create_region(world, active_locations, LocationName.sewer_stockpile_region,
sewer_stockpile_region_locations)
# Set up the regions correctly.
world.multiworld.regions += [
menu_region,
overworld_1_region,
overworld_2_region,
overworld_3_region,
overworld_4_region,
lake_orangatanga_region,
kremwood_forest_region,
cotton_top_cove_region,
mekanos_region,
k3_region,
razor_ridge_region,
kaos_kore_region,
krematoa_region,
lakeside_limbo_region,
doorstop_dash_region,
tidal_trouble_region,
skiddas_row_region,
murky_mill_region,
barrel_shield_bust_up_region,
riverside_race_region,
squeals_on_wheels_region,
springin_spiders_region,
bobbing_barrel_brawl_region,
bazzas_blockade_region,
rocket_barrel_ride_region,
kreeping_klasps_region,
tracker_barrel_trek_region,
fish_food_frenzy_region,
fire_ball_frenzy_region,
demolition_drain_pipe_region,
ripsaw_rage_region,
blazing_bazookas_region,
low_g_labyrinth_region,
krevice_kreepers_region,
tearaway_toboggan_region,
barrel_drop_bounce_region,
krack_shot_kroc_region,
lemguin_lunge_region,
buzzer_barrage_region,
kong_fused_cliffs_region,
floodlit_fish_region,
pothole_panic_region,
ropey_rumpus_region,
konveyor_rope_clash_region,
creepy_caverns_region,
lightning_lookout_region,
koindozer_klamber_region,
poisonous_pipeline_region,
stampede_sprint_region,
criss_cross_cliffs_region,
tyrant_twin_tussle_region,
swoopy_salvo_region,
rocket_rush_region,
belchas_barn_region,
arichs_ambush_region,
squirts_showdown_region,
kaos_karnage_region,
bleaks_house_region,
barboss_barrier_region,
kastle_kaos_region,
knautilus_region,
belchas_burrow_region,
kong_cave_region,
undercover_cove_region,
ks_cache_region,
hill_top_hoard_region,
bounty_beach_region,
smugglers_cove_region,
arichs_hoard_region,
bounty_bay_region,
sky_high_secret_region,
glacial_grotto_region,
cifftop_cache_region,
sewer_stockpile_region,
]
bazaar_region_locations = {}
bramble_region_locations = {}
flower_spot_region_locations = {}
barter_region_locations = {}
barnacle_region_locations = {}
blue_region_locations = {}
blizzard_region_locations = {}
if False:#world.options.include_trade_sequence:
bazaar_region_locations.update({
LocationName.bazaars_general_store_1: [0x615, 2, True],
LocationName.bazaars_general_store_2: [0x615, 3, True],
})
bramble_region_locations[LocationName.brambles_bungalow] = [0x619, 2]
#flower_spot_region_locations.update({
# LocationName.flower_spot: [0x615, 3, True],
#})
barter_region_locations[LocationName.barters_swap_shop] = [0x61B, 3]
barnacle_region_locations[LocationName.barnacles_island] = [0x61D, 2]
blue_region_locations[LocationName.blues_beach_hut] = [0x621, 4]
blizzard_region_locations[LocationName.blizzards_basecamp] = [0x625, 4, True]
bazaar_region = create_region(world, active_locations, LocationName.bazaar_region, bazaar_region_locations)
bramble_region = create_region(world, active_locations, LocationName.bramble_region,
bramble_region_locations)
flower_spot_region = create_region(world, active_locations, LocationName.flower_spot_region,
flower_spot_region_locations)
barter_region = create_region(world, active_locations, LocationName.barter_region, barter_region_locations)
barnacle_region = create_region(world, active_locations, LocationName.barnacle_region,
barnacle_region_locations)
blue_region = create_region(world, active_locations, LocationName.blue_region, blue_region_locations)
blizzard_region = create_region(world, active_locations, LocationName.blizzard_region,
blizzard_region_locations)
world.multiworld.regions += [
bazaar_region,
bramble_region,
flower_spot_region,
barter_region,
barnacle_region,
blue_region,
blizzard_region,
]
def connect_regions(world: World, level_list):
names: typing.Dict[str, int] = {}
# Overworld
connect(world, world.player, names, 'Menu', LocationName.overworld_1_region)
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_2_region,
lambda state: (state.has(ItemName.progressive_boat, world.player, 1)))
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.overworld_3_region,
lambda state: (state.has(ItemName.progressive_boat, world.player, 3)))
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_4_region,
lambda state: (state.has(ItemName.dk_coin, world.player, world.options.dk_coins_for_gyrocopter.value) and
state.has(ItemName.progressive_boat, world.player, 3)))
# World Connections
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region)
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region)
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region)
connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bazaar_region)
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region)
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.mekanos_region)
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.kong_cave_region)
connect(world, world.player, names, LocationName.overworld_2_region, LocationName.bramble_region)
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.k3_region)
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region)
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region)
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.krematoa_region)
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region)
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.flower_spot_region)
connect(world, world.player, names, LocationName.overworld_3_region, LocationName.barter_region)
connect(world, world.player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region)
connect(world, world.player, names, LocationName.overworld_4_region, LocationName.ks_cache_region)
connect(world, world.player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region)
# Lake Orangatanga Connections
lake_orangatanga_levels = [
level_list[0],
level_list[1],
level_list[2],
level_list[3],
level_list[4],
LocationName.belchas_barn_region,
LocationName.barnacle_region,
LocationName.smugglers_cove_region,
]
for i in range(0, len(lake_orangatanga_levels)):
connect(world, world.player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i])
# Kremwood Forest Connections
kremwood_forest_levels = [
level_list[5],
level_list[6],
level_list[7],
level_list[8],
level_list[9],
LocationName.arichs_ambush_region,
LocationName.arichs_hoard_region,
]
for i in range(0, len(kremwood_forest_levels) - 1):
connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i])
connection = connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1],
lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", world.player)))
world.multiworld.register_indirect_condition(world.get_location(LocationName.riverside_race_flag).parent_region,
connection)
# Cotton-Top Cove Connections
cotton_top_cove_levels = [
LocationName.blue_region,
level_list[10],
level_list[11],
level_list[12],
level_list[13],
level_list[14],
LocationName.squirts_showdown_region,
LocationName.bounty_bay_region,
]
for i in range(0, len(cotton_top_cove_levels)):
connect(world, world.player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i])
# Mekanos Connections
mekanos_levels = [
level_list[15],
level_list[16],
level_list[17],
level_list[18],
level_list[19],
LocationName.kaos_karnage_region,
]
for i in range(0, len(mekanos_levels)):
connect(world, world.player, names, LocationName.mekanos_region, mekanos_levels[i])
if False:#world.options.include_trade_sequence:
connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region,
lambda state: (state.has(ItemName.bowling_ball, world.player, 1)))
else:
connection = connect(world, world.player, names, LocationName.mekanos_region,
LocationName.sky_high_secret_region,
lambda state: (state.can_reach(LocationName.bleaks_house, "Location", world.player)))
world.multiworld.register_indirect_condition(world.get_location(LocationName.bleaks_house).parent_region,
connection)
# K3 Connections
k3_levels = [
level_list[20],
level_list[21],
level_list[22],
level_list[23],
level_list[24],
LocationName.bleaks_house_region,
LocationName.blizzard_region,
LocationName.glacial_grotto_region,
]
for i in range(0, len(k3_levels)):
connect(world, world.player, names, LocationName.k3_region, k3_levels[i])
# Razor Ridge Connections
razor_ridge_levels = [
level_list[25],
level_list[26],
level_list[27],
level_list[28],
level_list[29],
LocationName.barboss_barrier_region,
]
for i in range(0, len(razor_ridge_levels)):
connect(world, world.player, names, LocationName.razor_ridge_region, razor_ridge_levels[i])
if False:#world.options.include_trade_sequence:
connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region,
lambda state: (state.has(ItemName.wrench, world.player, 1)))
else:
connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region)
# KAOS Kore Connections
kaos_kore_levels = [
level_list[30],
level_list[31],
level_list[32],
level_list[33],
level_list[34],
LocationName.sewer_stockpile_region,
]
for i in range(0, len(kaos_kore_levels)):
connect(world, world.player, names, LocationName.kaos_kore_region, kaos_kore_levels[i])
# Krematoa Connections
krematoa_levels = [
level_list[35],
level_list[36],
level_list[37],
level_list[38],
LocationName.rocket_rush_region,
]
for i in range(0, len(krematoa_levels)):
connect(world, world.player, names, LocationName.krematoa_region, krematoa_levels[i],
lambda state, i=i: (state.has(ItemName.bonus_coin, world.player, world.options.krematoa_bonus_coin_cost.value * (i+1))))
if world.options.goal == "knautilus":
connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.knautilus_region)
connect(world, world.player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region,
lambda state: (state.has(ItemName.krematoa_cog, world.player, 5)))
else:
connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region)
connect(world, world.player, names, LocationName.krematoa_region, LocationName.knautilus_region,
lambda state: (state.has(ItemName.krematoa_cog, world.player, 5)))
def create_region(world: World, active_locations, name: str, locations=None):
# Shamelessly stolen from the ROR2 definition
ret = Region(name, world.player, world.multiworld)
if locations:
for locationName, locationData in locations.items():
loc_id = active_locations.get(locationName, 0)
if loc_id:
loc_byte = locationData[0] if (len(locationData) > 0) else 0
loc_bit = locationData[1] if (len(locationData) > 1) else 0
loc_invert = locationData[2] if (len(locationData) > 2) else False
location = DKC3Location(world.player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert)
ret.locations.append(location)
return ret
def connect(world: World, player: int, used_names: typing.Dict[str, int], source: str, target: str,
rule: typing.Optional[typing.Callable] = None):
source_region = world.multiworld.get_region(source, player)
target_region = world.multiworld.get_region(target, player)
if target not in used_names:
used_names[target] = 1
name = target
else:
used_names[target] += 1
name = target + (' ' * used_names[target])
connection = Entrance(player, name, source_region)
if rule:
connection.access_rule = rule
source_region.exits.append(connection)
connection.connect(target_region)
return connection

744
worlds/dkc3/Rom.py Normal file
View File

@@ -0,0 +1,744 @@
import Utils
from Utils import read_snes_rom
from worlds.AutoWorld import World
from worlds.Files import APDeltaPatch
from .Levels import level_list, level_dict
USHASH = '120abf304f0c40fe059f6a192ed4f947'
ROM_PLAYER_LIMIT = 65535
import hashlib
import os
import math
level_unlock_map = {
0x657: [0x65A],
0x65A: [0x680, 0x639, 0x659],
0x659: [0x65D],
0x65D: [0x65C],
0x65C: [0x688, 0x64F],
0x662: [0x681, 0x664],
0x664: [0x65B],
0x65B: [0x689, 0x661],
0x661: [0x63A, 0x666],
0x666: [0x650, 0x649],
0x667: [0x66A],
0x66A: [0x682, 0x658],
0x658: [0x68A, 0x66B],
0x66B: [0x668],
0x668: [0x651],
0x66D: [0x63C, 0x672],
0x672: [0x68B, 0x660],
0x660: [0x683, 0x66E],
0x66E: [0x670],
0x670: [0x652],
0x673: [0x684, 0x65F],
0x65F: [0x66C],
0x66C: [0x66F],
0x66F: [0x65E],
0x65E: [0x63D, 0x653, 0x68C, 0x64C],
0x676: [0x63E, 0x674, 0x685],
0x674: [0x63F, 0x669],
0x669: [0x677],
0x677: [0x68D, 0x675],
0x675: [0x654],
0x67A: [0x640, 0x678],
0x678: [0x665],
0x665: [0x686, 0x679],
0x679: [0x68E, 0x671],
0x67B: [0x67C],
0x67C: [0x67D],
0x67D: [0x663],
0x663: [0x67E],
}
location_rom_data = {
0xDC3000: [0x657, 1], # Lakeside Limbo
0xDC3001: [0x657, 2],
0xDC3002: [0x657, 3],
0xDC3003: [0x657, 5],
0xDC3100: [0x657, 7],
0xDC3004: [0x65A, 1], # Doorstop Dash
0xDC3005: [0x65A, 2],
0xDC3006: [0x65A, 3],
0xDC3007: [0x65A, 5],
0xDC3104: [0x65A, 7],
0xDC3008: [0x659, 1], # Tidal Trouble
0xDC3009: [0x659, 2],
0xDC300A: [0x659, 3],
0xDC300B: [0x659, 5],
0xDC3108: [0x659, 7],
0xDC300C: [0x65D, 1], # Skidda's Row
0xDC300D: [0x65D, 2],
0xDC300E: [0x65D, 3],
0xDC300F: [0x65D, 5],
0xDC310C: [0x65D, 7],
0xDC3010: [0x65C, 1], # Murky Mill
0xDC3011: [0x65C, 2],
0xDC3012: [0x65C, 3],
0xDC3013: [0x65C, 5],
0xDC3110: [0x65C, 7],
0xDC3014: [0x662, 1], # Barrel Shield Bust-Up
0xDC3015: [0x662, 2],
0xDC3016: [0x662, 3],
0xDC3017: [0x662, 5],
0xDC3114: [0x662, 7],
0xDC3018: [0x664, 1], # Riverside Race
0xDC3019: [0x664, 2],
0xDC301A: [0x664, 3],
0xDC301B: [0x664, 5],
0xDC3118: [0x664, 7],
0xDC301C: [0x65B, 1], # Squeals on Wheels
0xDC301D: [0x65B, 2],
0xDC301E: [0x65B, 3],
0xDC301F: [0x65B, 5],
0xDC311C: [0x65B, 7],
0xDC3020: [0x661, 1], # Springin' Spiders
0xDC3021: [0x661, 2],
0xDC3022: [0x661, 3],
0xDC3023: [0x661, 5],
0xDC3120: [0x661, 7],
0xDC3024: [0x666, 1], # Bobbing Barrel Brawl
0xDC3025: [0x666, 2],
0xDC3026: [0x666, 3],
0xDC3027: [0x666, 5],
0xDC3124: [0x666, 7],
0xDC3028: [0x667, 1], # Bazza's Blockade
0xDC3029: [0x667, 2],
0xDC302A: [0x667, 3],
0xDC302B: [0x667, 5],
0xDC3128: [0x667, 7],
0xDC302C: [0x66A, 1], # Rocket Barrel Ride
0xDC302D: [0x66A, 2],
0xDC302E: [0x66A, 3],
0xDC302F: [0x66A, 5],
0xDC312C: [0x66A, 7],
0xDC3030: [0x658, 1], # Kreeping Klasps
0xDC3031: [0x658, 2],
0xDC3032: [0x658, 3],
0xDC3033: [0x658, 5],
0xDC3130: [0x658, 7],
0xDC3034: [0x66B, 1], # Tracker Barrel Trek
0xDC3035: [0x66B, 2],
0xDC3036: [0x66B, 3],
0xDC3037: [0x66B, 5],
0xDC3134: [0x66B, 7],
0xDC3038: [0x668, 1], # Fish Food Frenzy
0xDC3039: [0x668, 2],
0xDC303A: [0x668, 3],
0xDC303B: [0x668, 5],
0xDC3138: [0x668, 7],
0xDC303C: [0x66D, 1], # Fire-ball Frenzy
0xDC303D: [0x66D, 2],
0xDC303E: [0x66D, 3],
0xDC303F: [0x66D, 5],
0xDC313C: [0x66D, 7],
0xDC3040: [0x672, 1], # Demolition Drainpipe
0xDC3041: [0x672, 2],
0xDC3042: [0x672, 3],
0xDC3043: [0x672, 5],
0xDC3140: [0x672, 7],
0xDC3044: [0x660, 1], # Ripsaw Rage
0xDC3045: [0x660, 2],
0xDC3046: [0x660, 3],
0xDC3047: [0x660, 5],
0xDC3144: [0x660, 7],
0xDC3048: [0x66E, 1], # Blazing Bazukas
0xDC3049: [0x66E, 2],
0xDC304A: [0x66E, 3],
0xDC304B: [0x66E, 5],
0xDC3148: [0x66E, 7],
0xDC304C: [0x670, 1], # Low-G Labyrinth
0xDC304D: [0x670, 2],
0xDC304E: [0x670, 3],
0xDC304F: [0x670, 5],
0xDC314C: [0x670, 7],
0xDC3050: [0x673, 1], # Krevice Kreepers
0xDC3051: [0x673, 2],
0xDC3052: [0x673, 3],
0xDC3053: [0x673, 5],
0xDC3150: [0x673, 7],
0xDC3054: [0x65F, 1], # Tearaway Toboggan
0xDC3055: [0x65F, 2],
0xDC3056: [0x65F, 3],
0xDC3057: [0x65F, 5],
0xDC3154: [0x65F, 7],
0xDC3058: [0x66C, 1], # Barrel Drop Bounce
0xDC3059: [0x66C, 2],
0xDC305A: [0x66C, 3],
0xDC305B: [0x66C, 5],
0xDC3158: [0x66C, 7],
0xDC305C: [0x66F, 1], # Krack-Shot Kroc
0xDC305D: [0x66F, 2],
0xDC305E: [0x66F, 3],
0xDC305F: [0x66F, 5],
0xDC315C: [0x66F, 7],
0xDC3060: [0x65E, 1], # Lemguin Lunge
0xDC3061: [0x65E, 2],
0xDC3062: [0x65E, 3],
0xDC3063: [0x65E, 5],
0xDC3160: [0x65E, 7],
0xDC3064: [0x676, 1], # Buzzer Barrage
0xDC3065: [0x676, 2],
0xDC3066: [0x676, 3],
0xDC3067: [0x676, 5],
0xDC3164: [0x676, 7],
0xDC3068: [0x674, 1], # Kong-Fused Cliffs
0xDC3069: [0x674, 2],
0xDC306A: [0x674, 3],
0xDC306B: [0x674, 5],
0xDC3168: [0x674, 7],
0xDC306C: [0x669, 1], # Floodlit Fish
0xDC306D: [0x669, 2],
0xDC306E: [0x669, 3],
0xDC306F: [0x669, 5],
0xDC316C: [0x669, 7],
0xDC3070: [0x677, 1], # Pothole Panic
0xDC3071: [0x677, 2],
0xDC3072: [0x677, 3],
0xDC3073: [0x677, 5],
0xDC3170: [0x677, 7],
0xDC3074: [0x675, 1], # Ropey Rumpus
0xDC3075: [0x675, 2],
0xDC3076: [0x675, 3],
0xDC3077: [0x675, 5],
0xDC3174: [0x675, 7],
0xDC3078: [0x67A, 1], # Konveyor Rope Klash
0xDC3079: [0x67A, 2],
0xDC307A: [0x67A, 3],
0xDC307B: [0x67A, 5],
0xDC3178: [0x67A, 7],
0xDC307C: [0x678, 1], # Creepy Caverns
0xDC307D: [0x678, 2],
0xDC307E: [0x678, 3],
0xDC307F: [0x678, 5],
0xDC317C: [0x678, 7],
0xDC3080: [0x665, 1], # Lightning Lookout
0xDC3081: [0x665, 2],
0xDC3082: [0x665, 3],
0xDC3083: [0x665, 5],
0xDC3180: [0x665, 7],
0xDC3084: [0x679, 1], # Koindozer Klamber
0xDC3085: [0x679, 2],
0xDC3086: [0x679, 3],
0xDC3087: [0x679, 5],
0xDC3184: [0x679, 7],
0xDC3088: [0x671, 1], # Poisonous Pipeline
0xDC3089: [0x671, 2],
0xDC308A: [0x671, 3],
0xDC308B: [0x671, 5],
0xDC3188: [0x671, 7],
0xDC308C: [0x67B, 1], # Stampede Sprint
0xDC308D: [0x67B, 2],
0xDC308E: [0x67B, 3],
0xDC308F: [0x67B, 4],
0xDC3090: [0x67B, 5],
0xDC318C: [0x67B, 7],
0xDC3091: [0x67C, 1], # Criss Kross Cliffs
0xDC3092: [0x67C, 2],
0xDC3093: [0x67C, 3],
0xDC3094: [0x67C, 5],
0xDC3191: [0x67C, 7],
0xDC3095: [0x67D, 1], # Tyrant Twin Tussle
0xDC3096: [0x67D, 2],
0xDC3097: [0x67D, 3],
0xDC3098: [0x67D, 4],
0xDC3099: [0x67D, 5],
0xDC3195: [0x67D, 7],
0xDC309A: [0x663, 1], # Swoopy Salvo
0xDC309B: [0x663, 2],
0xDC309C: [0x663, 3],
0xDC309D: [0x663, 4],
0xDC309E: [0x663, 5],
0xDC319A: [0x663, 7],
0xDC309F: [0x67E, 1], # Rocket Rush
0xDC30A0: [0x67E, 5],
0xDC30A1: [0x64F, 1], # Bosses
0xDC30A2: [0x650, 1],
0xDC30A3: [0x651, 1],
0xDC30A4: [0x652, 1],
0xDC30A5: [0x653, 1],
0xDC30A6: [0x654, 1],
0xDC30A7: [0x655, 1],
0xDC30A8: [0x656, 1],
0xDC30A9: [0x647, 1], # Banana Bird Caves
0xDC30AA: [0x645, 1],
0xDC30AB: [0x644, 1],
0xDC30AC: [0x642, 1],
0xDC30AD: [0x643, 1],
0xDC30AE: [0x646, 1],
0xDC30AF: [0x648, 1],
0xDC30B0: [0x649, 1],
0xDC30B1: [0x64A, 1],
#0xDC30B2: [0x64B, 1], # Disabled until Trade Sequence
0xDC30B3: [0x64C, 1],
#0xDC30B4: [0x64D, 1], # Disabled until Trade Sequence
0xDC30B5: [0x64E, 1],
0xDC30B6: [0x5FE, 4], # Banana Bird Mother
# DKC3_TODO: Disabled until Trade Sequence
#0xDC30B7: [0x615, 2, True],
#0xDC30B8: [0x615, 3, True],
#0xDC30B9: [0x619, 2],
##0xDC30BA:
#0xDC30BB: [0x61B, 3],
#0xDC30BC: [0x61D, 2],
#0xDC30BD: [0x621, 4],
#0xDC30BE: [0x625, 4, True],
}
boss_location_ids = [
0xDC30A1,
0xDC30A2,
0xDC30A3,
0xDC30A4,
0xDC30A5,
0xDC30A6,
0xDC30A7,
0xDC30A8,
0xDC30B6,
]
item_rom_data = {
0xDC3001: [0x5D5], # 1-Up Balloon
0xDC3002: [0x5C9], # Bear Coin
0xDC3003: [0x5CB], # Bonus Coin
0xDC3004: [0x5CF], # DK Coin
0xDC3005: [0x5CD], # Banana Bird
0xDC3006: [0x5D1, 0x603], # Cog
}
music_rom_data = [
0x3D06B1,
0x3D0753,
0x3D071D,
0x3D07FA,
0x3D07C4,
0x3D08FE,
0x3D096C,
0x3D078E,
0x3D08CD,
0x3D09DD,
0x3D0A0E,
0x3D0AB3,
0x3D06E7,
0x3D0AE4,
0x3D0A45,
0x3D0B46,
0x3D0C40,
0x3D0897,
0x3D0B77,
0x3D0BD9,
0x3D0C71,
0x3D0866,
0x3D0B15,
0x3D0BA8,
0x3D0830,
0x3D0D04,
0x3D0CA2,
0x3D0A7C,
0x3D0D35,
0x3D0CD3,
0x3D0DC8,
0x3D0D66,
0x3D09AC,
0x3D0D97,
0x3D0C0F,
0x3D0DF9,
0x3D0E31,
0x3D0E62,
0x3D0934,
0x3D0E9A,
]
level_music_ids = [
0x06,
0x07,
0x08,
0x0A,
0x0B,
0x0E,
0x0F,
0x10,
0x17,
0x19,
0x1C,
0x1D,
0x1E,
0x21,
]
class LocalRom:
def __init__(self, file, name=None, hash=None):
self.name = name
self.hash = hash
self.orig_buffer = None
with open(file, 'rb') as stream:
self.buffer = read_snes_rom(stream)
#if patch:
# self.patch_rom()
# self.orig_buffer = self.buffer.copy()
#if vanillaRom:
# with open(vanillaRom, 'rb') as vanillaStream:
# self.orig_buffer = read_snes_rom(vanillaStream)
def read_bit(self, address: int, bit_number: int) -> bool:
bitflag = (1 << bit_number)
return ((self.buffer[address] & bitflag) != 0)
def read_byte(self, address: int) -> int:
return self.buffer[address]
def read_bytes(self, startaddress: int, length: int) -> bytearray:
return self.buffer[startaddress:startaddress + length]
def write_byte(self, address: int, value: int):
self.buffer[address] = value
def write_bytes(self, startaddress: int, values):
self.buffer[startaddress:startaddress + len(values)] = values
def write_to_file(self, file):
with open(file, 'wb') as outfile:
outfile.write(self.buffer)
def read_from_file(self, file):
with open(file, 'rb') as stream:
self.buffer = bytearray(stream.read())
def patch_rom(world: World, rom: LocalRom, active_level_list):
# Boomer Costs
bonus_coin_cost = world.options.krematoa_bonus_coin_cost
inverted_bonus_coin_cost = 0x100 - bonus_coin_cost
rom.write_byte(0x3498B9, inverted_bonus_coin_cost)
rom.write_byte(0x3498BA, inverted_bonus_coin_cost)
rom.write_byte(0x3498BB, inverted_bonus_coin_cost)
rom.write_byte(0x3498BC, inverted_bonus_coin_cost)
rom.write_byte(0x3498BD, inverted_bonus_coin_cost)
rom.write_byte(0x349857, bonus_coin_cost)
rom.write_byte(0x349862, bonus_coin_cost)
# Gyrocopter Costs
dk_coin_cost = world.options.dk_coins_for_gyrocopter
rom.write_byte(0x3484A6, dk_coin_cost)
rom.write_byte(0x3484D5, dk_coin_cost)
rom.write_byte(0x3484D7, 0x90)
rom.write_byte(0x3484DC, 0xEA)
rom.write_byte(0x3484DD, 0xEA)
rom.write_byte(0x3484DE, 0xEA)
rom.write_byte(0x348528, 0x80) # Prevent Single-Ski Lock
# Make Swanky free
rom.write_byte(0x348C48, 0x00)
rom.write_bytes(0x34AB70, bytearray([0xEA, 0xEA]))
rom.write_bytes(0x34ABF7, bytearray([0xEA, 0xEA]))
rom.write_bytes(0x34ACD0, bytearray([0xEA, 0xEA]))
# Banana Bird Costs
if world.options.goal == "banana_bird_hunt":
banana_bird_cost = math.floor(world.options.number_of_banana_birds * world.options.percentage_of_banana_birds / 100.0)
rom.write_byte(0x34AB85, banana_bird_cost)
rom.write_byte(0x329FD8, banana_bird_cost)
rom.write_byte(0x32A025, banana_bird_cost)
rom.write_byte(0x329FDA, 0xB0)
else:
# rom.write_byte(0x34AB84, 0x20) # These cause hangs at Wrinkly's
# rom.write_byte(0x329FD8, 0x20)
# rom.write_byte(0x32A025, 0x20)
rom.write_byte(0x329FDA, 0xB0)
# Baffle Mirror Fix
rom.write_byte(0x9133, 0x08)
rom.write_byte(0x9135, 0x0C)
rom.write_byte(0x9136, 0x2B)
rom.write_byte(0x9137, 0x06)
# Palette Swap
rom.write_byte(0x3B96A5, 0xD0)
if world.options.kong_palette_swap == "default":
rom.write_byte(0x3B96A9, 0x00)
rom.write_byte(0x3B96A8, 0x00)
elif world.options.kong_palette_swap == "purple":
rom.write_byte(0x3B96A9, 0x00)
rom.write_byte(0x3B96A8, 0x3C)
elif world.options.kong_palette_swap == "spooky":
rom.write_byte(0x3B96A9, 0x00)
rom.write_byte(0x3B96A8, 0xA0)
elif world.options.kong_palette_swap == "dark":
rom.write_byte(0x3B96A9, 0x05)
rom.write_byte(0x3B96A8, 0xA0)
elif world.options.kong_palette_swap == "chocolate":
rom.write_byte(0x3B96A9, 0x1D)
rom.write_byte(0x3B96A8, 0xA0)
elif world.options.kong_palette_swap == "shadow":
rom.write_byte(0x3B96A9, 0x45)
rom.write_byte(0x3B96A8, 0xA0)
elif world.options.kong_palette_swap == "red_gold":
rom.write_byte(0x3B96A9, 0x5D)
rom.write_byte(0x3B96A8, 0xA0)
elif world.options.kong_palette_swap == "gbc":
rom.write_byte(0x3B96A9, 0x20)
rom.write_byte(0x3B96A8, 0x3C)
elif world.options.kong_palette_swap == "halloween":
rom.write_byte(0x3B96A9, 0x70)
rom.write_byte(0x3B96A8, 0x3C)
if world.options.music_shuffle:
for address in music_rom_data:
rand_song = world.random.choice(level_music_ids)
rom.write_byte(address, rand_song)
# Starting Lives
rom.write_byte(0x9130, world.options.starting_life_count.value)
rom.write_byte(0x913B, world.options.starting_life_count.value)
# Cheat options
cheat_bytes = [0x00, 0x00]
if world.options.merry:
cheat_bytes[0] |= 0x01
if world.options.autosave:
cheat_bytes[0] |= 0x02
if world.options.difficulty == "tufst":
cheat_bytes[0] |= 0x80
cheat_bytes[1] |= 0x80
elif world.options.difficulty == "hardr":
cheat_bytes[0] |= 0x00
cheat_bytes[1] |= 0x00
elif world.options.difficulty == "norml":
cheat_bytes[1] |= 0x40
rom.write_bytes(0x8303, bytearray(cheat_bytes))
# Handle Level Shuffle Here
if world.options.level_shuffle:
for i in range(len(active_level_list)):
rom.write_byte(level_dict[level_list[i]].nameIDAddress, level_dict[active_level_list[i]].nameID)
rom.write_byte(level_dict[level_list[i]].levelIDAddress, level_dict[active_level_list[i]].levelID)
rom.write_byte(0x3FF800 + level_dict[active_level_list[i]].levelID, level_dict[level_list[i]].levelID)
rom.write_byte(0x3FF860 + level_dict[level_list[i]].levelID, level_dict[active_level_list[i]].levelID)
# First levels of each world
rom.write_byte(0x34BC3E, (0x32 + level_dict[active_level_list[0]].levelID))
rom.write_byte(0x34BC47, (0x32 + level_dict[active_level_list[5]].levelID))
rom.write_byte(0x34BC4A, (0x32 + level_dict[active_level_list[10]].levelID))
rom.write_byte(0x34BC53, (0x32 + level_dict[active_level_list[15]].levelID))
rom.write_byte(0x34BC59, (0x32 + level_dict[active_level_list[20]].levelID))
rom.write_byte(0x34BC5C, (0x32 + level_dict[active_level_list[25]].levelID))
rom.write_byte(0x34BC65, (0x32 + level_dict[active_level_list[30]].levelID))
rom.write_byte(0x34BC6E, (0x32 + level_dict[active_level_list[35]].levelID))
# Cotton-Top Cove Boss Unlock
rom.write_byte(0x34C02A, (0x32 + level_dict[active_level_list[14]].levelID))
# Kong-Fused Cliffs Unlock
rom.write_byte(0x34C213, (0x32 + level_dict[active_level_list[25]].levelID))
rom.write_byte(0x34C21B, (0x32 + level_dict[active_level_list[26]].levelID))
if world.options.goal == "knautilus":
# Swap Kastle KAOS and Knautilus
rom.write_byte(0x34D4E1, 0xC2)
rom.write_byte(0x34D4E2, 0x24)
rom.write_byte(0x34D551, 0xBA)
rom.write_byte(0x34D552, 0x23)
rom.write_byte(0x32F339, 0x55)
# Handle KONGsanity Here
if world.options.kongsanity:
# Arich's Hoard KONGsanity fix
rom.write_bytes(0x34BA8C, bytearray([0xEA, 0xEA]))
# Don't hide the level flag if the 0x80 bit is set
rom.write_bytes(0x34CE92, bytearray([0x80]))
# Use the `!` next to level name for indicating KONG letters
rom.write_bytes(0x34B8F0, bytearray([0x80]))
rom.write_bytes(0x34B8F3, bytearray([0x80]))
# Hijack to code to set the 0x80 flag for the level when you complete KONG
rom.write_bytes(0x3BCD4B, bytearray([0x22, 0x80, 0xFA, 0XB8])) # JSL $B8FA80
rom.write_bytes(0x38FA80, bytearray([0xDA])) # PHX
rom.write_bytes(0x38FA81, bytearray([0x48])) # PHA
rom.write_bytes(0x38FA82, bytearray([0x08])) # PHP
rom.write_bytes(0x38FA83, bytearray([0xE2, 0x20])) # SEP #20
rom.write_bytes(0x38FA85, bytearray([0x48])) # PHA
rom.write_bytes(0x38FA86, bytearray([0x18])) # CLC
rom.write_bytes(0x38FA87, bytearray([0x6D, 0xD3, 0x18])) # ADC $18D3
rom.write_bytes(0x38FA8A, bytearray([0x8D, 0xD3, 0x18])) # STA $18D3
rom.write_bytes(0x38FA8D, bytearray([0x68])) # PLA
rom.write_bytes(0x38FA8E, bytearray([0xC2, 0x20])) # REP 20
rom.write_bytes(0x38FA90, bytearray([0X18])) # CLC
rom.write_bytes(0x38FA91, bytearray([0x6D, 0xD5, 0x05])) # ADC $05D5
rom.write_bytes(0x38FA94, bytearray([0x8D, 0xD5, 0x05])) # STA $05D5
rom.write_bytes(0x38FA97, bytearray([0xAE, 0xB9, 0x05])) # LDX $05B9
rom.write_bytes(0x38FA9A, bytearray([0xBD, 0x32, 0x06])) # LDA $0632, X
rom.write_bytes(0x38FA9D, bytearray([0x09, 0x80, 0x00])) # ORA #8000
rom.write_bytes(0x38FAA0, bytearray([0x9D, 0x32, 0x06])) # STA $0632, X
rom.write_bytes(0x38FAA3, bytearray([0xAD, 0xD5, 0x18])) # LDA $18D5
rom.write_bytes(0x38FAA6, bytearray([0xD0, 0x03])) # BNE $80EA
rom.write_bytes(0x38FAA8, bytearray([0x9C, 0xD9, 0x18])) # STZ $18D9
rom.write_bytes(0x38FAAB, bytearray([0xA9, 0x78, 0x00])) # LDA #0078
rom.write_bytes(0x38FAAE, bytearray([0x8D, 0xD5, 0x18])) # STA $18D5
rom.write_bytes(0x38FAB1, bytearray([0x28])) # PLP
rom.write_bytes(0x38FAB2, bytearray([0x68])) # PLA
rom.write_bytes(0x38FAB3, bytearray([0xFA])) # PLX
rom.write_bytes(0x38FAB4, bytearray([0x6B])) # RTL
# End Handle KONGsanity
# Handle Credits
rom.write_bytes(0x32A5DF, bytearray([0x41, 0x52, 0x43, 0x48, 0x49, 0x50, 0x45, 0x4C, 0x41, 0x47, 0x4F, 0x20, 0x4D, 0x4F, 0xC4])) # "ARCHIPELAGO MOD"
rom.write_bytes(0x32A5EE, bytearray([0x00, 0x03, 0x50, 0x4F, 0x52, 0x59, 0x47, 0x4F, 0x4E, 0xC5])) # "PORYGONE"
from Utils import __version__
rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21]
rom.name.extend([0] * (21 - len(rom.name)))
rom.write_bytes(0x7FC0, rom.name)
# DKC3_TODO: This is a hack, reconsider
# Don't grant (DK, Bonus, Bear) Coins
rom.write_byte(0x3BD454, 0xEA)
rom.write_byte(0x3BD455, 0xEA)
# Don't grant Cogs
rom.write_byte(0x3BD574, 0xEA)
rom.write_byte(0x3BD575, 0xEA)
rom.write_byte(0x3BD576, 0xEA)
# Don't grant Banana Birds at their caves
rom.write_byte(0x32DD62, 0xEA)
rom.write_byte(0x32DD63, 0xEA)
rom.write_byte(0x32DD64, 0xEA)
# Don't grant Banana Birds at Bears
rom.write_byte(0x3492DB, 0xEA)
rom.write_byte(0x3492DC, 0xEA)
rom.write_byte(0x3492DD, 0xEA)
rom.write_byte(0x3493F4, 0xEA)
rom.write_byte(0x3493F5, 0xEA)
rom.write_byte(0x3493F6, 0xEA)
# Don't grant present at Blizzard
rom.write_byte(0x8454, 0x00)
# Don't grant Patch and Skis from their bosses
rom.write_byte(0x3F3762, 0x00)
rom.write_byte(0x3F377B, 0x00)
rom.write_byte(0x3F3797, 0x00)
# Always allow Start+Select
rom.write_byte(0x8BAB, 0x01)
# Handle Alt Palettes in Krematoa
rom.write_byte(0x3B97E9, 0x80)
rom.write_byte(0x3B97EA, 0xEA)
class DKC3DeltaPatch(APDeltaPatch):
hash = USHASH
game = "Donkey Kong Country 3"
patch_file_ending = ".apdkc3"
@classmethod
def get_source_data(cls) -> bytes:
return get_base_rom_bytes()
def get_base_rom_bytes(file_name: str = "") -> bytes:
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
if not base_rom_bytes:
file_name = get_base_rom_path(file_name)
base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb")))
basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes)
if USHASH != basemd5.hexdigest():
raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. '
'Get the correct game and version, then dump it')
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
return base_rom_bytes
def get_base_rom_path(file_name: str = "") -> str:
if not file_name:
from settings import get_settings
file_name = get_settings()["dkc3_options"]["rom_file"]
if not os.path.exists(file_name):
file_name = Utils.user_path(file_name)
return file_name

31
worlds/dkc3/Rules.py Normal file
View File

@@ -0,0 +1,31 @@
import math
from worlds.AutoWorld import World
from worlds.generic.Rules import add_rule
from .Names import LocationName, ItemName
def set_rules(world: World):
if False:#world.options.include_trade_sequence:
add_rule(world.multiworld.get_location(LocationName.barnacles_island, world.player),
lambda state: state.has(ItemName.shell, world.player))
add_rule(world.multiworld.get_location(LocationName.blues_beach_hut, world.player),
lambda state: state.has(ItemName.present, world.player))
add_rule(world.multiworld.get_location(LocationName.brambles_bungalow, world.player),
lambda state: state.has(ItemName.flower, world.player))
add_rule(world.multiworld.get_location(LocationName.barters_swap_shop, world.player),
lambda state: state.has(ItemName.mirror, world.player))
if world.options.goal != "knautilus":
required_banana_birds = math.floor(
world.options.number_of_banana_birds.value * (world.options.percentage_of_banana_birds.value / 100.0))
add_rule(world.multiworld.get_location(LocationName.banana_bird_mother, world.player),
lambda state: state.has(ItemName.banana_bird, world.player, required_banana_birds))
world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.victory, world.player)

233
worlds/dkc3/__init__.py Normal file
View File

@@ -0,0 +1,233 @@
import dataclasses
import math
import os
import threading
import typing
import settings
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
from Options import PerGameCommonOptions
from worlds.AutoWorld import WebWorld, World
from .Client import DKC3SNIClient
from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table
from .Levels import level_list
from .Locations import DKC3Location, all_locations, setup_locations
from .Names import ItemName, LocationName
from .Options import DKC3Options, dkc3_option_groups
from .Regions import create_regions, connect_regions
from .Rom import LocalRom, patch_rom, get_base_rom_path, DKC3DeltaPatch
from .Rules import set_rules
class DK3Settings(settings.Group):
class RomFile(settings.UserFilePath):
"""File name of the DKC3 US rom"""
copy_to = "Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc"
description = "DKC3 (US) ROM File"
md5s = [DKC3DeltaPatch.hash]
rom_file: RomFile = RomFile(RomFile.copy_to)
class DKC3Web(WebWorld):
theme = "jungle"
setup_en = Tutorial(
"Multiworld Setup Guide",
"A guide to setting up the Donkey Kong Country 3 randomizer connected to an Archipelago Multiworld.",
"English",
"setup_en.md",
"setup/en",
["PoryGone"]
)
tutorials = [setup_en]
option_groups = dkc3_option_groups
class DKC3World(World):
"""
Donkey Kong Country 3 is an action platforming game.
Play as Dixie Kong and her baby cousin Kiddy as they try to solve the
mystery of why Donkey Kong and Diddy disappeared while on vacation.
"""
game: str = "Donkey Kong Country 3"
settings: typing.ClassVar[DK3Settings]
options_dataclass = DKC3Options
options: DKC3Options
topology_present = False
#hint_blacklist = {LocationName.rocket_rush_flag}
item_name_to_id = {name: data.code for name, data in item_table.items()}
location_name_to_id = all_locations
active_level_list: typing.List[str]
web = DKC3Web()
def __init__(self, world: MultiWorld, player: int):
self.rom_name_available_event = threading.Event()
super().__init__(world, player)
@classmethod
def stage_assert_generate(cls, multiworld: MultiWorld):
rom_file = get_base_rom_path()
if not os.path.exists(rom_file):
raise FileNotFoundError(rom_file)
def _get_slot_data(self):
return {
#"death_link": self.options.death_link.value,
"active_levels": self.active_level_list,
}
def fill_slot_data(self) -> dict:
slot_data = self._get_slot_data()
for option_name in (attr.name for attr in dataclasses.fields(DKC3Options)
if attr not in dataclasses.fields(PerGameCommonOptions)):
option = getattr(self.options, option_name)
slot_data[option_name] = option.value
return slot_data
def create_regions(self):
location_table = setup_locations(self)
create_regions(self, location_table)
# Not generate basic
self.topology_present = self.options.level_shuffle.value
itempool: typing.List[DKC3Item] = []
# Levels
total_required_locations = 159
number_of_banana_birds = 0
# Rocket Rush Cog
total_required_locations -= 1
number_of_cogs = 4
self.multiworld.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog))
number_of_bosses = 8
if self.options.goal == "knautilus":
self.multiworld.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory))
number_of_bosses = 7
else:
self.multiworld.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory))
number_of_banana_birds = self.options.number_of_banana_birds
# Bosses
total_required_locations += number_of_bosses
# Secret Caves
total_required_locations += 13
if self.options.kongsanity:
total_required_locations += 39
## Brothers Bear
if False:#self.options.include_trade_sequence:
total_required_locations += 10
number_of_bonus_coins = (self.options.krematoa_bonus_coin_cost * 5)
number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.options.percentage_of_extra_bonus_coins / 100)
itempool += [self.create_item(ItemName.bonus_coin) for _ in range(number_of_bonus_coins)]
itempool += [self.create_item(ItemName.dk_coin) for _ in range(41)]
itempool += [self.create_item(ItemName.banana_bird) for _ in range(number_of_banana_birds)]
itempool += [self.create_item(ItemName.krematoa_cog) for _ in range(number_of_cogs)]
itempool += [self.create_item(ItemName.progressive_boat) for _ in range(3)]
total_junk_count = total_required_locations - len(itempool)
junk_pool = []
for item_name in self.multiworld.random.choices(list(junk_table.keys()), k=total_junk_count):
junk_pool.append(self.create_item(item_name))
itempool += junk_pool
self.active_level_list = level_list.copy()
if self.options.level_shuffle:
self.random.shuffle(self.active_level_list)
connect_regions(self, self.active_level_list)
self.multiworld.itempool += itempool
def generate_output(self, output_directory: str):
try:
rom = LocalRom(get_base_rom_path())
patch_rom(self, rom, self.active_level_list)
self.active_level_list.append(LocationName.rocket_rush_region)
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc")
rom.write_to_file(rompath)
self.rom_name = rom.name
patch = DKC3DeltaPatch(os.path.splitext(rompath)[0]+DKC3DeltaPatch.patch_file_ending, player=self.player,
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
patch.write()
except:
raise
finally:
self.rom_name_available_event.set() # make sure threading continues and errors are collected
if os.path.exists(rompath):
os.unlink(rompath)
def modify_multidata(self, multidata: dict):
import base64
# wait for self.rom_name to be available.
self.rom_name_available_event.wait()
rom_name = getattr(self, "rom_name", None)
# we skip in case of error, so that the original error in the output thread is the one that gets raised
if rom_name:
new_name = base64.b64encode(bytes(self.rom_name)).decode()
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
if self.topology_present:
world_names = [
LocationName.lake_orangatanga_region,
LocationName.kremwood_forest_region,
LocationName.cotton_top_cove_region,
LocationName.mekanos_region,
LocationName.k3_region,
LocationName.razor_ridge_region,
LocationName.kaos_kore_region,
LocationName.krematoa_region,
]
er_hint_data = {}
for world_index in range(len(world_names)):
for level_index in range(5):
level_id: int = world_index * 5 + level_index
if level_id >= len(self.active_level_list):
break
level_region = self.multiworld.get_region(self.active_level_list[level_id], self.player)
for location in level_region.locations:
er_hint_data[location.address] = world_names[world_index]
hint_data[self.player] = er_hint_data
def create_item(self, name: str, force_non_progression=False) -> Item:
data = item_table[name]
if force_non_progression:
classification = ItemClassification.filler
elif data.progression:
classification = ItemClassification.progression
else:
classification = ItemClassification.filler
created_item = DKC3Item(name, classification, data.code, self.player)
return created_item
def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(list(junk_table.keys()))
def set_rules(self):
set_rules(self)

View File

@@ -0,0 +1,6 @@
{
"game": "Donkey Kong Country 3",
"authors": [ "PoryGone" ],
"minimum_ap_version": "0.6.3",
"world_version": "1.1.0"
}

View File

@@ -0,0 +1,35 @@
# Donkey Kong Country 3
## Where is the options page?
The [player options page for this game](../player-options) contains all the options you need to configure and export a config file.
## What does randomization do to this game?
Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is
always able to be completed, but because of the item shuffle the player may need to access certain areas before they
would in the vanilla game.
## What is the goal of Donkey Kong Country 3 when randomized?
There are two goals which can be chosen:
- `Knautilus`: Collect Bonus Coins and Krematoa Cogs to reach K. Rool's submarine in Krematoa
- `Banana Bird Hunt`: Collect Banana Birds to free the Banana Bird Mother
## What items and locations get shuffled?
All Bonus Coins, DK Coins, and Banana Birds (if on a `Banana Bird Hunt` goal) are randomized. Additionally, level clears award a location check.
The Patch and two Skis for upgrading the boat are included. Bear Coins are provided if additional items are needed for the item pool.
Four of the Five Krematoa Cogs are randomized, but the final one is always in its vanilla location at the Flag of Rocket Rush in Krematoa
## Which items can be in another player's world?
Any shuffled item can be in other players' worlds.
## What does another world's item look like in Donkey Kong Country 3
Items pickups all retain their original appearance. You won't know if an item belongs to another player until you collect.
## When the player receives an item, what happens?
Currently, the items are silently added to the player's inventory, which can be seen when saving the game.

View File

@@ -0,0 +1,162 @@
# Donkey Kong Country 3 Randomizer Setup Guide
## Required Software
- [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:
- snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases),
- BizHawk from: [TASVideos](https://tasvideos.org/BizHawk)
- RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or,
- An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other
compatible hardware
- Your legally obtained Donkey Kong Country 3 ROM file, probably named `Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc`
## Optional Software
- Donkey Kong Country 3 Tracker
- PopTracker from: [PopTracker Releases Page](https://github.com/black-sliver/PopTracker/releases/)
- Donkey Kong Country 3 Archipelago PopTracker pack from: [DKC3 AP Tracker Releases Page](https://github.com/PoryGone/DKC3_AP_Tracker/releases/)
## Installation Procedures
### Windows Setup
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.
2. Right-click on a ROM file and select **Open with...**
3. Check the box next to **Always use this app to open .sfc files**
4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC**
5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you
extracted in step one.
## Create a Config (.yaml) File
### What is a config file and why do I need one?
See the guide on setting up a basic YAML at the Archipelago setup
guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en)
### Where do I get a config file?
The Player Options page on the website allows you to configure your personal options and export a config file from
them. Player options page: [Donkey Kong Country 3 Player Options Page](/games/Donkey%20Kong%20Country%203/player-options)
### Verifying your config file
If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML
validator page: [YAML Validation page](/check)
## Generating a Single-Player Game
1. Navigate to the Player Options page, configure your options, and click the "Generate Game" button.
- Player Options page: [Donkey Kong Country 3 Player Options Page](/games/Donkey%20Kong%20Country%203/player-options)
2. You will be presented with a "Seed Info" page.
3. Click the "Create New Room" link.
4. You will be presented with a server page, from which you can download your patch file.
5. Double-click on your patch file, and the Donkey Kong Country 3 Client will launch automatically, create your ROM from the
patch file, and open your emulator for you.
## Joining a MultiWorld Game
### Obtain your patch file and create your ROM
When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that is done,
the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch
files. Your patch file should have a `.apdkc3` extension.
Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the
client, and will also create your ROM in the same place as your patch file.
### Connect to the client
#### With an emulator
When the client launched automatically, SNI should have also automatically launched in the background. If this is its
first time launching, you may be prompted to allow it to communicate through the Windows Firewall.
##### snes9x-rr
1. Load your ROM file if it hasn't already been loaded.
2. Click on the File menu and hover on **Lua Scripting**
3. Click on **New Lua Script Window...**
4. In the new window, click **Browse...**
5. Select the connector lua file included with your client
- Look in the Archipelago folder for `/SNI/lua/Connector.lua`.
6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of
the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install.
##### BizHawk
1. Ensure you have the BSNES core loaded. This is done with the main menubar, under:
- (≤ 2.8) `Config``Cores``SNES``BSNES`
- (≥ 2.9) `Config``Preferred Cores``SNES``BSNESv115+`
2. Load your ROM file if it hasn't already been loaded.
If you changed your core preference after loading the ROM, don't forget to reload it (default hotkey: Ctrl+R).
3. Drag+drop the `Connector.lua` file included with your client onto the main EmuHawk window.
- Look in the Archipelago folder for `/SNI/lua/Connector.lua`.
- You could instead open the Lua Console manually, click `Script``Open Script`, and navigate to `Connector.lua`
with the file picker.
##### RetroArch 1.10.3 or newer
You only have to do these steps once. Note, RetroArch 1.9.x will not work as it is older than 1.10.3.
1. Enter the RetroArch main menu screen.
2. Go to Settings --> User Interface. Set "Show Advanced Settings" to ON.
3. Go to Settings --> Network. Set "Network Commands" to ON. (It is found below Request Device 16.) Leave the default
Network Command Port at 55355. \
![Screenshot of Network Commands setting](../../generic/docs/retroarch-network-commands-en.png)
4. Go to Main Menu --> Online Updater --> Core Downloader. Scroll down and select "Nintendo - SNES / SFC (bsnes-mercury
Performance)".
When loading a ROM, be sure to select a **bsnes-mercury** core. These are the only cores that allow external tools to
read ROM data.
#### With hardware
This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do
this now. SD2SNES and FXPak Pro users may download the appropriate firmware on the SD2SNES releases page. SD2SNES
releases page: [SD2SNES Releases Page](https://github.com/RedGuyyyy/sd2snes/releases)
Other hardware may find helpful information on the usb2snes platforms
page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platforms)
1. Close your emulator, which may have auto-launched.
2. Power on your device and load the ROM.
### Connect to the Archipelago Server
The patch file which launched your client should have automatically connected you to the AP Server. There are a few
reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the
client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it
into the "Server" input field then press enter.
The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected".
### Play the game
When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on
successfully joining a multiworld game!
## Hosting a MultiWorld game
The recommended way to host a game is to use our hosting service. The process is relatively simple:
1. Collect config files from your players.
2. Create a zip file containing your players' config files.
3. Upload that zip file to the Generate page above.
- Generate page: [WebHost Seed Generation Page](/generate)
4. Wait a moment while the seed is generated.
5. When the seed is generated, you will be redirected to a "Seed Info" page.
6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so
they may download their patch files from there.
7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all
players in the game. Any observers may also be given the link to this page.
8. Once all players have joined, you may begin playing.

View File

@@ -45,35 +45,35 @@ def create_regions(multiworld: MultiWorld, player: int, world_options: Options.D
def create_regions_basic_campaign(has_campaign_basic: bool, region_menu: Region, has_item_shuffle: bool, has_coinsanity: bool, def create_regions_basic_campaign(has_campaign_basic: bool, region_menu: Region, has_item_shuffle: bool, has_coinsanity: bool,
coin_bundle_size: int, player: int, multiworld: MultiWorld): coin_bundle_size: int, player: int, world: MultiWorld):
if not has_campaign_basic: if not has_campaign_basic:
return return
region_menu.exits += [Entrance(player, "DLC Quest Basic", region_menu)] region_menu.exits += [Entrance(player, "DLC Quest Basic", region_menu)]
locations_move_right = ["Movement Pack", "Animation Pack", "Audio Pack", "Pause Menu Pack"] locations_move_right = ["Movement Pack", "Animation Pack", "Audio Pack", "Pause Menu Pack"]
region_move_right = create_region_and_locations_basic("Move Right", locations_move_right, ["Moving"], player, multiworld, 4) region_move_right = create_region_and_locations_basic("Move Right", locations_move_right, ["Moving"], player, world, 4)
create_coinsanity_locations_dlc_quest(has_coinsanity, coin_bundle_size, player, region_move_right) create_coinsanity_locations_dlc_quest(has_coinsanity, coin_bundle_size, player, region_move_right)
locations_movement_pack = ["Time is Money Pack", "Psychological Warfare Pack", "Armor for your Horse Pack", "Shepherd Sheep"] locations_movement_pack = ["Time is Money Pack", "Psychological Warfare Pack", "Armor for your Horse Pack", "Shepherd Sheep"]
locations_movement_pack += conditional_location(has_item_shuffle, "Sword") locations_movement_pack += conditional_location(has_item_shuffle, "Sword")
create_region_and_locations_basic("Movement Pack", locations_movement_pack, ["Tree", "Cloud"], player, multiworld, 46) create_region_and_locations_basic("Movement Pack", locations_movement_pack, ["Tree", "Cloud"], player, world, 46)
locations_behind_tree = ["Double Jump Pack", "Map Pack", "Between Trees Sheep", "Hole in the Wall Sheep"] + conditional_location(has_item_shuffle, "Gun") locations_behind_tree = ["Double Jump Pack", "Map Pack", "Between Trees Sheep", "Hole in the Wall Sheep"] + conditional_location(has_item_shuffle, "Gun")
create_region_and_locations_basic("Behind Tree", locations_behind_tree, ["Behind Tree Double Jump", "Forest Entrance"], player, multiworld, 60) create_region_and_locations_basic("Behind Tree", locations_behind_tree, ["Behind Tree Double Jump", "Forest Entrance"], player, world, 60)
create_region_and_locations_basic("Psychological Warfare", ["West Cave Sheep"], ["Cloud Double Jump"], player, multiworld, 100) create_region_and_locations_basic("Psychological Warfare", ["West Cave Sheep"], ["Cloud Double Jump"], player, world, 100)
locations_double_jump_left = ["Pet Pack", "Top Hat Pack", "North West Alcove Sheep"] locations_double_jump_left = ["Pet Pack", "Top Hat Pack", "North West Alcove Sheep"]
create_region_and_locations_basic("Double Jump Total Left", locations_double_jump_left, ["Cave Tree", "Cave Roof"], player, multiworld, 50) create_region_and_locations_basic("Double Jump Total Left", locations_double_jump_left, ["Cave Tree", "Cave Roof"], player, world, 50)
create_region_and_locations_basic("Double Jump Total Left Cave", ["Top Hat Sheep"], [], player, multiworld, 9) create_region_and_locations_basic("Double Jump Total Left Cave", ["Top Hat Sheep"], [], player, world, 9)
create_region_and_locations_basic("Double Jump Total Left Roof", ["North West Ceiling Sheep"], [], player, multiworld, 10) create_region_and_locations_basic("Double Jump Total Left Roof", ["North West Ceiling Sheep"], [], player, world, 10)
locations_double_jump_left_ceiling = ["Sexy Outfits Pack", "Double Jump Alcove Sheep", "Sexy Outfits Sheep"] locations_double_jump_left_ceiling = ["Sexy Outfits Pack", "Double Jump Alcove Sheep", "Sexy Outfits Sheep"]
create_region_and_locations_basic("Double Jump Behind Tree", locations_double_jump_left_ceiling, ["True Double Jump"], player, multiworld, 89) create_region_and_locations_basic("Double Jump Behind Tree", locations_double_jump_left_ceiling, ["True Double Jump"], player, world, 89)
create_region_and_locations_basic("True Double Jump Behind Tree", ["Double Jump Floating Sheep", "Cutscene Sheep"], [], player, multiworld, 7) create_region_and_locations_basic("True Double Jump Behind Tree", ["Double Jump Floating Sheep", "Cutscene Sheep"], [], player, world, 7)
create_region_and_locations_basic("The Forest", ["Gun Pack", "Night Map Pack"], ["Behind Ogre", "Forest Double Jump"], player, multiworld, 171) create_region_and_locations_basic("The Forest", ["Gun Pack", "Night Map Pack"], ["Behind Ogre", "Forest Double Jump"], player, world, 171)
create_region_and_locations_basic("The Forest with double Jump", ["The Zombie Pack", "Forest Low Sheep"], ["Forest True Double Jump"], player, multiworld, 76) create_region_and_locations_basic("The Forest with double Jump", ["The Zombie Pack", "Forest Low Sheep"], ["Forest True Double Jump"], player, world, 76)
create_region_and_locations_basic("The Forest with double Jump Part 2", ["Forest High Sheep"], [], player, multiworld, 203) create_region_and_locations_basic("The Forest with double Jump Part 2", ["Forest High Sheep"], [], player, world, 203)
region_final_boss_room = create_region_and_locations_basic("The Final Boss Room", ["Finish the Fight Pack"], [], player, multiworld) region_final_boss_room = create_region_and_locations_basic("The Final Boss Room", ["Finish the Fight Pack"], [], player, world)
create_victory_event(region_final_boss_room, "Winning Basic", "Victory Basic", player) create_victory_event(region_final_boss_room, "Winning Basic", "Victory Basic", player)
connect_entrances_basic(player, multiworld) connect_entrances_basic(player, world)
def create_regions_lfod_campaign(coin_bundle_size, has_campaign_lfod, has_coinsanity, has_item_shuffle, multiworld, player, region_menu): def create_regions_lfod_campaign(coin_bundle_size, has_campaign_lfod, has_coinsanity, has_item_shuffle, multiworld, player, region_menu):
@@ -141,20 +141,20 @@ def create_victory_event(region_victory: Region, event_name: str, item_name: str
location_victory.place_locked_item(create_event(player, item_name)) location_victory.place_locked_item(create_event(player, item_name))
def connect_entrances_basic(player, multiworld): def connect_entrances_basic(player, world):
multiworld.get_entrance("DLC Quest Basic", player).connect(multiworld.get_region("Move Right", player)) world.get_entrance("DLC Quest Basic", player).connect(world.get_region("Move Right", player))
multiworld.get_entrance("Moving", player).connect(multiworld.get_region("Movement Pack", player)) world.get_entrance("Moving", player).connect(world.get_region("Movement Pack", player))
multiworld.get_entrance("Tree", player).connect(multiworld.get_region("Behind Tree", player)) world.get_entrance("Tree", player).connect(world.get_region("Behind Tree", player))
multiworld.get_entrance("Cloud", player).connect(multiworld.get_region("Psychological Warfare", player)) world.get_entrance("Cloud", player).connect(world.get_region("Psychological Warfare", player))
multiworld.get_entrance("Cloud Double Jump", player).connect(multiworld.get_region("Double Jump Total Left", player)) world.get_entrance("Cloud Double Jump", player).connect(world.get_region("Double Jump Total Left", player))
multiworld.get_entrance("Cave Tree", player).connect(multiworld.get_region("Double Jump Total Left Cave", player)) world.get_entrance("Cave Tree", player).connect(world.get_region("Double Jump Total Left Cave", player))
multiworld.get_entrance("Cave Roof", player).connect(multiworld.get_region("Double Jump Total Left Roof", player)) world.get_entrance("Cave Roof", player).connect(world.get_region("Double Jump Total Left Roof", player))
multiworld.get_entrance("Forest Entrance", player).connect(multiworld.get_region("The Forest", player)) world.get_entrance("Forest Entrance", player).connect(world.get_region("The Forest", player))
multiworld.get_entrance("Behind Tree Double Jump", player).connect(multiworld.get_region("Double Jump Behind Tree", player)) world.get_entrance("Behind Tree Double Jump", player).connect(world.get_region("Double Jump Behind Tree", player))
multiworld.get_entrance("Behind Ogre", player).connect(multiworld.get_region("The Final Boss Room", player)) world.get_entrance("Behind Ogre", player).connect(world.get_region("The Final Boss Room", player))
multiworld.get_entrance("Forest Double Jump", player).connect(multiworld.get_region("The Forest with double Jump", player)) world.get_entrance("Forest Double Jump", player).connect(world.get_region("The Forest with double Jump", player))
multiworld.get_entrance("Forest True Double Jump", player).connect(multiworld.get_region("The Forest with double Jump Part 2", player)) world.get_entrance("Forest True Double Jump", player).connect(world.get_region("The Forest with double Jump Part 2", player))
multiworld.get_entrance("True Double Jump", player).connect(multiworld.get_region("True Double Jump Behind Tree", player)) world.get_entrance("True Double Jump", player).connect(world.get_region("True Double Jump Behind Tree", player))
def connect_entrances_lfod(multiworld, player): def connect_entrances_lfod(multiworld, player):

View File

@@ -18,251 +18,251 @@ def has_enough_coin_freemium(player: int, coin: int):
return lambda state: state.prog_items[player][" coins freemium"] >= coin return lambda state: state.prog_items[player][" coins freemium"] >= coin
def set_rules(multiworld, player, world_options: Options.DLCQuestOptions): def set_rules(world, player, world_options: Options.DLCQuestOptions):
set_basic_rules(world_options, player, multiworld) set_basic_rules(world_options, player, world)
set_lfod_rules(world_options, player, multiworld) set_lfod_rules(world_options, player, world)
set_completion_condition(world_options, player, multiworld) set_completion_condition(world_options, player, world)
def set_basic_rules(world_options, player, multiworld): def set_basic_rules(world_options, player, world):
if world_options.campaign == Options.Campaign.option_live_freemium_or_die: if world_options.campaign == Options.Campaign.option_live_freemium_or_die:
return return
set_basic_entrance_rules(player, multiworld) set_basic_entrance_rules(player, world)
set_basic_self_obtained_items_rules(world_options, player, multiworld) set_basic_self_obtained_items_rules(world_options, player, world)
set_basic_shuffled_items_rules(world_options, player, multiworld) set_basic_shuffled_items_rules(world_options, player, world)
set_double_jump_glitchless_rules(world_options, player, multiworld) set_double_jump_glitchless_rules(world_options, player, world)
set_easy_double_jump_glitch_rules(world_options, player, multiworld) set_easy_double_jump_glitch_rules(world_options, player, world)
self_basic_coinsanity_funded_purchase_rules(world_options, player, multiworld) self_basic_coinsanity_funded_purchase_rules(world_options, player, world)
set_basic_self_funded_purchase_rules(world_options, player, multiworld) set_basic_self_funded_purchase_rules(world_options, player, world)
self_basic_win_condition(world_options, player, multiworld) self_basic_win_condition(world_options, player, world)
def set_basic_entrance_rules(player, multiworld): def set_basic_entrance_rules(player, world):
set_rule(multiworld.get_entrance("Moving", player), set_rule(world.get_entrance("Moving", player),
lambda state: state.has("Movement Pack", player)) lambda state: state.has("Movement Pack", player))
set_rule(multiworld.get_entrance("Cloud", player), set_rule(world.get_entrance("Cloud", player),
lambda state: state.has("Psychological Warfare Pack", player)) lambda state: state.has("Psychological Warfare Pack", player))
set_rule(multiworld.get_entrance("Forest Entrance", player), set_rule(world.get_entrance("Forest Entrance", player),
lambda state: state.has("Map Pack", player)) lambda state: state.has("Map Pack", player))
set_rule(multiworld.get_entrance("Forest True Double Jump", player), set_rule(world.get_entrance("Forest True Double Jump", player),
lambda state: state.has("Double Jump Pack", player)) lambda state: state.has("Double Jump Pack", player))
def set_basic_self_obtained_items_rules(world_options, player, multiworld): def set_basic_self_obtained_items_rules(world_options, player, world):
if world_options.item_shuffle != Options.ItemShuffle.option_disabled: if world_options.item_shuffle != Options.ItemShuffle.option_disabled:
return return
set_rule(multiworld.get_entrance("Behind Ogre", player), set_rule(world.get_entrance("Behind Ogre", player),
lambda state: state.has("Gun Pack", player)) lambda state: state.has("Gun Pack", player))
if world_options.time_is_money == Options.TimeIsMoney.option_required: if world_options.time_is_money == Options.TimeIsMoney.option_required:
set_rule(multiworld.get_entrance("Tree", player), set_rule(world.get_entrance("Tree", player),
lambda state: state.has("Time is Money Pack", player)) lambda state: state.has("Time is Money Pack", player))
set_rule(multiworld.get_entrance("Cave Tree", player), set_rule(world.get_entrance("Cave Tree", player),
lambda state: state.has("Time is Money Pack", player)) lambda state: state.has("Time is Money Pack", player))
set_rule(multiworld.get_location("Shepherd Sheep", player), set_rule(world.get_location("Shepherd Sheep", player),
lambda state: state.has("Time is Money Pack", player)) lambda state: state.has("Time is Money Pack", player))
set_rule(multiworld.get_location("North West Ceiling Sheep", player), set_rule(world.get_location("North West Ceiling Sheep", player),
lambda state: state.has("Time is Money Pack", player)) lambda state: state.has("Time is Money Pack", player))
set_rule(multiworld.get_location("North West Alcove Sheep", player), set_rule(world.get_location("North West Alcove Sheep", player),
lambda state: state.has("Time is Money Pack", player)) lambda state: state.has("Time is Money Pack", player))
set_rule(multiworld.get_location("West Cave Sheep", player), set_rule(world.get_location("West Cave Sheep", player),
lambda state: state.has("Time is Money Pack", player)) lambda state: state.has("Time is Money Pack", player))
def set_basic_shuffled_items_rules(world_options, player, multiworld): def set_basic_shuffled_items_rules(world_options, player, world):
if world_options.item_shuffle != Options.ItemShuffle.option_shuffled: if world_options.item_shuffle != Options.ItemShuffle.option_shuffled:
return return
set_rule(multiworld.get_entrance("Behind Ogre", player), set_rule(world.get_entrance("Behind Ogre", player),
lambda state: state.has("DLC Quest: Progressive Weapon", player, 2)) lambda state: state.has("DLC Quest: Progressive Weapon", player, 2))
set_rule(multiworld.get_entrance("Tree", player), set_rule(world.get_entrance("Tree", player),
lambda state: state.has("DLC Quest: Progressive Weapon", player)) lambda state: state.has("DLC Quest: Progressive Weapon", player))
set_rule(multiworld.get_entrance("Cave Tree", player), set_rule(world.get_entrance("Cave Tree", player),
lambda state: state.has("DLC Quest: Progressive Weapon", player)) lambda state: state.has("DLC Quest: Progressive Weapon", player))
set_rule(multiworld.get_entrance("True Double Jump", player), set_rule(world.get_entrance("True Double Jump", player),
lambda state: state.has("Double Jump Pack", player)) lambda state: state.has("Double Jump Pack", player))
set_rule(multiworld.get_location("Shepherd Sheep", player), set_rule(world.get_location("Shepherd Sheep", player),
lambda state: state.has("DLC Quest: Progressive Weapon", player)) lambda state: state.has("DLC Quest: Progressive Weapon", player))
set_rule(multiworld.get_location("North West Ceiling Sheep", player), set_rule(world.get_location("North West Ceiling Sheep", player),
lambda state: state.has("DLC Quest: Progressive Weapon", player)) lambda state: state.has("DLC Quest: Progressive Weapon", player))
set_rule(multiworld.get_location("North West Alcove Sheep", player), set_rule(world.get_location("North West Alcove Sheep", player),
lambda state: state.has("DLC Quest: Progressive Weapon", player)) lambda state: state.has("DLC Quest: Progressive Weapon", player))
set_rule(multiworld.get_location("West Cave Sheep", player), set_rule(world.get_location("West Cave Sheep", player),
lambda state: state.has("DLC Quest: Progressive Weapon", player)) lambda state: state.has("DLC Quest: Progressive Weapon", player))
set_rule(multiworld.get_location("Gun", player), set_rule(world.get_location("Gun", player),
lambda state: state.has("Gun Pack", player)) lambda state: state.has("Gun Pack", player))
if world_options.time_is_money == Options.TimeIsMoney.option_required: if world_options.time_is_money == Options.TimeIsMoney.option_required:
set_rule(multiworld.get_location("Sword", player), set_rule(world.get_location("Sword", player),
lambda state: state.has("Time is Money Pack", player)) lambda state: state.has("Time is Money Pack", player))
def set_double_jump_glitchless_rules(world_options, player, multiworld): def set_double_jump_glitchless_rules(world_options, player, world):
if world_options.double_jump_glitch != Options.DoubleJumpGlitch.option_none: if world_options.double_jump_glitch != Options.DoubleJumpGlitch.option_none:
return return
set_rule(multiworld.get_entrance("Cloud Double Jump", player), set_rule(world.get_entrance("Cloud Double Jump", player),
lambda state: state.has("Double Jump Pack", player)) lambda state: state.has("Double Jump Pack", player))
set_rule(multiworld.get_entrance("Forest Double Jump", player), set_rule(world.get_entrance("Forest Double Jump", player),
lambda state: state.has("Double Jump Pack", player)) lambda state: state.has("Double Jump Pack", player))
def set_easy_double_jump_glitch_rules(world_options, player, multiworld): def set_easy_double_jump_glitch_rules(world_options, player, world):
if world_options.double_jump_glitch == Options.DoubleJumpGlitch.option_all: if world_options.double_jump_glitch == Options.DoubleJumpGlitch.option_all:
return return
set_rule(multiworld.get_entrance("Behind Tree Double Jump", player), set_rule(world.get_entrance("Behind Tree Double Jump", player),
lambda state: state.has("Double Jump Pack", player)) lambda state: state.has("Double Jump Pack", player))
set_rule(multiworld.get_entrance("Cave Roof", player), set_rule(world.get_entrance("Cave Roof", player),
lambda state: state.has("Double Jump Pack", player)) lambda state: state.has("Double Jump Pack", player))
def self_basic_coinsanity_funded_purchase_rules(world_options, player, multiworld): def self_basic_coinsanity_funded_purchase_rules(world_options, player, world):
if world_options.coinsanity != Options.CoinSanity.option_coin: if world_options.coinsanity != Options.CoinSanity.option_coin:
return return
if world_options.coinbundlequantity == -1: if world_options.coinbundlequantity == -1:
self_basic_coinsanity_piece_rules(player, multiworld) self_basic_coinsanity_piece_rules(player, world)
return return
number_of_bundle = math.floor(825 / world_options.coinbundlequantity) number_of_bundle = math.floor(825 / world_options.coinbundlequantity)
for i in range(number_of_bundle): for i in range(number_of_bundle):
item_coin = f"DLC Quest: {world_options.coinbundlequantity * (i + 1)} Coin" item_coin = f"DLC Quest: {world_options.coinbundlequantity * (i + 1)} Coin"
set_rule(multiworld.get_location(item_coin, player), set_rule(world.get_location(item_coin, player),
has_enough_coin(player, world_options.coinbundlequantity * (i + 1))) has_enough_coin(player, world_options.coinbundlequantity * (i + 1)))
if 825 % world_options.coinbundlequantity != 0: if 825 % world_options.coinbundlequantity != 0:
set_rule(multiworld.get_location("DLC Quest: 825 Coin", player), set_rule(world.get_location("DLC Quest: 825 Coin", player),
has_enough_coin(player, 825)) has_enough_coin(player, 825))
set_rule(multiworld.get_location("Movement Pack", player), set_rule(world.get_location("Movement Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(4 / world_options.coinbundlequantity))) math.ceil(4 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Animation Pack", player), set_rule(world.get_location("Animation Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Audio Pack", player), set_rule(world.get_location("Audio Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Pause Menu Pack", player), set_rule(world.get_location("Pause Menu Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Time is Money Pack", player), set_rule(world.get_location("Time is Money Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(20 / world_options.coinbundlequantity))) math.ceil(20 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Double Jump Pack", player), set_rule(world.get_location("Double Jump Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(100 / world_options.coinbundlequantity))) math.ceil(100 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Pet Pack", player), set_rule(world.get_location("Pet Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Sexy Outfits Pack", player), set_rule(world.get_location("Sexy Outfits Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Top Hat Pack", player), set_rule(world.get_location("Top Hat Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Map Pack", player), set_rule(world.get_location("Map Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(140 / world_options.coinbundlequantity))) math.ceil(140 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Gun Pack", player), set_rule(world.get_location("Gun Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(75 / world_options.coinbundlequantity))) math.ceil(75 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("The Zombie Pack", player), set_rule(world.get_location("The Zombie Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Night Map Pack", player), set_rule(world.get_location("Night Map Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(75 / world_options.coinbundlequantity))) math.ceil(75 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Psychological Warfare Pack", player), set_rule(world.get_location("Psychological Warfare Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(50 / world_options.coinbundlequantity))) math.ceil(50 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Armor for your Horse Pack", player), set_rule(world.get_location("Armor for your Horse Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(250 / world_options.coinbundlequantity))) math.ceil(250 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Finish the Fight Pack", player), set_rule(world.get_location("Finish the Fight Pack", player),
lambda state: state.has("DLC Quest: Coin Bundle", player, lambda state: state.has("DLC Quest: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
def set_basic_self_funded_purchase_rules(world_options, player, multiworld): def set_basic_self_funded_purchase_rules(world_options, player, world):
if world_options.coinsanity != Options.CoinSanity.option_none: if world_options.coinsanity != Options.CoinSanity.option_none:
return return
set_rule(multiworld.get_location("Movement Pack", player), set_rule(world.get_location("Movement Pack", player),
has_enough_coin(player, 4)) has_enough_coin(player, 4))
set_rule(multiworld.get_location("Animation Pack", player), set_rule(world.get_location("Animation Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
set_rule(multiworld.get_location("Audio Pack", player), set_rule(world.get_location("Audio Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
set_rule(multiworld.get_location("Pause Menu Pack", player), set_rule(world.get_location("Pause Menu Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
set_rule(multiworld.get_location("Time is Money Pack", player), set_rule(world.get_location("Time is Money Pack", player),
has_enough_coin(player, 20)) has_enough_coin(player, 20))
set_rule(multiworld.get_location("Double Jump Pack", player), set_rule(world.get_location("Double Jump Pack", player),
has_enough_coin(player, 100)) has_enough_coin(player, 100))
set_rule(multiworld.get_location("Pet Pack", player), set_rule(world.get_location("Pet Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
set_rule(multiworld.get_location("Sexy Outfits Pack", player), set_rule(world.get_location("Sexy Outfits Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
set_rule(multiworld.get_location("Top Hat Pack", player), set_rule(world.get_location("Top Hat Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
set_rule(multiworld.get_location("Map Pack", player), set_rule(world.get_location("Map Pack", player),
has_enough_coin(player, 140)) has_enough_coin(player, 140))
set_rule(multiworld.get_location("Gun Pack", player), set_rule(world.get_location("Gun Pack", player),
has_enough_coin(player, 75)) has_enough_coin(player, 75))
set_rule(multiworld.get_location("The Zombie Pack", player), set_rule(world.get_location("The Zombie Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
set_rule(multiworld.get_location("Night Map Pack", player), set_rule(world.get_location("Night Map Pack", player),
has_enough_coin(player, 75)) has_enough_coin(player, 75))
set_rule(multiworld.get_location("Psychological Warfare Pack", player), set_rule(world.get_location("Psychological Warfare Pack", player),
has_enough_coin(player, 50)) has_enough_coin(player, 50))
set_rule(multiworld.get_location("Armor for your Horse Pack", player), set_rule(world.get_location("Armor for your Horse Pack", player),
has_enough_coin(player, 250)) has_enough_coin(player, 250))
set_rule(multiworld.get_location("Finish the Fight Pack", player), set_rule(world.get_location("Finish the Fight Pack", player),
has_enough_coin(player, 5)) has_enough_coin(player, 5))
def self_basic_win_condition(world_options, player, multiworld): def self_basic_win_condition(world_options, player, world):
if world_options.ending_choice == Options.EndingChoice.option_any: if world_options.ending_choice == Options.EndingChoice.option_any:
set_rule(multiworld.get_location("Winning Basic", player), set_rule(world.get_location("Winning Basic", player),
lambda state: state.has("Finish the Fight Pack", player)) lambda state: state.has("Finish the Fight Pack", player))
if world_options.ending_choice == Options.EndingChoice.option_true: if world_options.ending_choice == Options.EndingChoice.option_true:
set_rule(multiworld.get_location("Winning Basic", player), set_rule(world.get_location("Winning Basic", player),
lambda state: state.has("Armor for your Horse Pack", player) and state.has("Finish the Fight Pack", lambda state: state.has("Armor for your Horse Pack", player) and state.has("Finish the Fight Pack",
player)) player))
def set_lfod_rules(world_options, player, multiworld): def set_lfod_rules(world_options, player, world):
if world_options.campaign == Options.Campaign.option_basic: if world_options.campaign == Options.Campaign.option_basic:
return return
set_lfod_entrance_rules(player, multiworld) set_lfod_entrance_rules(player, world)
set_boss_door_requirements_rules(player, multiworld) set_boss_door_requirements_rules(player, world)
set_lfod_self_obtained_items_rules(world_options, player, multiworld) set_lfod_self_obtained_items_rules(world_options, player, world)
set_lfod_shuffled_items_rules(world_options, player, multiworld) set_lfod_shuffled_items_rules(world_options, player, world)
self_lfod_coinsanity_funded_purchase_rules(world_options, player, multiworld) self_lfod_coinsanity_funded_purchase_rules(world_options, player, world)
set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, multiworld) set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, world)
def set_lfod_entrance_rules(player, multiworld): def set_lfod_entrance_rules(player, world):
set_rule(multiworld.get_entrance("Wall Jump Entrance", player), set_rule(world.get_entrance("Wall Jump Entrance", player),
lambda state: state.has("Wall Jump Pack", player)) lambda state: state.has("Wall Jump Pack", player))
set_rule(multiworld.get_entrance("Harmless Plants", player), set_rule(world.get_entrance("Harmless Plants", player),
lambda state: state.has("Harmless Plants Pack", player)) lambda state: state.has("Harmless Plants Pack", player))
set_rule(multiworld.get_entrance("Name Change Entrance", player), set_rule(world.get_entrance("Name Change Entrance", player),
lambda state: state.has("Name Change Pack", player)) lambda state: state.has("Name Change Pack", player))
set_rule(multiworld.get_entrance("Cut Content Entrance", player), set_rule(world.get_entrance("Cut Content Entrance", player),
lambda state: state.has("Cut Content Pack", player)) lambda state: state.has("Cut Content Pack", player))
set_rule(multiworld.get_entrance("Blizzard", player), set_rule(world.get_entrance("Blizzard", player),
lambda state: state.has("Season Pass", player)) lambda state: state.has("Season Pass", player))
set_rule(multiworld.get_location("I Get That Reference!", player), set_rule(world.get_location("I Get That Reference!", player),
lambda state: state.has("Death of Comedy Pack", player)) lambda state: state.has("Death of Comedy Pack", player))
set_rule(multiworld.get_location("Story is Important", player), set_rule(world.get_location("Story is Important", player),
lambda state: state.has("DLC NPC Pack", player)) lambda state: state.has("DLC NPC Pack", player))
def set_boss_door_requirements_rules(player, multiworld): def set_boss_door_requirements_rules(player, world):
sword_1 = "Big Sword Pack" sword_1 = "Big Sword Pack"
sword_2 = "Really Big Sword Pack" sword_2 = "Really Big Sword Pack"
sword_3 = "Unfathomable Sword Pack" sword_3 = "Unfathomable Sword Pack"
big_sword_location = multiworld.get_location(sword_1, player) big_sword_location = world.get_location(sword_1, player)
really_big_sword_location = multiworld.get_location(sword_2, player) really_big_sword_location = world.get_location(sword_2, player)
unfathomable_sword_location = multiworld.get_location(sword_3, player) unfathomable_sword_location = world.get_location(sword_3, player)
big_sword_valid_locations = [big_sword_location] big_sword_valid_locations = [big_sword_location]
really_big_sword_valid_locations = [big_sword_location, really_big_sword_location] really_big_sword_valid_locations = [big_sword_location, really_big_sword_location]
@@ -277,7 +277,7 @@ def set_boss_door_requirements_rules(player, multiworld):
has_3_swords = lambda state: ((state.has(sword_1, player) or big_sword_during_boss_fight) and has_3_swords = lambda state: ((state.has(sword_1, player) or big_sword_during_boss_fight) and
(state.has(sword_2, player) or really_big_sword_during_boss_fight) and (state.has(sword_2, player) or really_big_sword_during_boss_fight) and
(state.has(sword_3, player) or unfathomable_sword_during_boss_fight)) (state.has(sword_3, player) or unfathomable_sword_during_boss_fight))
set_rule(multiworld.get_entrance("Boss Door", player), has_3_swords) set_rule(world.get_entrance("Boss Door", player), has_3_swords)
def set_lfod_self_obtained_items_rules(world_options, player, multiworld): def set_lfod_self_obtained_items_rules(world_options, player, multiworld):
@@ -295,234 +295,234 @@ def set_lfod_self_obtained_items_rules(world_options, player, multiworld):
multiworld.register_indirect_condition(world.get_region("Cut Content"), world.get_entrance("Pickaxe Hard Cave")) multiworld.register_indirect_condition(world.get_region("Cut Content"), world.get_entrance("Pickaxe Hard Cave"))
def set_lfod_shuffled_items_rules(world_options, player, multiworld): def set_lfod_shuffled_items_rules(world_options, player, world):
if world_options.item_shuffle != Options.ItemShuffle.option_shuffled: if world_options.item_shuffle != Options.ItemShuffle.option_shuffled:
return return
set_rule(multiworld.get_entrance("Vines", player), set_rule(world.get_entrance("Vines", player),
lambda state: state.has("Live Freemium or Die: Progressive Weapon", player)) lambda state: state.has("Live Freemium or Die: Progressive Weapon", player))
set_rule(multiworld.get_entrance("Behind Rocks", player), set_rule(world.get_entrance("Behind Rocks", player),
lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2)) lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2))
set_rule(multiworld.get_entrance("Pickaxe Hard Cave", player), set_rule(world.get_entrance("Pickaxe Hard Cave", player),
lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2)) lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2))
set_rule(multiworld.get_location("Wooden Sword", player), set_rule(world.get_location("Wooden Sword", player),
lambda state: state.has("Incredibly Important Pack", player)) lambda state: state.has("Incredibly Important Pack", player))
set_rule(multiworld.get_location("Pickaxe", player), set_rule(world.get_location("Pickaxe", player),
lambda state: state.has("Humble Indie Bindle", player)) lambda state: state.has("Humble Indie Bindle", player))
set_rule(multiworld.get_location("Humble Indie Bindle", player), set_rule(world.get_location("Humble Indie Bindle", player),
lambda state: state.has("Box of Various Supplies", player) and lambda state: state.has("Box of Various Supplies", player) and
state.can_reach("Cut Content", 'region', player)) state.can_reach("Cut Content", 'region', player))
set_rule(multiworld.get_location("Box of Various Supplies", player), set_rule(world.get_location("Box of Various Supplies", player),
lambda state: state.can_reach("Cut Content", 'region', player)) lambda state: state.can_reach("Cut Content", 'region', player))
def self_lfod_coinsanity_funded_purchase_rules(world_options, player, multiworld): def self_lfod_coinsanity_funded_purchase_rules(world_options, player, world):
if world_options.coinsanity != Options.CoinSanity.option_coin: if world_options.coinsanity != Options.CoinSanity.option_coin:
return return
if world_options.coinbundlequantity == -1: if world_options.coinbundlequantity == -1:
self_lfod_coinsanity_piece_rules(player, multiworld) self_lfod_coinsanity_piece_rules(player, world)
return return
number_of_bundle = math.floor(889 / world_options.coinbundlequantity) number_of_bundle = math.floor(889 / world_options.coinbundlequantity)
for i in range(number_of_bundle): for i in range(number_of_bundle):
item_coin_freemium = f"Live Freemium or Die: {world_options.coinbundlequantity * (i + 1)} Coin" item_coin_freemium = f"Live Freemium or Die: {world_options.coinbundlequantity * (i + 1)} Coin"
set_rule(multiworld.get_location(item_coin_freemium, player), set_rule(world.get_location(item_coin_freemium, player),
has_enough_coin_freemium(player, world_options.coinbundlequantity * (i + 1))) has_enough_coin_freemium(player, world_options.coinbundlequantity * (i + 1)))
if 889 % world_options.coinbundlequantity != 0: if 889 % world_options.coinbundlequantity != 0:
set_rule(multiworld.get_location("Live Freemium or Die: 889 Coin", player), set_rule(world.get_location("Live Freemium or Die: 889 Coin", player),
has_enough_coin_freemium(player, 889)) has_enough_coin_freemium(player, 889))
add_rule(multiworld.get_entrance("Boss Door", player), add_rule(world.get_entrance("Boss Door", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(200 / world_options.coinbundlequantity))) math.ceil(200 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Particles Pack", player), set_rule(world.get_location("Particles Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Day One Patch Pack", player), set_rule(world.get_location("Day One Patch Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Checkpoint Pack", player), set_rule(world.get_location("Checkpoint Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Incredibly Important Pack", player), set_rule(world.get_location("Incredibly Important Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(15 / world_options.coinbundlequantity))) math.ceil(15 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Wall Jump Pack", player), set_rule(world.get_location("Wall Jump Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(35 / world_options.coinbundlequantity))) math.ceil(35 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Health Bar Pack", player), set_rule(world.get_location("Health Bar Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Parallax Pack", player), set_rule(world.get_location("Parallax Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(5 / world_options.coinbundlequantity))) math.ceil(5 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Harmless Plants Pack", player), set_rule(world.get_location("Harmless Plants Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(130 / world_options.coinbundlequantity))) math.ceil(130 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Death of Comedy Pack", player), set_rule(world.get_location("Death of Comedy Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(15 / world_options.coinbundlequantity))) math.ceil(15 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Canadian Dialog Pack", player), set_rule(world.get_location("Canadian Dialog Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(10 / world_options.coinbundlequantity))) math.ceil(10 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("DLC NPC Pack", player), set_rule(world.get_location("DLC NPC Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(15 / world_options.coinbundlequantity))) math.ceil(15 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Cut Content Pack", player), set_rule(world.get_location("Cut Content Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(40 / world_options.coinbundlequantity))) math.ceil(40 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Name Change Pack", player), set_rule(world.get_location("Name Change Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(150 / world_options.coinbundlequantity))) math.ceil(150 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Season Pass", player), set_rule(world.get_location("Season Pass", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(199 / world_options.coinbundlequantity))) math.ceil(199 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("High Definition Next Gen Pack", player), set_rule(world.get_location("High Definition Next Gen Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(20 / world_options.coinbundlequantity))) math.ceil(20 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Increased HP Pack", player), set_rule(world.get_location("Increased HP Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(10 / world_options.coinbundlequantity))) math.ceil(10 / world_options.coinbundlequantity)))
set_rule(multiworld.get_location("Remove Ads Pack", player), set_rule(world.get_location("Remove Ads Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Bundle", player, lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
math.ceil(25 / world_options.coinbundlequantity))) math.ceil(25 / world_options.coinbundlequantity)))
def set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, multiworld): def set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, world):
if world_options.coinsanity != Options.CoinSanity.option_none: if world_options.coinsanity != Options.CoinSanity.option_none:
return return
add_rule(multiworld.get_entrance("Boss Door", player), add_rule(world.get_entrance("Boss Door", player),
has_enough_coin_freemium(player, 200)) has_enough_coin_freemium(player, 200))
set_rule(multiworld.get_location("Particles Pack", player), set_rule(world.get_location("Particles Pack", player),
has_enough_coin_freemium(player, 5)) has_enough_coin_freemium(player, 5))
set_rule(multiworld.get_location("Day One Patch Pack", player), set_rule(world.get_location("Day One Patch Pack", player),
has_enough_coin_freemium(player, 5)) has_enough_coin_freemium(player, 5))
set_rule(multiworld.get_location("Checkpoint Pack", player), set_rule(world.get_location("Checkpoint Pack", player),
has_enough_coin_freemium(player, 5)) has_enough_coin_freemium(player, 5))
set_rule(multiworld.get_location("Incredibly Important Pack", player), set_rule(world.get_location("Incredibly Important Pack", player),
has_enough_coin_freemium(player, 15)) has_enough_coin_freemium(player, 15))
set_rule(multiworld.get_location("Wall Jump Pack", player), set_rule(world.get_location("Wall Jump Pack", player),
has_enough_coin_freemium(player, 35)) has_enough_coin_freemium(player, 35))
set_rule(multiworld.get_location("Health Bar Pack", player), set_rule(world.get_location("Health Bar Pack", player),
has_enough_coin_freemium(player, 5)) has_enough_coin_freemium(player, 5))
set_rule(multiworld.get_location("Parallax Pack", player), set_rule(world.get_location("Parallax Pack", player),
has_enough_coin_freemium(player, 5)) has_enough_coin_freemium(player, 5))
set_rule(multiworld.get_location("Harmless Plants Pack", player), set_rule(world.get_location("Harmless Plants Pack", player),
has_enough_coin_freemium(player, 130)) has_enough_coin_freemium(player, 130))
set_rule(multiworld.get_location("Death of Comedy Pack", player), set_rule(world.get_location("Death of Comedy Pack", player),
has_enough_coin_freemium(player, 15)) has_enough_coin_freemium(player, 15))
set_rule(multiworld.get_location("Canadian Dialog Pack", player), set_rule(world.get_location("Canadian Dialog Pack", player),
has_enough_coin_freemium(player, 10)) has_enough_coin_freemium(player, 10))
set_rule(multiworld.get_location("DLC NPC Pack", player), set_rule(world.get_location("DLC NPC Pack", player),
has_enough_coin_freemium(player, 15)) has_enough_coin_freemium(player, 15))
set_rule(multiworld.get_location("Cut Content Pack", player), set_rule(world.get_location("Cut Content Pack", player),
has_enough_coin_freemium(player, 40)) has_enough_coin_freemium(player, 40))
set_rule(multiworld.get_location("Name Change Pack", player), set_rule(world.get_location("Name Change Pack", player),
has_enough_coin_freemium(player, 150)) has_enough_coin_freemium(player, 150))
set_rule(multiworld.get_location("Season Pass", player), set_rule(world.get_location("Season Pass", player),
has_enough_coin_freemium(player, 199)) has_enough_coin_freemium(player, 199))
set_rule(multiworld.get_location("High Definition Next Gen Pack", player), set_rule(world.get_location("High Definition Next Gen Pack", player),
has_enough_coin_freemium(player, 20)) has_enough_coin_freemium(player, 20))
set_rule(multiworld.get_location("Increased HP Pack", player), set_rule(world.get_location("Increased HP Pack", player),
has_enough_coin_freemium(player, 10)) has_enough_coin_freemium(player, 10))
set_rule(multiworld.get_location("Remove Ads Pack", player), set_rule(world.get_location("Remove Ads Pack", player),
has_enough_coin_freemium(player, 25)) has_enough_coin_freemium(player, 25))
def set_completion_condition(world_options, player, multiworld): def set_completion_condition(world_options, player, world):
if world_options.campaign == Options.Campaign.option_basic: if world_options.campaign == Options.Campaign.option_basic:
multiworld.completion_condition[player] = lambda state: state.has("Victory Basic", player) world.completion_condition[player] = lambda state: state.has("Victory Basic", player)
if world_options.campaign == Options.Campaign.option_live_freemium_or_die: if world_options.campaign == Options.Campaign.option_live_freemium_or_die:
multiworld.completion_condition[player] = lambda state: state.has("Victory Freemium", player) world.completion_condition[player] = lambda state: state.has("Victory Freemium", player)
if world_options.campaign == Options.Campaign.option_both: if world_options.campaign == Options.Campaign.option_both:
multiworld.completion_condition[player] = lambda state: state.has("Victory Basic", player) and state.has( world.completion_condition[player] = lambda state: state.has("Victory Basic", player) and state.has(
"Victory Freemium", player) "Victory Freemium", player)
def self_basic_coinsanity_piece_rules(player, multiworld): def self_basic_coinsanity_piece_rules(player, world):
for i in range(1,8251): for i in range(1,8251):
item_coin = f"DLC Quest: {i} Coin Piece" item_coin = f"DLC Quest: {i} Coin Piece"
set_rule(multiworld.get_location(item_coin, player), set_rule(world.get_location(item_coin, player),
has_enough_coin(player, math.ceil(i / 10))) has_enough_coin(player, math.ceil(i / 10)))
set_rule(multiworld.get_location("Movement Pack", player), set_rule(world.get_location("Movement Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 40)) lambda state: state.has("DLC Quest: Coin Piece", player, 40))
set_rule(multiworld.get_location("Animation Pack", player), set_rule(world.get_location("Animation Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
set_rule(multiworld.get_location("Audio Pack", player), set_rule(world.get_location("Audio Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
set_rule(multiworld.get_location("Pause Menu Pack", player), set_rule(world.get_location("Pause Menu Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
set_rule(multiworld.get_location("Time is Money Pack", player), set_rule(world.get_location("Time is Money Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 200)) lambda state: state.has("DLC Quest: Coin Piece", player, 200))
set_rule(multiworld.get_location("Double Jump Pack", player), set_rule(world.get_location("Double Jump Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 100)) lambda state: state.has("DLC Quest: Coin Piece", player, 100))
set_rule(multiworld.get_location("Pet Pack", player), set_rule(world.get_location("Pet Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
set_rule(multiworld.get_location("Sexy Outfits Pack", player), set_rule(world.get_location("Sexy Outfits Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
set_rule(multiworld.get_location("Top Hat Pack", player), set_rule(world.get_location("Top Hat Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
set_rule(multiworld.get_location("Map Pack", player), set_rule(world.get_location("Map Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 1400)) lambda state: state.has("DLC Quest: Coin Piece", player, 1400))
set_rule(multiworld.get_location("Gun Pack", player), set_rule(world.get_location("Gun Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 750)) lambda state: state.has("DLC Quest: Coin Piece", player, 750))
set_rule(multiworld.get_location("The Zombie Pack", player), set_rule(world.get_location("The Zombie Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
set_rule(multiworld.get_location("Night Map Pack", player), set_rule(world.get_location("Night Map Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 750)) lambda state: state.has("DLC Quest: Coin Piece", player, 750))
set_rule(multiworld.get_location("Psychological Warfare Pack", player), set_rule(world.get_location("Psychological Warfare Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 500)) lambda state: state.has("DLC Quest: Coin Piece", player, 500))
set_rule(multiworld.get_location("Armor for your Horse Pack", player), set_rule(world.get_location("Armor for your Horse Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 2500)) lambda state: state.has("DLC Quest: Coin Piece", player, 2500))
set_rule(multiworld.get_location("Finish the Fight Pack", player), set_rule(world.get_location("Finish the Fight Pack", player),
lambda state: state.has("DLC Quest: Coin Piece", player, 50)) lambda state: state.has("DLC Quest: Coin Piece", player, 50))
def self_lfod_coinsanity_piece_rules(player, multiworld): def self_lfod_coinsanity_piece_rules(player, world):
for i in range(1, 8891): for i in range(1, 8891):
item_coin_freemium = f"Live Freemium or Die: {i} Coin Piece" item_coin_freemium = f"Live Freemium or Die: {i} Coin Piece"
set_rule(multiworld.get_location(item_coin_freemium, player), set_rule(world.get_location(item_coin_freemium, player),
has_enough_coin_freemium(player, math.ceil(i / 10))) has_enough_coin_freemium(player, math.ceil(i / 10)))
add_rule(multiworld.get_entrance("Boss Door", player), add_rule(world.get_entrance("Boss Door", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 2000)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 2000))
set_rule(multiworld.get_location("Particles Pack", player), set_rule(world.get_location("Particles Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
set_rule(multiworld.get_location("Day One Patch Pack", player), set_rule(world.get_location("Day One Patch Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
set_rule(multiworld.get_location("Checkpoint Pack", player), set_rule(world.get_location("Checkpoint Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
set_rule(multiworld.get_location("Incredibly Important Pack", player), set_rule(world.get_location("Incredibly Important Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150))
set_rule(multiworld.get_location("Wall Jump Pack", player), set_rule(world.get_location("Wall Jump Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 350)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 350))
set_rule(multiworld.get_location("Health Bar Pack", player), set_rule(world.get_location("Health Bar Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
set_rule(multiworld.get_location("Parallax Pack", player), set_rule(world.get_location("Parallax Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
set_rule(multiworld.get_location("Harmless Plants Pack", player), set_rule(world.get_location("Harmless Plants Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1300)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1300))
set_rule(multiworld.get_location("Death of Comedy Pack", player), set_rule(world.get_location("Death of Comedy Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150))
set_rule(multiworld.get_location("Canadian Dialog Pack", player), set_rule(world.get_location("Canadian Dialog Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100))
set_rule(multiworld.get_location("DLC NPC Pack", player), set_rule(world.get_location("DLC NPC Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150))
set_rule(multiworld.get_location("Cut Content Pack", player), set_rule(world.get_location("Cut Content Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 400)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 400))
set_rule(multiworld.get_location("Name Change Pack", player), set_rule(world.get_location("Name Change Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1500)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1500))
set_rule(multiworld.get_location("Season Pass", player), set_rule(world.get_location("Season Pass", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 199)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 199))
set_rule(multiworld.get_location("High Definition Next Gen Pack", player), set_rule(world.get_location("High Definition Next Gen Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 20)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 20))
set_rule(multiworld.get_location("Increased HP Pack", player), set_rule(world.get_location("Increased HP Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100))
set_rule(multiworld.get_location("Remove Ads Pack", player), set_rule(world.get_location("Remove Ads Pack", player),
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 250)) lambda state: state.has("Live Freemium or Die: Coin Piece", player, 250))

View File

@@ -37,7 +37,7 @@ class FactorioWeb(WebWorld):
"English", "English",
"setup_en.md", "setup_en.md",
"setup/en", "setup/en",
["Berserker", "Farrak Kilhn"] ["Berserker, Farrak Kilhn"]
)] )]
option_groups = option_groups option_groups = option_groups

View File

@@ -130,7 +130,6 @@ end
data.raw["assembling-machine"]["assembling-machine-1"].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories) data.raw["assembling-machine"]["assembling-machine-1"].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories)
data.raw["assembling-machine"]["assembling-machine-2"].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories) data.raw["assembling-machine"]["assembling-machine-2"].crafting_categories = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-3"].crafting_categories)
data.raw["assembling-machine"]["assembling-machine-1"].fluid_boxes = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-2"].fluid_boxes) data.raw["assembling-machine"]["assembling-machine-1"].fluid_boxes = table.deepcopy(data.raw["assembling-machine"]["assembling-machine-2"].fluid_boxes)
data.raw["assembling-machine"]["assembling-machine-1"].fluid_boxes_off_when_no_fluid_recipe = data.raw["assembling-machine"]["assembling-machine-2"].fluid_boxes_off_when_no_fluid_recipe
if mods["factory-levels"] then if mods["factory-levels"] then
-- Factory-Levels allows the assembling machines to get faster (and depending on settings), more productive at crafting products, the more the -- Factory-Levels allows the assembling machines to get faster (and depending on settings), more productive at crafting products, the more the
-- assembling machine crafts the product. If the machine crafts enough, it may auto-upgrade to the next tier. -- assembling machine crafts the product. If the machine crafts enough, it may auto-upgrade to the next tier.

View File

@@ -1 +1 @@
factorio-rcon-py==2.1.3 factorio-rcon-py>=2.1.2

View File

@@ -43,7 +43,7 @@ class FaxanaduWorld(World):
item_name_to_item = {item.name: item for item in Items.items} item_name_to_item = {item.name: item for item in Items.items}
location_name_to_id = {loc.name: loc.id for loc in Locations.locations if loc.id is not None} location_name_to_id = {loc.name: loc.id for loc in Locations.locations if loc.id is not None}
def __init__(self, multiworld: MultiWorld, player: int): def __init__(self, world: MultiWorld, player: int):
self.filler_ratios: Dict[str, int] = { self.filler_ratios: Dict[str, int] = {
item.name: item.count item.name: item.count
for item in Items.items for item in Items.items
@@ -51,7 +51,7 @@ class FaxanaduWorld(World):
} }
# Remove poison by default to respect itemlinking # Remove poison by default to respect itemlinking
self.filler_ratios["Poison"] = 0 self.filler_ratios["Poison"] = 0
super().__init__(multiworld, player) super().__init__(world, player)
def create_regions(self): def create_regions(self):
Regions.create_regions(self) Regions.create_regions(self)

View File

@@ -41,8 +41,8 @@ class FF1Locations:
@staticmethod @staticmethod
def create_menu_region(player: int, locations: Dict[str, int], def create_menu_region(player: int, locations: Dict[str, int],
rules: Dict[str, List[List[str]]], multiworld: MultiWorld) -> Region: rules: Dict[str, List[List[str]]], world: MultiWorld) -> Region:
menu_region = Region("Menu", player, multiworld) menu_region = Region("Menu", player, world)
for name, address in locations.items(): for name, address in locations.items():
location = Location(player, name, address, menu_region) location = Location(player, name, address, menu_region)
## TODO REMOVE WHEN LOGIC FOR TOFR IS CORRECT ## TODO REMOVE WHEN LOGIC FOR TOFR IS CORRECT

View File

@@ -50,8 +50,8 @@ class FF1World(World):
web = FF1Web() web = FF1Web()
def __init__(self, multiworld: MultiWorld, player: int): def __init__(self, world: MultiWorld, player: int):
super().__init__(multiworld, player) super().__init__(world, player)
self.locked_items = [] self.locked_items = []
self.locked_locations = [] self.locked_locations = []

View File

@@ -33,10 +33,10 @@ def process_rules(spot, access):
add_rule(spot, lambda state: state.has_all(access, spot.player)) add_rule(spot, lambda state: state.has_all(access, spot.player))
def create_region(multiworld: MultiWorld, player: int, name: str, room_id=None, locations=None, links=None): def create_region(world: MultiWorld, player: int, name: str, room_id=None, locations=None, links=None):
if links is None: if links is None:
links = [] links = []
ret = Region(name, player, multiworld) ret = Region(name, player, world)
if locations: if locations:
for location in locations: for location in locations:
location.parent_region = ret location.parent_region = ret

View File

@@ -26,10 +26,10 @@ class GenericWeb(WebWorld):
'English', 'setup_en.md', 'setup/en', ['alwaysintreble']) 'English', 'setup_en.md', 'setup/en', ['alwaysintreble'])
triggers = Tutorial('Archipelago Triggers Guide', 'A guide to setting up and using triggers in your game settings.', triggers = Tutorial('Archipelago Triggers Guide', 'A guide to setting up and using triggers in your game settings.',
'English', 'triggers_en.md', 'triggers/en', ['alwaysintreble']) 'English', 'triggers_en.md', 'triggers/en', ['alwaysintreble'])
other_games = Tutorial('Other Games and Tools', other = Tutorial('Other Games and Tools',
'A guide to additional games and tools that can be used with Archipelago.', 'A guide to additional games and tools that can be used with Archipelago.',
'English', 'other_en.md', 'other/en', ['Berserker']) 'English', 'other_en.md', 'other/en', ['Berserker'])
tutorials = [setup, mac, commands, advanced_settings, triggers, plando, other_games] tutorials = [setup, mac, commands, advanced_settings, triggers, plando, other]
class GenericWorld(World): class GenericWorld(World):

View File

@@ -2,7 +2,7 @@
Archipelago does not have a compiled release on macOS. However, it is possible to run from source code on macOS. This guide expects you to have some experience with running software from the terminal. Archipelago does not have a compiled release on macOS. However, it is possible to run from source code on macOS. This guide expects you to have some experience with running software from the terminal.
## Prerequisite Software ## Prerequisite Software
Here is a list of software to install and source code to download. Here is a list of software to install and source code to download.
1. Python 3.11.9 "universal2" or newer from the [macOS Python downloads page](https://www.python.org/downloads/macos/). 1. Python 3.11 "universal2" or newer from the [macOS Python downloads page](https://www.python.org/downloads/macos/).
**Python 3.14 is not supported yet.** **Python 3.14 is not supported yet.**
2. Xcode from the [macOS App Store](https://apps.apple.com/us/app/xcode/id497799835). 2. Xcode from the [macOS App Store](https://apps.apple.com/us/app/xcode/id497799835).
3. The source code from the [Archipelago releases page](https://github.com/ArchipelagoMW/Archipelago/releases). 3. The source code from the [Archipelago releases page](https://github.com/ArchipelagoMW/Archipelago/releases).

View File

@@ -1,37 +1,24 @@
# Other Games And Tools # Other Games and Tools
This page provides information and links regarding various tools that may be of use with Archipelago, including additional playable games not supported by this website. This guide provides information on additional community resources, tools, and games that function with Archipelago.
You should only download and use files from sources you trust; sources listed here are not officially vetted for safety, so use your own judgement and caution. ## Community Resources
## Discord The Archipelago community is active across several platforms where you can find support, new games, and tools.
Currently, Discord is the primary hub for Archipelago; whether it be finding people to play with, developing new game implementations, or finding new playable games. ### Discord Servers
Archipelago has two primary Discord servers for community interaction, game support, and hosting public games:
- **[Archipelago Official Discord](https://discord.gg/8Z65BR2)**: The main hub for the community, including general discussion, support, and public multiworld hosting.
- **[Archipelago After Dark Discord](https://discord.gg/fqvNCCRsu4)**: An adults-only server for 18+ and unrated content.
The [Archipelago Official Discord](https://discord.gg/8Z65BR2) is the main hub, while the [Archipelago After Dark Discord](https://discord.gg/fqvNCCRsu4) houses additional games that may be unrated or 18+ in some territories. Both servers feature an **#apworld-index** channel. These channels are repositories for "APWorlds" — additional game implementations that can be easily added to your Archipelago installation to support more games.
The `#apworld-index` channels in each of these servers contain lists of playable games which should be easily downloadable and playable with an Archipelago installation. ### Documentation
- **[Archipelago Wiki](https://archipelago.miraheze.org/)**: A community-maintained wiki.
## Wiki ## Community Tools
The community-maintained [Archipelago Wiki](https://archipelago.miraheze.org/) has information on many games as well, and acts as a great discord-free source of information. These community-developed tools are frequently used alongside Archipelago to improve the player experience.
## Hint Games
Hint Games are a special type of game which are not included as part of the multiworld generation process. Instead, they can log in to an ongoing multiworld, connecting to a slot designated for any game. Rather than earning items for other games in the multiworld, a Hint Game will allow you to earn hints for the slot you are connected to.
Hint Games can be found from sources such as the Discord and the [Hint Game Category](https://archipelago.miraheze.org/wiki/Category:Hint_games) of the wiki, as detailed above.
## Notable Tools
### Options Creator
The Options Creator is included in the Archipelago installation, and is accessible from the Archipelago Launcher. Using this simple GUI tool, you can easily create randomization options for any installed `.apworld` - perfect when using custom worlds you've installed that don't have options pages on the website.
### PopTracker ### PopTracker
**[PopTracker](https://github.com/black-sliver/PopTracker)** is a universal multi-platform tracking application designed for randomizers. It supports many Archipelago games through tracker packs, providing both manual and automatic autotracking capabilities by connecting directly to an Archipelago server.
[PopTracker](https://poptracker.github.io) is a popular tool in Randomizer communities, which many games support via custom PopTracker Packs. Many Archipelago packs include the ability to directly connect to your slot for auto-tracking capabilities. (Check each game's setup guide or Discord channel to see if it has PopTracker compatibility!)
### Universal Tracker
[Universal Tracker](https://github.com/FarisTheAncient/Archipelago/releases?q=Tracker) is a custom tracker client that uses your .yaml files from generation (as well as the .apworld files) to attempt to provide a view of what locations are currently in-logic or not, using the actual generation logic. Specific steps may need to be taken depending on the game, or the use of randomness in your yaml. Support for UT can be found in the [#universal-tracker](https://discord.com/channels/731205301247803413/1367270230635839539) channel of the Archipelago Official Discord.

25
worlds/hk/Regions.py Normal file
View File

@@ -0,0 +1,25 @@
from .ExtractedData import region_names, exits, connectors
def create_regions(world, player: int):
from . import create_region, HKLocation, HKItem
world.regions.append(create_region(world, player, 'Menu', None, ['Hollow Nest S&Q']))
for region in region_names:
world.regions.append(create_region(world, player, region, [],
exits.get(region, [])))
for entrance_name, exit_name in connectors.items():
if exit_name:
target_region = world.get_entrance(exit_name, player).parent_region
world.get_entrance(entrance_name, player).connect(target_region)
if not entrance_name.endswith("_R"):
# a traversable entrance puts the name of the target door "into logic".
loc = HKLocation(player, exit_name, None, target_region)
loc.place_locked_item(HKItem(exit_name,
not exit_name.startswith("White_Palace_"),
None, "Event", player))
target_region.locations.append(loc)
else:
ent = world.get_entrance(entrance_name, player)
ent.parent_region.exits.remove(ent)

View File

@@ -126,7 +126,7 @@ def enter_hylemxylem(state: CollectionState, player: int) -> bool:
def set_rules(hylics2world): def set_rules(hylics2world):
multiworld = hylics2world.multiworld world = hylics2world.multiworld
player = hylics2world.player player = hylics2world.player
extra = hylics2world.options.extra_items_in_logic extra = hylics2world.options.extra_items_in_logic
@@ -135,23 +135,23 @@ def set_rules(hylics2world):
start_location = hylics2world.options.start_location start_location = hylics2world.options.start_location
# Afterlife # Afterlife
add_rule(multiworld.get_location("Afterlife: TV", player), add_rule(world.get_location("Afterlife: TV", player),
lambda state: cave_key(state, player)) lambda state: cave_key(state, player))
# New Muldul # New Muldul
add_rule(multiworld.get_location("New Muldul: Underground Chest", player), add_rule(world.get_location("New Muldul: Underground Chest", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("New Muldul: TV", player), add_rule(world.get_location("New Muldul: TV", player),
lambda state: house_key(state, player)) lambda state: house_key(state, player))
add_rule(multiworld.get_location("New Muldul: Upper House Chest 1", player), add_rule(world.get_location("New Muldul: Upper House Chest 1", player),
lambda state: upper_house_key(state, player)) lambda state: upper_house_key(state, player))
add_rule(multiworld.get_location("New Muldul: Upper House Chest 2", player), add_rule(world.get_location("New Muldul: Upper House Chest 2", player),
lambda state: upper_house_key(state, player)) lambda state: upper_house_key(state, player))
add_rule(multiworld.get_location("New Muldul: Pot above Vault", player), add_rule(world.get_location("New Muldul: Pot above Vault", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
# New Muldul Vault # New Muldul Vault
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 1", player), add_rule(world.get_location("New Muldul: Rescued Blerol 1", player),
lambda state: ( lambda state: (
( (
( (
@@ -165,7 +165,7 @@ def set_rules(hylics2world):
) )
or enter_hylemxylem(state, player) or enter_hylemxylem(state, player)
)) ))
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 2", player), add_rule(world.get_location("New Muldul: Rescued Blerol 2", player),
lambda state: ( lambda state: (
( (
( (
@@ -179,194 +179,194 @@ def set_rules(hylics2world):
) )
or enter_hylemxylem(state, player) or enter_hylemxylem(state, player)
)) ))
add_rule(multiworld.get_location("New Muldul: Vault Left Chest", player), add_rule(world.get_location("New Muldul: Vault Left Chest", player),
lambda state: enter_hylemxylem(state, player)) lambda state: enter_hylemxylem(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Right Chest", player), add_rule(world.get_location("New Muldul: Vault Right Chest", player),
lambda state: enter_hylemxylem(state, player)) lambda state: enter_hylemxylem(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Bomb", player), add_rule(world.get_location("New Muldul: Vault Bomb", player),
lambda state: enter_hylemxylem(state, player)) lambda state: enter_hylemxylem(state, player))
# Viewax's Edifice # Viewax's Edifice
add_rule(multiworld.get_location("Viewax's Edifice: Canopic Jar", player), add_rule(world.get_location("Viewax's Edifice: Canopic Jar", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Cave Sarcophagus", player), add_rule(world.get_location("Viewax's Edifice: Cave Sarcophagus", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Shielded Key", player), add_rule(world.get_location("Viewax's Edifice: Shielded Key", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Shielded Key", player), add_rule(world.get_location("Viewax's Edifice: Shielded Key", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Tower Pot", player), add_rule(world.get_location("Viewax's Edifice: Tower Pot", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Tower Jar", player), add_rule(world.get_location("Viewax's Edifice: Tower Jar", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Tower Chest", player), add_rule(world.get_location("Viewax's Edifice: Tower Chest", player),
lambda state: ( lambda state: (
paddle(state, player) paddle(state, player)
and tower_key(state, player) and tower_key(state, player)
)) ))
add_rule(multiworld.get_location("Viewax's Edifice: Viewax Pot", player), add_rule(world.get_location("Viewax's Edifice: Viewax Pot", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Defeat Viewax", player), add_rule(world.get_location("Viewax's Edifice: Defeat Viewax", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: TV", player), add_rule(world.get_location("Viewax's Edifice: TV", player),
lambda state: ( lambda state: (
paddle(state, player) paddle(state, player)
and jail_key(state, player) and jail_key(state, player)
)) ))
add_rule(multiworld.get_location("Viewax's Edifice: Sage Fridge", player), add_rule(world.get_location("Viewax's Edifice: Sage Fridge", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Sage Item 1", player), add_rule(world.get_location("Viewax's Edifice: Sage Item 1", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Sage Item 2", player), add_rule(world.get_location("Viewax's Edifice: Sage Item 2", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
# Arcade 1 # Arcade 1
add_rule(multiworld.get_location("Arcade 1: Key", player), add_rule(world.get_location("Arcade 1: Key", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Coin Dash", player), add_rule(world.get_location("Arcade 1: Coin Dash", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Burrito Alcove 1", player), add_rule(world.get_location("Arcade 1: Burrito Alcove 1", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Burrito Alcove 2", player), add_rule(world.get_location("Arcade 1: Burrito Alcove 2", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Behind Spikes Banana", player), add_rule(world.get_location("Arcade 1: Behind Spikes Banana", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Pyramid Banana", player), add_rule(world.get_location("Arcade 1: Pyramid Banana", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Moving Platforms Muscle Applique", player), add_rule(world.get_location("Arcade 1: Moving Platforms Muscle Applique", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Bed Banana", player), add_rule(world.get_location("Arcade 1: Bed Banana", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
# Airship # Airship
add_rule(multiworld.get_location("Airship: Talk to Somsnosa", player), add_rule(world.get_location("Airship: Talk to Somsnosa", player),
lambda state: worm_room_key(state, player)) lambda state: worm_room_key(state, player))
# Foglast # Foglast
add_rule(multiworld.get_location("Foglast: Underground Sarcophagus", player), add_rule(world.get_location("Foglast: Underground Sarcophagus", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Foglast: Shielded Key", player), add_rule(world.get_location("Foglast: Shielded Key", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Foglast: TV", player), add_rule(world.get_location("Foglast: TV", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and clicker(state, player) and clicker(state, player)
)) ))
add_rule(multiworld.get_location("Foglast: Buy Clicker", player), add_rule(world.get_location("Foglast: Buy Clicker", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Foglast: Shielded Chest", player), add_rule(world.get_location("Foglast: Shielded Chest", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Foglast: Cave Fridge", player), add_rule(world.get_location("Foglast: Cave Fridge", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Foglast: Roof Sarcophagus", player), add_rule(world.get_location("Foglast: Roof Sarcophagus", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and bridge_key(state, player) and bridge_key(state, player)
)) ))
add_rule(multiworld.get_location("Foglast: Under Lair Sarcophagus 1", player), add_rule(world.get_location("Foglast: Under Lair Sarcophagus 1", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and bridge_key(state, player) and bridge_key(state, player)
)) ))
add_rule(multiworld.get_location("Foglast: Under Lair Sarcophagus 2", player), add_rule(world.get_location("Foglast: Under Lair Sarcophagus 2", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and bridge_key(state, player) and bridge_key(state, player)
)) ))
add_rule(multiworld.get_location("Foglast: Under Lair Sarcophagus 3", player), add_rule(world.get_location("Foglast: Under Lair Sarcophagus 3", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and bridge_key(state, player) and bridge_key(state, player)
)) ))
add_rule(multiworld.get_location("Foglast: Sage Sarcophagus", player), add_rule(world.get_location("Foglast: Sage Sarcophagus", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and bridge_key(state, player) and bridge_key(state, player)
)) ))
add_rule(multiworld.get_location("Foglast: Sage Item 1", player), add_rule(world.get_location("Foglast: Sage Item 1", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and bridge_key(state, player) and bridge_key(state, player)
)) ))
add_rule(multiworld.get_location("Foglast: Sage Item 2", player), add_rule(world.get_location("Foglast: Sage Item 2", player),
lambda state: ( lambda state: (
air_dash(state, player) air_dash(state, player)
and bridge_key(state, player) and bridge_key(state, player)
)) ))
# Drill Castle # Drill Castle
add_rule(multiworld.get_location("Drill Castle: Island Banana", player), add_rule(world.get_location("Drill Castle: Island Banana", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Drill Castle: Island Pot", player), add_rule(world.get_location("Drill Castle: Island Pot", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Drill Castle: Cave Sarcophagus", player), add_rule(world.get_location("Drill Castle: Cave Sarcophagus", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Drill Castle: TV", player), add_rule(world.get_location("Drill Castle: TV", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
# Sage Labyrinth # Sage Labyrinth
add_rule(multiworld.get_location("Sage Labyrinth: Sage Item 1", player), add_rule(world.get_location("Sage Labyrinth: Sage Item 1", player),
lambda state: deep_key(state, player)) lambda state: deep_key(state, player))
add_rule(multiworld.get_location("Sage Labyrinth: Sage Item 2", player), add_rule(world.get_location("Sage Labyrinth: Sage Item 2", player),
lambda state: deep_key(state, player)) lambda state: deep_key(state, player))
add_rule(multiworld.get_location("Sage Labyrinth: Sage Left Arm", player), add_rule(world.get_location("Sage Labyrinth: Sage Left Arm", player),
lambda state: deep_key(state, player)) lambda state: deep_key(state, player))
add_rule(multiworld.get_location("Sage Labyrinth: Sage Right Arm", player), add_rule(world.get_location("Sage Labyrinth: Sage Right Arm", player),
lambda state: deep_key(state, player)) lambda state: deep_key(state, player))
add_rule(multiworld.get_location("Sage Labyrinth: Sage Left Leg", player), add_rule(world.get_location("Sage Labyrinth: Sage Left Leg", player),
lambda state: deep_key(state, player)) lambda state: deep_key(state, player))
add_rule(multiworld.get_location("Sage Labyrinth: Sage Right Leg", player), add_rule(world.get_location("Sage Labyrinth: Sage Right Leg", player),
lambda state: deep_key(state, player)) lambda state: deep_key(state, player))
# Sage Airship # Sage Airship
add_rule(multiworld.get_location("Sage Airship: TV", player), add_rule(world.get_location("Sage Airship: TV", player),
lambda state: all_tokens(state, player)) lambda state: all_tokens(state, player))
# Hylemxylem # Hylemxylem
add_rule(multiworld.get_location("Hylemxylem: Upper Chamber Banana", player), add_rule(world.get_location("Hylemxylem: Upper Chamber Banana", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Across Upper Reservoir Chest", player), add_rule(world.get_location("Hylemxylem: Across Upper Reservoir Chest", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Drained Lower Reservoir Chest", player), add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Chest", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Drained Lower Reservoir Burrito 1", player), add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Burrito 1", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Drained Lower Reservoir Burrito 2", player), add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Burrito 2", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Pot 1", player), add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 1", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Pot 2", player), add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 2", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Pot 3", player), add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 3", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Sarcophagus", player), add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Sarcophagus", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Drained Upper Reservoir Burrito 1", player), add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 1", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Drained Upper Reservoir Burrito 2", player), add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 2", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Drained Upper Reservoir Burrito 3", player), add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 3", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
add_rule(multiworld.get_location("Hylemxylem: Upper Reservoir Hole Key", player), add_rule(world.get_location("Hylemxylem: Upper Reservoir Hole Key", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
# extra rules if Extra Items in Logic is enabled # extra rules if Extra Items in Logic is enabled
if extra: if extra:
for i in multiworld.get_region("Foglast", player).entrances: for i in world.get_region("Foglast", player).entrances:
add_rule(i, lambda state: charge_up(state, player)) add_rule(i, lambda state: charge_up(state, player))
for i in multiworld.get_region("Sage Airship", player).entrances: for i in world.get_region("Sage Airship", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
charge_up(state, player) charge_up(state, player)
and paper_cup(state, player) and paper_cup(state, player)
and worm_room_key(state, player) and worm_room_key(state, player)
)) ))
for i in multiworld.get_region("Hylemxylem", player).entrances: for i in world.get_region("Hylemxylem", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
charge_up(state, player) charge_up(state, player)
and paper_cup(state, player) and paper_cup(state, player)
)) ))
add_rule(multiworld.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player), add_rule(world.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
lambda state: ( lambda state: (
charge_up(state, player) charge_up(state, player)
and paper_cup(state, player) and paper_cup(state, player)
@@ -374,9 +374,9 @@ def set_rules(hylics2world):
# extra rules if Shuffle Party Members is enabled # extra rules if Shuffle Party Members is enabled
if party: if party:
for i in multiworld.get_region("Arcade Island", player).entrances: for i in world.get_region("Arcade Island", player).entrances:
add_rule(i, lambda state: party_3(state, player)) add_rule(i, lambda state: party_3(state, player))
for i in multiworld.get_region("Foglast", player).entrances: for i in world.get_region("Foglast", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
party_3(state, player) party_3(state, player)
or ( or (
@@ -384,197 +384,197 @@ def set_rules(hylics2world):
and jail_key(state, player) and jail_key(state, player)
) )
)) ))
for i in multiworld.get_region("Sage Airship", player).entrances: for i in world.get_region("Sage Airship", player).entrances:
add_rule(i, lambda state: party_3(state, player)) add_rule(i, lambda state: party_3(state, player))
for i in multiworld.get_region("Hylemxylem", player).entrances: for i in world.get_region("Hylemxylem", player).entrances:
add_rule(i, lambda state: party_3(state, player)) add_rule(i, lambda state: party_3(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Defeat Viewax", player), add_rule(world.get_location("Viewax's Edifice: Defeat Viewax", player),
lambda state: party_2(state, player)) lambda state: party_2(state, player))
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 1", player), add_rule(world.get_location("New Muldul: Rescued Blerol 1", player),
lambda state: party_2(state, player)) lambda state: party_2(state, player))
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 2", player), add_rule(world.get_location("New Muldul: Rescued Blerol 2", player),
lambda state: party_2(state, player)) lambda state: party_2(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Left Chest", player), add_rule(world.get_location("New Muldul: Vault Left Chest", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Right Chest", player), add_rule(world.get_location("New Muldul: Vault Right Chest", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Bomb", player), add_rule(world.get_location("New Muldul: Vault Bomb", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("Juice Ranch: Battle with Somsnosa", player), add_rule(world.get_location("Juice Ranch: Battle with Somsnosa", player),
lambda state: party_2(state, player)) lambda state: party_2(state, player))
add_rule(multiworld.get_location("Juice Ranch: Somsnosa Joins", player), add_rule(world.get_location("Juice Ranch: Somsnosa Joins", player),
lambda state: party_2(state, player)) lambda state: party_2(state, player))
add_rule(multiworld.get_location("Airship: Talk to Somsnosa", player), add_rule(world.get_location("Airship: Talk to Somsnosa", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player), add_rule(world.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
# extra rules if Shuffle Red Medallions is enabled # extra rules if Shuffle Red Medallions is enabled
if medallion: if medallion:
add_rule(multiworld.get_location("New Muldul: Upper House Medallion", player), add_rule(world.get_location("New Muldul: Upper House Medallion", player),
lambda state: upper_house_key(state, player)) lambda state: upper_house_key(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Rear Left Medallion", player), add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
lambda state: ( lambda state: (
enter_foglast(state, player) enter_foglast(state, player)
and bridge_key(state, player) and bridge_key(state, player)
and air_dash(state, player) and air_dash(state, player)
)) ))
add_rule(multiworld.get_location("New Muldul: Vault Rear Right Medallion", player), add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player),
lambda state: ( lambda state: (
enter_foglast(state, player) enter_foglast(state, player)
and bridge_key(state, player) and bridge_key(state, player)
and air_dash(state, player) and air_dash(state, player)
)) ))
add_rule(multiworld.get_location("New Muldul: Vault Center Medallion", player), add_rule(world.get_location("New Muldul: Vault Center Medallion", player),
lambda state: ( lambda state: (
enter_foglast(state, player) enter_foglast(state, player)
and bridge_key(state, player) and bridge_key(state, player)
and air_dash(state, player) and air_dash(state, player)
)) ))
add_rule(multiworld.get_location("New Muldul: Vault Front Left Medallion", player), add_rule(world.get_location("New Muldul: Vault Front Left Medallion", player),
lambda state: ( lambda state: (
enter_foglast(state, player) enter_foglast(state, player)
and bridge_key(state, player) and bridge_key(state, player)
and air_dash(state, player) and air_dash(state, player)
)) ))
add_rule(multiworld.get_location("New Muldul: Vault Front Right Medallion", player), add_rule(world.get_location("New Muldul: Vault Front Right Medallion", player),
lambda state: ( lambda state: (
enter_foglast(state, player) enter_foglast(state, player)
and bridge_key(state, player) and bridge_key(state, player)
and air_dash(state, player) and air_dash(state, player)
)) ))
add_rule(multiworld.get_location("Viewax's Edifice: Fort Wall Medallion", player), add_rule(world.get_location("Viewax's Edifice: Fort Wall Medallion", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Jar Medallion", player), add_rule(world.get_location("Viewax's Edifice: Jar Medallion", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Viewax's Edifice: Sage Chair Medallion", player), add_rule(world.get_location("Viewax's Edifice: Sage Chair Medallion", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Arcade 1: Lonely Medallion", player), add_rule(world.get_location("Arcade 1: Lonely Medallion", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Alcove Medallion", player), add_rule(world.get_location("Arcade 1: Alcove Medallion", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Arcade 1: Lava Medallion", player), add_rule(world.get_location("Arcade 1: Lava Medallion", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Foglast: Under Lair Medallion", player), add_rule(world.get_location("Foglast: Under Lair Medallion", player),
lambda state: bridge_key(state, player)) lambda state: bridge_key(state, player))
add_rule(multiworld.get_location("Foglast: Mid-Air Medallion", player), add_rule(world.get_location("Foglast: Mid-Air Medallion", player),
lambda state: air_dash(state, player)) lambda state: air_dash(state, player))
add_rule(multiworld.get_location("Foglast: Top of Tower Medallion", player), add_rule(world.get_location("Foglast: Top of Tower Medallion", player),
lambda state: paddle(state, player)) lambda state: paddle(state, player))
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Medallion", player), add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Medallion", player),
lambda state: upper_chamber_key(state, player)) lambda state: upper_chamber_key(state, player))
# extra rules if Shuffle Red Medallions and Party Shuffle are enabled # extra rules if Shuffle Red Medallions and Party Shuffle are enabled
if party and medallion: if party and medallion:
add_rule(multiworld.get_location("New Muldul: Vault Rear Left Medallion", player), add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Rear Right Medallion", player), add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Center Medallion", player), add_rule(world.get_location("New Muldul: Vault Center Medallion", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Front Left Medallion", player), add_rule(world.get_location("New Muldul: Vault Front Left Medallion", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
add_rule(multiworld.get_location("New Muldul: Vault Front Right Medallion", player), add_rule(world.get_location("New Muldul: Vault Front Right Medallion", player),
lambda state: party_3(state, player)) lambda state: party_3(state, player))
# entrances # entrances
for i in multiworld.get_region("Airship", player).entrances: for i in world.get_region("Airship", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Arcade Island", player).entrances: for i in world.get_region("Arcade Island", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
airship(state, player) airship(state, player)
and air_dash(state, player) and air_dash(state, player)
)) ))
for i in multiworld.get_region("Worm Pod", player).entrances: for i in world.get_region("Worm Pod", player).entrances:
add_rule(i, lambda state: enter_wormpod(state, player)) add_rule(i, lambda state: enter_wormpod(state, player))
for i in multiworld.get_region("Foglast", player).entrances: for i in world.get_region("Foglast", player).entrances:
add_rule(i, lambda state: enter_foglast(state, player)) add_rule(i, lambda state: enter_foglast(state, player))
for i in multiworld.get_region("Sage Labyrinth", player).entrances: for i in world.get_region("Sage Labyrinth", player).entrances:
add_rule(i, lambda state: skull_bomb(state, player)) add_rule(i, lambda state: skull_bomb(state, player))
for i in multiworld.get_region("Sage Airship", player).entrances: for i in world.get_region("Sage Airship", player).entrances:
add_rule(i, lambda state: enter_sageship(state, player)) add_rule(i, lambda state: enter_sageship(state, player))
for i in multiworld.get_region("Hylemxylem", player).entrances: for i in world.get_region("Hylemxylem", player).entrances:
add_rule(i, lambda state: enter_hylemxylem(state, player)) add_rule(i, lambda state: enter_hylemxylem(state, player))
# random start logic (default) # random start logic (default)
if start_location == "waynehouse": if start_location == "waynehouse":
# entrances # entrances
for i in multiworld.get_region("Viewax", player).entrances: for i in world.get_region("Viewax", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
air_dash(state, player) air_dash(state, player)
and airship(state, player) and airship(state, player)
)) ))
for i in multiworld.get_region("TV Island", player).entrances: for i in world.get_region("TV Island", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Shield Facility", player).entrances: for i in world.get_region("Shield Facility", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Juice Ranch", player).entrances: for i in world.get_region("Juice Ranch", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
# random start logic (Viewax's Edifice) # random start logic (Viewax's Edifice)
elif start_location == "viewaxs_edifice": elif start_location == "viewaxs_edifice":
for i in multiworld.get_region("Waynehouse", player).entrances: for i in world.get_region("Waynehouse", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
air_dash(state, player) air_dash(state, player)
or airship(state, player) or airship(state, player)
)) ))
for i in multiworld.get_region("New Muldul", player).entrances: for i in world.get_region("New Muldul", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
air_dash(state, player) air_dash(state, player)
or airship(state, player) or airship(state, player)
)) ))
for i in multiworld.get_region("New Muldul Vault", player).entrances: for i in world.get_region("New Muldul Vault", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
air_dash(state, player) air_dash(state, player)
or airship(state, player) or airship(state, player)
)) ))
for i in multiworld.get_region("Drill Castle", player).entrances: for i in world.get_region("Drill Castle", player).entrances:
add_rule(i, lambda state: ( add_rule(i, lambda state: (
air_dash(state, player) air_dash(state, player)
or airship(state, player) or airship(state, player)
)) ))
for i in multiworld.get_region("TV Island", player).entrances: for i in world.get_region("TV Island", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Shield Facility", player).entrances: for i in world.get_region("Shield Facility", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Juice Ranch", player).entrances: for i in world.get_region("Juice Ranch", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Sage Labyrinth", player).entrances: for i in world.get_region("Sage Labyrinth", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
# start logic (TV Island) # start logic (TV Island)
elif start_location == "tv_island": elif start_location == "tv_island":
for i in multiworld.get_region("Waynehouse", player).entrances: for i in world.get_region("Waynehouse", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("New Muldul", player).entrances: for i in world.get_region("New Muldul", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("New Muldul Vault", player).entrances: for i in world.get_region("New Muldul Vault", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Drill Castle", player).entrances: for i in world.get_region("Drill Castle", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Viewax", player).entrances: for i in world.get_region("Viewax", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Shield Facility", player).entrances: for i in world.get_region("Shield Facility", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Juice Ranch", player).entrances: for i in world.get_region("Juice Ranch", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Sage Labyrinth", player).entrances: for i in world.get_region("Sage Labyrinth", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
# start logic (Shield Facility) # start logic (Shield Facility)
elif start_location == "shield_facility": elif start_location == "shield_facility":
for i in multiworld.get_region("Waynehouse", player).entrances: for i in world.get_region("Waynehouse", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("New Muldul", player).entrances: for i in world.get_region("New Muldul", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("New Muldul Vault", player).entrances: for i in world.get_region("New Muldul Vault", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Drill Castle", player).entrances: for i in world.get_region("Drill Castle", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Viewax", player).entrances: for i in world.get_region("Viewax", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("TV Island", player).entrances: for i in world.get_region("TV Island", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))
for i in multiworld.get_region("Sage Labyrinth", player).entrances: for i in world.get_region("Sage Labyrinth", player).entrances:
add_rule(i, lambda state: airship(state, player)) add_rule(i, lambda state: airship(state, player))

View File

@@ -5,11 +5,11 @@ from enum import IntFlag
from typing import Any, ClassVar, Dict, Iterator, List, Set, Tuple, Type from typing import Any, ClassVar, Dict, Iterator, List, Set, Tuple, Type
import settings import settings
from BaseClasses import CollectionRule, Item, ItemClassification, Location, MultiWorld, Region, Tutorial from BaseClasses import Item, ItemClassification, Location, MultiWorld, Region, Tutorial
from Options import PerGameCommonOptions from Options import PerGameCommonOptions
from Utils import __version__ from Utils import __version__
from worlds.AutoWorld import WebWorld, World from worlds.AutoWorld import WebWorld, World
from worlds.generic.Rules import add_rule, set_rule from worlds.generic.Rules import add_rule, CollectionRule, set_rule
from .Client import L2ACSNIClient # noqa: F401 from .Client import L2ACSNIClient # noqa: F401
from .Items import ItemData, ItemType, l2ac_item_name_to_id, l2ac_item_table, L2ACItem, start_id as items_start_id from .Items import ItemData, ItemType, l2ac_item_name_to_id, l2ac_item_table, L2ACItem, start_id as items_start_id
from .Locations import l2ac_location_name_to_id, L2ACLocation from .Locations import l2ac_location_name_to_id, L2ACLocation

View File

@@ -478,7 +478,7 @@ def space_zone_2_boss(state, player):
def space_zone_2_coins(state, player, coins): def space_zone_2_coins(state, player, coins):
auto_scroll = is_auto_scroll(state, player, "Space Zone 2") auto_scroll = is_auto_scroll(state, player, "Space Zone 2")
reachable_coins = 9 reachable_coins = 12
if state.has_any(["Mushroom", "Fire Flower", "Carrot", "Space Physics"], player): if state.has_any(["Mushroom", "Fire Flower", "Carrot", "Space Physics"], player):
reachable_coins += 15 reachable_coins += 15
if state.has("Space Physics", player) or not auto_scroll: if state.has("Space Physics", player) or not auto_scroll:
@@ -487,7 +487,7 @@ def space_zone_2_coins(state, player, coins):
state.has("Mushroom", player) and state.has_any(["Fire Flower", "Carrot"], player))): state.has("Mushroom", player) and state.has_any(["Fire Flower", "Carrot"], player))):
reachable_coins += 3 reachable_coins += 3
if state.has("Space Physics", player): if state.has("Space Physics", player):
reachable_coins += 82 reachable_coins += 79
if not auto_scroll: if not auto_scroll:
reachable_coins += 21 reachable_coins += 21
return coins <= reachable_coins return coins <= reachable_coins

View File

@@ -192,7 +192,7 @@ class MessengerRules:
or (self.has_dart(state) and self.has_wingsuit(state)), or (self.has_dart(state) and self.has_wingsuit(state)),
# Dark Cave # Dark Cave
"Dark Cave - Right -> Dark Cave - Left": "Dark Cave - Right -> Dark Cave - Left":
lambda state: state.has("Candle", self.player) and self.has_dart(state) and self.has_wingsuit(state), lambda state: state.has("Candle", self.player) and self.has_dart(state),
# Riviere Turquoise # Riviere Turquoise
"Riviere Turquoise - Waterfall Shop -> Riviere Turquoise - Flower Flight Checkpoint": "Riviere Turquoise - Waterfall Shop -> Riviere Turquoise - Flower Flight Checkpoint":
lambda state: self.has_dart(state) or ( lambda state: self.has_dart(state) or (

View File

@@ -95,10 +95,10 @@ class MM3World(World):
web = MM3WebWorld() web = MM3WebWorld()
rom_name: bytearray rom_name: bytearray
def __init__(self, multiworld: MultiWorld, player: int): def __init__(self, world: MultiWorld, player: int):
self.rom_name = bytearray() self.rom_name = bytearray()
self.rom_name_available_event = threading.Event() self.rom_name_available_event = threading.Event()
super().__init__(multiworld, player) super().__init__(world, player)
self.weapon_damage = deepcopy(weapon_damage) self.weapon_damage = deepcopy(weapon_damage)
self.wily_4_weapons: dict[int, list[int]] = {} self.wily_4_weapons: dict[int, list[int]] = {}

View File

@@ -1,6 +1,6 @@
{ {
"game": "Mega Man 3", "game": "Mega Man 3",
"authors": ["Silvris"], "authors": ["Silvris"],
"world_version": "0.1.8", "world_version": "0.1.7",
"minimum_ap_version": "0.6.4" "minimum_ap_version": "0.6.4"
} }

View File

@@ -463,13 +463,13 @@ class MMBN3World(World):
rompath: str = "" rompath: str = ""
try: try:
multiworld = self.multiworld world = self.multiworld
player = self.player player = self.player
rom = LocalRom(get_base_rom_path()) rom = LocalRom(get_base_rom_path())
for location_name in location_table.keys(): for location_name in location_table.keys():
location = multiworld.get_location(location_name, player) location = world.get_location(location_name, player)
ap_item = location.item ap_item = location.item
item_id = ap_item.code item_id = ap_item.code
if item_id is not None: if item_id is not None:
@@ -511,7 +511,7 @@ class MMBN3World(World):
rom.insert_hint_text(location_data, item_name_text, long_item_text) rom.insert_hint_text(location_data, item_name_text, long_item_text)
rom.inject_name(self.player_name) rom.inject_name(world.player_name[player])
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gba") rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gba")
@@ -519,7 +519,7 @@ class MMBN3World(World):
rom.write_to_file(rompath) rom.write_to_file(rompath)
patch = MMBN3DeltaPatch(os.path.splitext(rompath)[0]+MMBN3DeltaPatch.patch_file_ending, player=player, patch = MMBN3DeltaPatch(os.path.splitext(rompath)[0]+MMBN3DeltaPatch.patch_file_ending, player=player,
player_name=self.player_name, patched_path=rompath) player_name=world.player_name[player], patched_path=rompath)
patch.write() patch.write()
except: except:
raise raise

View File

@@ -15,7 +15,6 @@ class MuseDashCollections:
"Default Music", "Default Music",
"Budget Is Burning: Nano Core", "Budget Is Burning: Nano Core",
"Budget Is Burning Vol.1", "Budget Is Burning Vol.1",
"Wuthering Waves Pioneer Podcast",
] ]
MUSE_PLUS_DLC: str = "Muse Plus" MUSE_PLUS_DLC: str = "Muse Plus"
@@ -41,7 +40,6 @@ class MuseDashCollections:
"Heart Message feat. Aoi Tokimori Secret", "Heart Message feat. Aoi Tokimori Secret",
"Meow Rock feat. Chun Ge, Yuan Shen", "Meow Rock feat. Chun Ge, Yuan Shen",
"Stra Stella Secret", "Stra Stella Secret",
"Musepyoi Legend",
] ]
song_items = SONG_DATA song_items = SONG_DATA

View File

@@ -696,20 +696,11 @@ SONG_DATA: Dict[str, SongData] = {
"Otsukimi Koete Otsukiai": SongData(2900820, "43-70", "MD Plus Project", True, 6, 8, 10), "Otsukimi Koete Otsukiai": SongData(2900820, "43-70", "MD Plus Project", True, 6, 8, 10),
"Obenkyou Time": SongData(2900821, "43-71", "MD Plus Project", False, 6, 8, 11), "Obenkyou Time": SongData(2900821, "43-71", "MD Plus Project", False, 6, 8, 11),
"Retry Now": SongData(2900822, "43-72", "MD Plus Project", False, 3, 6, 9), "Retry Now": SongData(2900822, "43-72", "MD Plus Project", False, 3, 6, 9),
"Master Bancho's Sushi Class": SongData(2900823, "93-0", "Welcome to the Blue Hole!", False, None, 7, None), "Master Bancho's Sushi Class ": SongData(2900823, "93-0", "Welcome to the Blue Hole!", False, None, None, None),
"CHAOTiC BATTLE": SongData(2900824, "94-0", "Cosmic Radio 2025", False, 7, 9, 11), "CHAOTiC BATTLE": SongData(2900824, "94-0", "Cosmic Radio 2025", False, 7, 9, 11),
"FATAL GAME": SongData(2900825, "94-1", "Cosmic Radio 2025", False, 3, 6, 9), "FATAL GAME": SongData(2900825, "94-1", "Cosmic Radio 2025", False, 3, 6, 9),
"Aria": SongData(2900826, "94-2", "Cosmic Radio 2025", False, 4, 6, 9), "Aria": SongData(2900826, "94-2", "Cosmic Radio 2025", False, 4, 6, 9),
"+1 UNKNOWN -NUMBER": SongData(2900827, "94-3", "Cosmic Radio 2025", True, 4, 7, 10), "+1 UNKNOWN -NUMBER": SongData(2900827, "94-3", "Cosmic Radio 2025", True, 4, 7, 10),
"To the Beyond, from the Nameless Seaside": SongData(2900828, "94-4", "Cosmic Radio 2025", False, 5, 8, 10), "To the Beyond, from the Nameless Seaside": SongData(2900828, "94-4", "Cosmic Radio 2025", False, 5, 8, 10),
"REK421": SongData(2900829, "94-5", "Cosmic Radio 2025", True, 7, 9, 11), "REK421": SongData(2900829, "94-5", "Cosmic Radio 2025", True, 7, 9, 11),
"Musepyoi Legend": SongData(2900830, "95-0", "Ay-Aye Horse", True, None, None, None),
"Not Regret": SongData(2900831, "95-1", "Ay-Aye Horse", False, 7, 9, 11),
"-Toryanna-": SongData(2900832, "95-2", "Ay-Aye Horse", True, 4, 6, 9),
"Icecream Angels": SongData(2900833, "95-3", "Ay-Aye Horse", False, 3, 6, 9),
"MEGA TSKR": SongData(2900834, "95-4", "Ay-Aye Horse", False, 4, 7, 10),
"777 Vocal ver.": SongData(2900835, "95-5", "Ay-Aye Horse", False, 7, 9, 11),
"Chasing Daylight": SongData(2900836, "96-0", "Wuthering Waves Pioneer Podcast", False, 3, 5, 8),
"CATCH ME IF YOU CAN": SongData(2900837, "96-1", "Wuthering Waves Pioneer Podcast", False, 4, 6, 9),
"RUNNING FOR YOUR LIFE": SongData(2900838, "96-2", "Wuthering Waves Pioneer Podcast", False, 2, 5, 8),
} }

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