mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-24 13:33:29 -07:00
Compare commits
49 Commits
setup_more
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc911399fb | ||
|
|
0b38065123 | ||
|
|
a6c1347102 | ||
|
|
0a742b6c98 | ||
|
|
66712bbd87 | ||
|
|
5f9e38b783 | ||
|
|
bdde2140b3 | ||
|
|
42d24b4869 | ||
|
|
14aa6571de | ||
|
|
37d32a10b1 | ||
|
|
0ef2ed5b81 | ||
|
|
b7c4fcb4c6 | ||
|
|
bcd3f9a74c | ||
|
|
6262235161 | ||
|
|
030cb4b578 | ||
|
|
36bab6f52a | ||
|
|
e0cfef3407 | ||
|
|
bb2a775c05 | ||
|
|
427b147818 | ||
|
|
3f3c343fb3 | ||
|
|
debe4cf035 | ||
|
|
68f25f4642 | ||
|
|
3c4af8f432 | ||
|
|
5360b6bb37 | ||
|
|
2ee20a3ac4 | ||
|
|
c640d2fa24 | ||
|
|
58a6407040 | ||
|
|
ba7ca0bd23 | ||
|
|
bdbf72f148 | ||
|
|
2b46df90b4 | ||
|
|
88dc135960 | ||
|
|
95f696c04f | ||
|
|
96277fe9be | ||
|
|
a7a7879df4 | ||
|
|
773f3c4f08 | ||
|
|
139856a573 | ||
|
|
a1ed804267 | ||
|
|
2d58e7953c | ||
|
|
393ed51203 | ||
|
|
03c9d0717b | ||
|
|
5ca50cd8d3 | ||
|
|
36cf86f2e8 | ||
|
|
1705620c4f | ||
|
|
ffe4c6dd15 | ||
|
|
cf47cc67c0 | ||
|
|
645f25a94e | ||
|
|
74f41e3733 | ||
|
|
4276c6d6b0 | ||
|
|
116ab2286a |
2
.github/pyright-config.json
vendored
2
.github/pyright-config.json
vendored
@@ -3,6 +3,7 @@
|
||||
"../BizHawkClient.py",
|
||||
"../Patch.py",
|
||||
"../rule_builder/cached_world.py",
|
||||
"../rule_builder/field_resolvers.py",
|
||||
"../rule_builder/options.py",
|
||||
"../rule_builder/rules.py",
|
||||
"../test/param.py",
|
||||
@@ -18,6 +19,7 @@
|
||||
"../test/programs/test_multi_server.py",
|
||||
"../test/utils/__init__.py",
|
||||
"../test/webhost/test_descriptions.py",
|
||||
"../test/webhost/test_suuid.py",
|
||||
"../worlds/AutoSNIClient.py",
|
||||
"type_check.py"
|
||||
],
|
||||
|
||||
6
.github/workflows/analyze-modified-files.yml
vendored
6
.github/workflows/analyze-modified-files.yml
vendored
@@ -14,6 +14,8 @@ env:
|
||||
BEFORE: ${{ github.event.before }}
|
||||
AFTER: ${{ github.event.after }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
flake8-or-mypy:
|
||||
strategy:
|
||||
@@ -25,7 +27,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: "Determine modified files (pull_request)"
|
||||
if: github.event_name == 'pull_request'
|
||||
@@ -50,7 +52,7 @@ jobs:
|
||||
run: |
|
||||
echo "diff=." >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
if: env.diff != ''
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
31
.github/workflows/build.yml
vendored
31
.github/workflows/build.yml
vendored
@@ -41,9 +41,9 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
# - copy code below to release.yml -
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: '~3.12.7'
|
||||
check-latest: true
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
# - copy code above to release.yml -
|
||||
- name: Attest Build
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: actions/attest-build-provenance@v2
|
||||
uses: actions/attest@v4.1.0
|
||||
with:
|
||||
subject-path: |
|
||||
build/exe.*/ArchipelagoLauncher.exe
|
||||
@@ -110,18 +110,17 @@ jobs:
|
||||
cp Players/Templates/VVVVVV.yaml Players/
|
||||
timeout 30 ./ArchipelagoGenerate
|
||||
- name: Store 7z
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: ${{ env.ZIP_NAME }}
|
||||
path: dist/${{ env.ZIP_NAME }}
|
||||
compression-level: 0 # .7z is incompressible by zip
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
retention-days: 7 # keep for 7 days, should be enough
|
||||
- name: Store Setup
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: ${{ env.SETUP_NAME }}
|
||||
path: setups/${{ env.SETUP_NAME }}
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
retention-days: 7 # keep for 7 days, should be enough
|
||||
|
||||
@@ -129,14 +128,14 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
# - copy code below to release.yml -
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Install base dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
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
|
||||
- name: Get a recent python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: '~3.12.7'
|
||||
check-latest: true
|
||||
@@ -173,7 +172,7 @@ jobs:
|
||||
# - copy code above to release.yml -
|
||||
- name: Attest Build
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: actions/attest-build-provenance@v2
|
||||
uses: actions/attest@v4.1.0
|
||||
with:
|
||||
subject-path: |
|
||||
build/exe.*/ArchipelagoLauncher
|
||||
@@ -204,17 +203,17 @@ jobs:
|
||||
cp Players/Templates/VVVVVV.yaml Players/
|
||||
timeout 30 ./ArchipelagoGenerate
|
||||
- name: Store AppImage
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: ${{ 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
|
||||
retention-days: 7
|
||||
- name: Store .tar.gz
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: ${{ env.TAR_NAME }}
|
||||
path: dist/${{ env.TAR_NAME }}
|
||||
compression-level: 0 # .gz is incompressible by zip
|
||||
archive: false
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
24
.github/workflows/codeql-analysis.yml
vendored
24
.github/workflows/codeql-analysis.yml
vendored
@@ -17,17 +17,26 @@ on:
|
||||
paths:
|
||||
- '**.py'
|
||||
- '**.js'
|
||||
- '.github/workflows/codeql-analysis.yml'
|
||||
- '.github/workflows/*.yml'
|
||||
- '.github/workflows/*.yaml'
|
||||
- '**/action.yml'
|
||||
- '**/action.yaml'
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- '**.py'
|
||||
- '**.js'
|
||||
- '.github/workflows/codeql-analysis.yml'
|
||||
- '.github/workflows/*.yml'
|
||||
- '.github/workflows/*.yaml'
|
||||
- '**/action.yml'
|
||||
- '**/action.yaml'
|
||||
schedule:
|
||||
- cron: '44 8 * * 1'
|
||||
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -36,18 +45,17 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript', 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
language: [ 'javascript', 'python', 'actions' ]
|
||||
# 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
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4.35.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -58,7 +66,7 @@ jobs:
|
||||
# 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)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4.35.1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -72,4 +80,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4.35.1
|
||||
|
||||
4
.github/workflows/ctest.yml
vendored
4
.github/workflows/ctest.yml
vendored
@@ -24,6 +24,8 @@ on:
|
||||
- '**/CMakeLists.txt'
|
||||
- '.github/workflows/ctest.yml'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
ctest:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -35,7 +37,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
|
||||
if: startsWith(matrix.os,'windows')
|
||||
- uses: Bacondish2023/setup-googletest@49065d1f7a6d21f6134864dd65980fe5dbe06c73
|
||||
|
||||
16
.github/workflows/docker.yml
vendored
16
.github/workflows/docker.yml
vendored
@@ -19,6 +19,8 @@ on:
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -29,7 +31,7 @@ jobs:
|
||||
package-name: ${{ steps.package.outputs.name }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Set lowercase image name
|
||||
id: image
|
||||
@@ -43,7 +45,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
|
||||
tags: |
|
||||
@@ -92,13 +94,13 @@ jobs:
|
||||
cache-scope: arm64
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6.0.2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -115,7 +117,7 @@ jobs:
|
||||
echo "tags=$(IFS=','; echo "${suffixed[*]}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v7.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
@@ -135,7 +137,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
||||
2
.github/workflows/label-pull-requests.yml
vendored
2
.github/workflows/label-pull-requests.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: 'Apply content-based labels'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6.0.1
|
||||
with:
|
||||
sync-labels: false
|
||||
peer_review:
|
||||
|
||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -48,9 +48,9 @@ jobs:
|
||||
shell: bash
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
# - code below copied from build.yml -
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: '~3.12.7'
|
||||
check-latest: true
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
echo "SETUP_NAME=$SETUP_NAME" >> $Env:GITHUB_ENV
|
||||
# - code above copied from build.yml -
|
||||
- name: Attest Build
|
||||
uses: actions/attest-build-provenance@v2
|
||||
uses: actions/attest@v4.1.0
|
||||
with:
|
||||
subject-path: |
|
||||
build/exe.*/ArchipelagoLauncher.exe
|
||||
@@ -114,14 +114,14 @@ jobs:
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
# - code below copied from build.yml -
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Install base dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
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
|
||||
- name: Get a recent python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: '~3.12.7'
|
||||
check-latest: true
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
echo "TAR_NAME=$TAR_NAME" >> $GITHUB_ENV
|
||||
# - code above copied from build.yml -
|
||||
- name: Attest Build
|
||||
uses: actions/attest-build-provenance@v2
|
||||
uses: actions/attest@v4.1.0
|
||||
with:
|
||||
subject-path: |
|
||||
build/exe.*/ArchipelagoLauncher
|
||||
|
||||
10
.github/workflows/scan-build.yml
vendored
10
.github/workflows/scan-build.yml
vendored
@@ -28,12 +28,14 @@ on:
|
||||
- 'requirements.txt'
|
||||
- '.github/workflows/scan-build.yml'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
scan-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install newer Clang
|
||||
@@ -45,7 +47,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt install clang-tools-19
|
||||
- name: Get a recent python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install dependencies
|
||||
@@ -59,7 +61,9 @@ jobs:
|
||||
scan-build-19 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y
|
||||
- name: Store report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7.0.0
|
||||
with:
|
||||
name: scan-build-reports
|
||||
path: scan-build-reports
|
||||
compression-level: 9 # highly compressible
|
||||
if-no-files-found: error
|
||||
|
||||
6
.github/workflows/strict-type-check.yml
vendored
6
.github/workflows/strict-type-check.yml
vendored
@@ -14,13 +14,15 @@ on:
|
||||
- ".github/workflows/strict-type-check.yml"
|
||||
- "**.pyi"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
pyright:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
|
||||
10
.github/workflows/unittests.yml
vendored
10
.github/workflows/unittests.yml
vendored
@@ -29,6 +29,8 @@ on:
|
||||
- '!.github/workflows/**'
|
||||
- '.github/workflows/unittests.yml'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -51,9 +53,9 @@ jobs:
|
||||
os: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Set up Python ${{ matrix.python.version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python.version }}
|
||||
- name: Install dependencies
|
||||
@@ -78,9 +80,9 @@ jobs:
|
||||
- {version: '3.13'} # current
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6.0.2
|
||||
- name: Set up Python ${{ matrix.python.version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python.version }}
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -87,7 +87,8 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
|
||||
|
||||
seed = get_seed(args.seed)
|
||||
|
||||
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level, add_timestamp=args.log_time)
|
||||
if __name__ == "__main__":
|
||||
Utils.init_logging(f"Generate_{seed}", loglevel=args.log_level, add_timestamp=args.log_time)
|
||||
random.seed(seed)
|
||||
seed_name = get_seed_name(random)
|
||||
|
||||
|
||||
49
Launcher.py
49
Launcher.py
@@ -29,8 +29,8 @@ if __name__ == "__main__":
|
||||
|
||||
import settings
|
||||
import Utils
|
||||
from Utils import (init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, messagebox, open_filename,
|
||||
user_path)
|
||||
from Utils import (env_cleared_lib_path, init_logging, is_frozen, is_linux, is_macos, is_windows, local_path,
|
||||
messagebox, open_filename, user_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_logging('Launcher')
|
||||
@@ -52,10 +52,7 @@ def open_host_yaml():
|
||||
webbrowser.open(file)
|
||||
return
|
||||
|
||||
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
|
||||
env = env_cleared_lib_path()
|
||||
subprocess.Popen([exe, file], env=env)
|
||||
|
||||
def open_patch():
|
||||
@@ -106,10 +103,7 @@ def open_folder(folder_path):
|
||||
return
|
||||
|
||||
if exe:
|
||||
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
|
||||
env = env_cleared_lib_path()
|
||||
subprocess.Popen([exe, folder_path], env=env)
|
||||
else:
|
||||
logging.warning(f"No file browser available to open {folder_path}")
|
||||
@@ -202,22 +196,32 @@ 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
|
||||
|
||||
|
||||
def launch(exe, in_terminal=False):
|
||||
def launch(exe: Sequence[str], in_terminal: bool = False) -> bool:
|
||||
"""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 is_windows:
|
||||
# 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)
|
||||
return
|
||||
return True
|
||||
elif is_linux:
|
||||
terminal = which('x-terminal-emulator') or which('gnome-terminal') or which('xterm')
|
||||
terminal = which("x-terminal-emulator") or which("konsole") or which("gnome-terminal") or which("xterm")
|
||||
if terminal:
|
||||
subprocess.Popen([terminal, '-e', shlex.join(exe)])
|
||||
return
|
||||
# Clear LD_LIB_PATH during terminal startup, but set it again when running command in case it's needed
|
||||
ld_lib_path = os.environ.get("LD_LIBRARY_PATH")
|
||||
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:
|
||||
terminal = [which('open'), '-W', '-a', 'Terminal.app']
|
||||
terminal = [which("open"), "-W", "-a", "Terminal.app"]
|
||||
subprocess.Popen([*terminal, *exe])
|
||||
return
|
||||
return True
|
||||
subprocess.Popen(exe)
|
||||
return False
|
||||
|
||||
|
||||
def create_shortcut(button: Any, component: Component) -> None:
|
||||
@@ -406,12 +410,17 @@ def run_gui(launch_components: list[Component], args: Any) -> None:
|
||||
|
||||
@staticmethod
|
||||
def component_action(button):
|
||||
MDSnackbar(MDSnackbarText(text="Opening in a new window..."), y=dp(24), pos_hint={"center_x": 0.5},
|
||||
size_hint_x=0.5).open()
|
||||
open_text = "Opening in a new window..."
|
||||
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()
|
||||
else:
|
||||
launch(get_exe(button.component), button.component.cli)
|
||||
# if launch returns False, it started the process in background (not in a new terminal)
|
||||
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:
|
||||
""" When a patch file is dropped into the window, run the associated component. """
|
||||
|
||||
39
Options.py
39
Options.py
@@ -212,6 +212,13 @@ class Option(typing.Generic[T], metaclass=AssembleOptions):
|
||||
else:
|
||||
return cls.name_lookup[value]
|
||||
|
||||
def __eq__(self, other: typing.Any) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.value == other.value
|
||||
if isinstance(other, Option):
|
||||
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
|
||||
return self.value == other
|
||||
|
||||
def __int__(self) -> T:
|
||||
return self.value
|
||||
|
||||
@@ -930,13 +937,34 @@ class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mappin
|
||||
class OptionCounter(OptionDict):
|
||||
min: int | None = None
|
||||
max: int | None = None
|
||||
cull_zeroes: bool = False
|
||||
|
||||
def __init__(self, value: dict[str, int]) -> None:
|
||||
super(OptionCounter, self).__init__(collections.Counter(value))
|
||||
cleaned_dict = {}
|
||||
|
||||
invalid_value_errors = []
|
||||
for key, value in value.items():
|
||||
if not isinstance(value, (int, float)) or int(value) != value:
|
||||
invalid_value_errors += [f"Invalid value {value} for key {key}, must be an integer."]
|
||||
continue
|
||||
|
||||
if self.cull_zeroes and value == 0:
|
||||
continue
|
||||
|
||||
cleaned_dict[key] = int(value)
|
||||
|
||||
if invalid_value_errors:
|
||||
type_errors = [f"For option {self.__class__.__name__}:"] + invalid_value_errors
|
||||
raise TypeError("\n".join(invalid_value_errors))
|
||||
|
||||
super(OptionCounter, self).__init__(collections.Counter(cleaned_dict))
|
||||
|
||||
def verify(self, world: type[World], player_name: str, plando_options: PlandoOptions) -> None:
|
||||
super(OptionCounter, self).verify(world, player_name, plando_options)
|
||||
|
||||
self.verify_values()
|
||||
|
||||
def verify_values(self):
|
||||
range_errors = []
|
||||
|
||||
if self.max is not None:
|
||||
@@ -959,13 +987,8 @@ class OptionCounter(OptionDict):
|
||||
class ItemDict(OptionCounter):
|
||||
verify_item_name = True
|
||||
|
||||
min = 0
|
||||
|
||||
def __init__(self, value: dict[str, int]) -> None:
|
||||
# Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter
|
||||
value = {item_name: amount for item_name, amount in value.items() if amount != 0}
|
||||
|
||||
super(ItemDict, self).__init__(value)
|
||||
# Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter
|
||||
cull_zeroes = True
|
||||
|
||||
|
||||
class OptionList(Option[typing.List[typing.Any]], VerifyKeys):
|
||||
|
||||
@@ -384,10 +384,11 @@ class OptionsCreator(ThemedApp):
|
||||
def create_free_text(self, option: typing.Type[FreeText] | typing.Type[TextChoice], name: str):
|
||||
text = VisualFreeText(option=option, name=name)
|
||||
|
||||
def set_value(instance):
|
||||
self.options[name] = instance.text
|
||||
def set_value(instance, value):
|
||||
self.options[name] = value
|
||||
|
||||
text.bind(on_text_validate=set_value)
|
||||
text.bind(text=set_value)
|
||||
self.options[name] = option.default
|
||||
return text
|
||||
|
||||
def create_choice(self, option: typing.Type[Choice], name: str):
|
||||
|
||||
@@ -24,7 +24,6 @@ Currently, the following games are supported:
|
||||
* The Witness
|
||||
* Sonic Adventure 2: Battle
|
||||
* Starcraft 2
|
||||
* Donkey Kong Country 3
|
||||
* Dark Souls 3
|
||||
* Super Mario World
|
||||
* Pokémon Red and Blue
|
||||
|
||||
40
Utils.py
40
Utils.py
@@ -22,7 +22,7 @@ from datetime import datetime, timezone
|
||||
|
||||
from settings import Settings, get_settings
|
||||
from time import sleep
|
||||
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union, TypeGuard
|
||||
from typing import BinaryIO, Coroutine, Mapping, Optional, Set, Dict, Any, Union, TypeGuard
|
||||
from yaml import load, load_all, dump
|
||||
from pathspec import PathSpec, GitIgnoreSpec
|
||||
from typing_extensions import deprecated
|
||||
@@ -236,10 +236,7 @@ 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"))
|
||||
assert open_command, "Didn't find program for open_file! Please report this together with system details."
|
||||
|
||||
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
|
||||
env = env_cleared_lib_path()
|
||||
subprocess.call([open_command, filename], env=env)
|
||||
|
||||
|
||||
@@ -345,6 +342,9 @@ def persistent_load() -> Dict[str, Dict[str, Any]]:
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
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:
|
||||
logging.debug(f"Could not read store: {e}")
|
||||
if storage is None:
|
||||
@@ -369,11 +369,6 @@ def load_data_package_for_checksum(game: str, checksum: typing.Optional[str]) ->
|
||||
except Exception as 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
|
||||
return {}
|
||||
|
||||
@@ -455,13 +450,10 @@ safe_builtins = frozenset((
|
||||
|
||||
|
||||
class RestrictedUnpickler(pickle.Unpickler):
|
||||
generic_properties_module: Optional[object]
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super(RestrictedUnpickler, self).__init__(*args, **kwargs)
|
||||
self.options_module = importlib.import_module("Options")
|
||||
self.net_utils_module = importlib.import_module("NetUtils")
|
||||
self.generic_properties_module = None
|
||||
|
||||
def find_class(self, module: str, name: str) -> type:
|
||||
if module == "builtins" and name in safe_builtins:
|
||||
@@ -475,10 +467,6 @@ class RestrictedUnpickler(pickle.Unpickler):
|
||||
"SlotType", "NetworkSlot", "HintStatus"}:
|
||||
return getattr(self.net_utils_module, name)
|
||||
# Options and Plando are unpickled by WebHost -> Generate
|
||||
if module == "worlds.generic" and name == "PlandoItem":
|
||||
if not self.generic_properties_module:
|
||||
self.generic_properties_module = importlib.import_module("worlds.generic")
|
||||
return getattr(self.generic_properties_module, name)
|
||||
# pep 8 specifies that modules should have "all-lowercase names" (options, not Options)
|
||||
if module.lower().endswith("options"):
|
||||
if module == "Options":
|
||||
@@ -758,6 +746,19 @@ def is_kivy_running() -> bool:
|
||||
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:
|
||||
if is_kivy_running():
|
||||
raise RuntimeError("kivy should not be running in multiprocess")
|
||||
@@ -770,10 +771,7 @@ def _mp_save_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args:
|
||||
res.put(save_filename(*args))
|
||||
|
||||
def _run_for_stdout(*args: str):
|
||||
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
|
||||
env = env_cleared_lib_path()
|
||||
return subprocess.run(args, capture_output=True, text=True, env=env).stdout.split("\n", 1)[0] or None
|
||||
|
||||
|
||||
|
||||
@@ -110,13 +110,14 @@ if __name__ == "__main__":
|
||||
logging.exception(e)
|
||||
logging.warning("Could not update LttP sprites.")
|
||||
app = get_app()
|
||||
from worlds import AutoWorldRegister
|
||||
from worlds import AutoWorldRegister, network_data_package
|
||||
# Update to only valid WebHost worlds
|
||||
invalid_worlds = {name for name, world in AutoWorldRegister.world_types.items()
|
||||
if not hasattr(world.web, "tutorials")}
|
||||
if 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}
|
||||
network_data_package["games"] = {k: v for k, v in network_data_package["games"].items() if k not in invalid_worlds}
|
||||
create_options_files()
|
||||
copy_tutorials_files_to_static()
|
||||
if app.config["SELFLAUNCH"]:
|
||||
|
||||
@@ -71,7 +71,9 @@ CLI(app)
|
||||
|
||||
|
||||
def to_python(value: str) -> uuid.UUID:
|
||||
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=='))
|
||||
if "=" in value or any(c.isspace() for c in value):
|
||||
raise ValueError("Invalid UUID format")
|
||||
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=' * (-len(value) % 4)))
|
||||
|
||||
|
||||
def to_url(value: uuid.UUID) -> str:
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
flask>=3.1.1
|
||||
werkzeug>=3.1.3
|
||||
pony>=0.7.19; python_version <= '3.12'
|
||||
flask==3.1.3
|
||||
werkzeug==3.1.6
|
||||
pony==0.7.19; python_version <= '3.12'
|
||||
pony @ git+https://github.com/black-sliver/pony@7feb1221953b7fa4a6735466bf21a8b4d35e33ba#0.7.19; python_version >= '3.13'
|
||||
waitress>=3.0.2
|
||||
Flask-Caching>=2.3.0
|
||||
waitress==3.0.2
|
||||
Flask-Caching==2.3.1
|
||||
Flask-Compress==1.18 # pkg_resources can't resolve the "backports.zstd" dependency of >1.18, breaking ModuleUpdate.py
|
||||
Flask-Limiter>=3.12
|
||||
Flask-Cors>=6.0.2
|
||||
bokeh>=3.6.3
|
||||
markupsafe>=3.0.2
|
||||
setproctitle>=1.3.5
|
||||
mistune>=3.1.3
|
||||
docutils>=0.22.2
|
||||
Flask-Limiter==4.1.1
|
||||
Flask-Cors==6.0.2
|
||||
bokeh==3.8.2
|
||||
markupsafe==3.0.3
|
||||
setproctitle==1.3.7
|
||||
mistune==3.2.0
|
||||
docutils==0.22.4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% block footer %}
|
||||
<footer id="island-footer">
|
||||
<div id="copyright-notice">Copyright 2025 Archipelago</div>
|
||||
<div id="copyright-notice">Copyright 2026 Archipelago</div>
|
||||
<div id="links">
|
||||
<a href="/sitemap">Site Map</a>
|
||||
-
|
||||
|
||||
@@ -20,11 +20,7 @@
|
||||
{% for file_name, file_data in tutorial_data.files.items() %}
|
||||
<li>
|
||||
<a href="{{ url_for("tutorial", game=world_name, file=file_name) }}">{{ file_data.language }}</a>
|
||||
by
|
||||
{% for author in file_data.authors %}
|
||||
{{ author }}
|
||||
{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
by {{ file_data.authors | join(", ") }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -56,9 +56,6 @@
|
||||
# Dark Souls III
|
||||
/worlds/dark_souls_3/ @Marechal-L @nex3
|
||||
|
||||
# Donkey Kong Country 3
|
||||
/worlds/dkc3/ @PoryGone
|
||||
|
||||
# DLCQuest
|
||||
/worlds/dlcquest/ @axe-y @agilbert1412
|
||||
|
||||
|
||||
@@ -69,12 +69,6 @@ flowchart LR
|
||||
end
|
||||
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
|
||||
subgraph Super Mario World
|
||||
SMW[SNES]
|
||||
|
||||
@@ -129,6 +129,42 @@ common_rule_only_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
|
||||
|
||||
The rule builder provides a `CachedRuleBuilderWorld` base class for your `World` class that enables caching on your rules.
|
||||
|
||||
@@ -131,7 +131,7 @@ Unless you configured PyCharm to use pytest as a test runner, you may get import
|
||||
edit the run configuration, and set the working directory to the Archipelago directory which contains all the project files.
|
||||
|
||||
If you only want to run your world's defined tests, repeat the steps for the test directory within your world.
|
||||
Your working directory should be the directory of your world in the worlds directory and the script should be the
|
||||
Your working directory should be the root Archipelago directory and the script should be the
|
||||
tests folder within your world.
|
||||
|
||||
You can also find the 'Archipelago Unittests' as an option in the dropdown at the top of the window
|
||||
|
||||
@@ -108,7 +108,6 @@ Example:
|
||||
```json
|
||||
{
|
||||
...
|
||||
"Donkey Kong Country 3":"f90acedcd958213f483a6a4c238e2a3faf92165e",
|
||||
"Factorio":"a699194a9589db3ebc0d821915864b422c782f44",
|
||||
...
|
||||
}
|
||||
|
||||
@@ -98,11 +98,6 @@ 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\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: "{#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: "";
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
colorama>=0.4.6
|
||||
websockets>=13.0.1,<14
|
||||
PyYAML>=6.0.3
|
||||
jellyfish>=1.2.1
|
||||
jinja2>=3.1.6
|
||||
schema>=0.7.8
|
||||
kivy>=2.3.1
|
||||
bsdiff4>=1.2.6
|
||||
platformdirs>=4.5.0
|
||||
certifi>=2025.11.12
|
||||
cython>=3.2.1
|
||||
cymem>=2.0.13
|
||||
orjson>=3.11.4
|
||||
typing_extensions>=4.15.0
|
||||
pyshortcuts>=1.9.6
|
||||
pathspec>=0.12.1
|
||||
colorama==0.4.6
|
||||
websockets==13.1 # ,<14
|
||||
PyYAML==6.0.3
|
||||
jellyfish==1.2.1
|
||||
jinja2==3.1.6
|
||||
schema==0.7.8
|
||||
kivy==2.3.1
|
||||
bsdiff4==1.2.6
|
||||
platformdirs==4.9.4
|
||||
certifi==2026.2.25
|
||||
cython==3.2.4
|
||||
cymem==2.0.13
|
||||
orjson==3.11.7
|
||||
typing_extensions==4.15.0
|
||||
pyshortcuts==1.9.7
|
||||
pathspec==1.0.4
|
||||
kivymd @ git+https://github.com/kivymd/KivyMD@5ff9d0d
|
||||
kivymd>=2.0.1.dev0
|
||||
|
||||
# Legacy world dependencies that custom worlds rely on
|
||||
Pymem>=1.13.0
|
||||
Pymem==1.14.0
|
||||
|
||||
162
rule_builder/field_resolvers.py
Normal file
162
rule_builder/field_resolvers.py
Normal file
@@ -0,0 +1,162 @@
|
||||
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
|
||||
}
|
||||
@@ -7,6 +7,7 @@ from typing_extensions import TypeVar, dataclass_transform, override
|
||||
from BaseClasses import CollectionState
|
||||
from NetUtils import JSONMessagePart
|
||||
|
||||
from .field_resolvers import FieldResolver, FieldResolverRegister, resolve_field
|
||||
from .options import OptionFilter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -108,11 +109,14 @@ class Rule(Generic[TWorld]):
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Returns a JSON compatible dict representation of this rule"""
|
||||
args = {
|
||||
field.name: getattr(self, field.name, None)
|
||||
for field in dataclasses.fields(self)
|
||||
if field.name not in ("options", "filtered_resolution")
|
||||
}
|
||||
args = {}
|
||||
for field in dataclasses.fields(self):
|
||||
if field.name in ("options", "filtered_resolution"):
|
||||
continue
|
||||
value = getattr(self, field.name, None)
|
||||
if isinstance(value, FieldResolver):
|
||||
value = value.to_dict()
|
||||
args[field.name] = value
|
||||
return {
|
||||
"rule": self.__class__.__qualname__,
|
||||
"options": [o.to_dict() for o in self.options],
|
||||
@@ -124,7 +128,19 @@ class Rule(Generic[TWorld]):
|
||||
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"""
|
||||
options = OptionFilter.multiple_from_dict(data.get("options", ()))
|
||||
return cls(**data.get("args", {}), options=options, filtered_resolution=data.get("filtered_resolution", False))
|
||||
args = cls._parse_field_resolvers(data.get("args", {}), world_cls.game)
|
||||
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]":
|
||||
"""Combines two rules or a rule and an option filter into an And rule"""
|
||||
@@ -688,24 +704,24 @@ class Filtered(WrapperRule[TWorld], game="Archipelago"):
|
||||
class Has(Rule[TWorld], game="Archipelago"):
|
||||
"""A rule that checks if the player has at least `count` of a given item"""
|
||||
|
||||
item_name: str
|
||||
item_name: str | FieldResolver
|
||||
"""The item to check for"""
|
||||
|
||||
count: int = 1
|
||||
count: int | FieldResolver = 1
|
||||
"""The count the player is required to have"""
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
return self.Resolved(
|
||||
self.item_name,
|
||||
self.count,
|
||||
resolve_field(self.item_name, world, str),
|
||||
count=resolve_field(self.count, world, int),
|
||||
player=world.player,
|
||||
caching_enabled=getattr(world, "rule_caching_enabled", False),
|
||||
)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
count = f", count={self.count}" if self.count > 1 else ""
|
||||
count = f", count={self.count}" if isinstance(self.count, FieldResolver) or self.count > 1 else ""
|
||||
options = f", options={self.options}" if self.options else ""
|
||||
return f"{self.__class__.__name__}({self.item_name}{count}{options})"
|
||||
|
||||
@@ -991,7 +1007,7 @@ class HasAny(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"""
|
||||
|
||||
item_counts: dict[str, int]
|
||||
item_counts: Mapping[str, int | FieldResolver]
|
||||
"""A mapping of item name to count to check for"""
|
||||
|
||||
@override
|
||||
@@ -1002,12 +1018,30 @@ class HasAllCounts(Rule[TWorld], game="Archipelago"):
|
||||
if len(self.item_counts) == 1:
|
||||
item = next(iter(self.item_counts))
|
||||
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(
|
||||
tuple(self.item_counts.items()),
|
||||
item_counts,
|
||||
player=world.player,
|
||||
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
|
||||
def __str__(self) -> str:
|
||||
items = ", ".join([f"{item} x{count}" for item, count in self.item_counts.items()])
|
||||
@@ -1096,7 +1130,7 @@ class HasAllCounts(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"""
|
||||
|
||||
item_counts: dict[str, int]
|
||||
item_counts: Mapping[str, int | FieldResolver]
|
||||
"""A mapping of item name to count to check for"""
|
||||
|
||||
@override
|
||||
@@ -1107,12 +1141,30 @@ class HasAnyCount(Rule[TWorld], game="Archipelago"):
|
||||
if len(self.item_counts) == 1:
|
||||
item = next(iter(self.item_counts))
|
||||
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(
|
||||
tuple(self.item_counts.items()),
|
||||
item_counts,
|
||||
player=world.player,
|
||||
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
|
||||
def __str__(self) -> str:
|
||||
items = ", ".join([f"{item} x{count}" for item, count in self.item_counts.items()])
|
||||
@@ -1204,13 +1256,13 @@ class HasFromList(Rule[TWorld], game="Archipelago"):
|
||||
item_names: tuple[str, ...]
|
||||
"""A tuple of item names to check for"""
|
||||
|
||||
count: int = 1
|
||||
count: int | FieldResolver = 1
|
||||
"""The number of items the player needs to have"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*item_names: str,
|
||||
count: int = 1,
|
||||
count: int | FieldResolver = 1,
|
||||
options: Iterable[OptionFilter] = (),
|
||||
filtered_resolution: bool = False,
|
||||
) -> None:
|
||||
@@ -1227,7 +1279,7 @@ class HasFromList(Rule[TWorld], game="Archipelago"):
|
||||
return Has(self.item_names[0], self.count).resolve(world)
|
||||
return self.Resolved(
|
||||
self.item_names,
|
||||
self.count,
|
||||
count=resolve_field(self.count, world, int),
|
||||
player=world.player,
|
||||
caching_enabled=getattr(world, "rule_caching_enabled", False),
|
||||
)
|
||||
@@ -1235,7 +1287,7 @@ class HasFromList(Rule[TWorld], game="Archipelago"):
|
||||
@override
|
||||
@classmethod
|
||||
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
|
||||
args = {**data.get("args", {})}
|
||||
args = cls._parse_field_resolvers(data.get("args", {}), world_cls.game)
|
||||
item_names = args.pop("item_names", ())
|
||||
options = OptionFilter.multiple_from_dict(data.get("options", ()))
|
||||
return cls(*item_names, **args, options=options, filtered_resolution=data.get("filtered_resolution", False))
|
||||
@@ -1338,13 +1390,13 @@ class HasFromListUnique(Rule[TWorld], game="Archipelago"):
|
||||
item_names: tuple[str, ...]
|
||||
"""A tuple of item names to check for"""
|
||||
|
||||
count: int = 1
|
||||
count: int | FieldResolver = 1
|
||||
"""The number of items the player needs to have"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*item_names: str,
|
||||
count: int = 1,
|
||||
count: int | FieldResolver = 1,
|
||||
options: Iterable[OptionFilter] = (),
|
||||
filtered_resolution: bool = False,
|
||||
) -> None:
|
||||
@@ -1354,14 +1406,15 @@ class HasFromListUnique(Rule[TWorld], game="Archipelago"):
|
||||
|
||||
@override
|
||||
def _instantiate(self, world: TWorld) -> Rule.Resolved:
|
||||
if len(self.item_names) == 0 or len(self.item_names) < self.count:
|
||||
count = resolve_field(self.count, world, int)
|
||||
if len(self.item_names) == 0 or len(self.item_names) < count:
|
||||
# match state.has_from_list_unique
|
||||
return False_().resolve(world)
|
||||
if len(self.item_names) == 1:
|
||||
return Has(self.item_names[0]).resolve(world)
|
||||
return self.Resolved(
|
||||
self.item_names,
|
||||
self.count,
|
||||
count,
|
||||
player=world.player,
|
||||
caching_enabled=getattr(world, "rule_caching_enabled", False),
|
||||
)
|
||||
@@ -1369,7 +1422,7 @@ class HasFromListUnique(Rule[TWorld], game="Archipelago"):
|
||||
@override
|
||||
@classmethod
|
||||
def from_dict(cls, data: Mapping[str, Any], world_cls: "type[World]") -> Self:
|
||||
args = {**data.get("args", {})}
|
||||
args = cls._parse_field_resolvers(data.get("args", {}), world_cls.game)
|
||||
item_names = args.pop("item_names", ())
|
||||
options = OptionFilter.multiple_from_dict(data.get("options", ()))
|
||||
return cls(*item_names, **args, options=options, filtered_resolution=data.get("filtered_resolution", False))
|
||||
@@ -1468,7 +1521,7 @@ class HasGroup(Rule[TWorld], game="Archipelago"):
|
||||
item_name_group: str
|
||||
"""The name of the item group containing the items"""
|
||||
|
||||
count: int = 1
|
||||
count: int | FieldResolver = 1
|
||||
"""The number of items the player needs to have"""
|
||||
|
||||
@override
|
||||
@@ -1477,14 +1530,14 @@ class HasGroup(Rule[TWorld], game="Archipelago"):
|
||||
return self.Resolved(
|
||||
self.item_name_group,
|
||||
item_names,
|
||||
self.count,
|
||||
count=resolve_field(self.count, world, int),
|
||||
player=world.player,
|
||||
caching_enabled=getattr(world, "rule_caching_enabled", False),
|
||||
)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
count = f", count={self.count}" if self.count > 1 else ""
|
||||
count = f", count={self.count}" if isinstance(self.count, FieldResolver) or self.count > 1 else ""
|
||||
options = f", options={self.options}" if self.options else ""
|
||||
return f"{self.__class__.__name__}({self.item_name_group}{count}{options})"
|
||||
|
||||
@@ -1542,7 +1595,7 @@ class HasGroupUnique(Rule[TWorld], game="Archipelago"):
|
||||
item_name_group: str
|
||||
"""The name of the item group containing the items"""
|
||||
|
||||
count: int = 1
|
||||
count: int | FieldResolver = 1
|
||||
"""The number of items the player needs to have"""
|
||||
|
||||
@override
|
||||
@@ -1551,14 +1604,14 @@ class HasGroupUnique(Rule[TWorld], game="Archipelago"):
|
||||
return self.Resolved(
|
||||
self.item_name_group,
|
||||
item_names,
|
||||
self.count,
|
||||
count=resolve_field(self.count, world, int),
|
||||
player=world.player,
|
||||
caching_enabled=getattr(world, "rule_caching_enabled", False),
|
||||
)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
count = f", count={self.count}" if self.count > 1 else ""
|
||||
count = f", count={self.count}" if isinstance(self.count, FieldResolver) or self.count > 1 else ""
|
||||
options = f", options={self.options}" if self.options else ""
|
||||
return f"{self.__class__.__name__}({self.item_name_group}{count}{options})"
|
||||
|
||||
|
||||
2
setup.py
2
setup.py
@@ -657,7 +657,7 @@ cx_Freeze.setup(
|
||||
options={
|
||||
"build_exe": {
|
||||
"packages": ["worlds", "kivy", "cymem", "websockets", "kivymd"],
|
||||
"includes": [],
|
||||
"includes": ["rule_builder.cached_world"],
|
||||
"excludes": ["numpy", "Cython", "PySide2", "PIL",
|
||||
"pandas"],
|
||||
"zip_includes": [],
|
||||
|
||||
@@ -6,8 +6,9 @@ from typing_extensions import override
|
||||
|
||||
from BaseClasses import CollectionState, Item, ItemClassification, Location, MultiWorld, Region
|
||||
from NetUtils import JSONMessagePart
|
||||
from Options import Choice, FreeText, Option, OptionSet, PerGameCommonOptions, Toggle
|
||||
from Options import Choice, FreeText, Option, OptionSet, PerGameCommonOptions, Range, Toggle
|
||||
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.rules import (
|
||||
And,
|
||||
@@ -59,12 +60,20 @@ class SetOption(OptionSet):
|
||||
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
|
||||
class RuleBuilderOptions(PerGameCommonOptions):
|
||||
toggle_option: ToggleOption
|
||||
choice_option: ChoiceOption
|
||||
text_option: FreeTextOption
|
||||
set_option: SetOption
|
||||
range_option: RangeOption
|
||||
|
||||
|
||||
GAME_NAME = "Rule Builder Test Game"
|
||||
@@ -659,14 +668,15 @@ class TestRules(RuleBuilderTestCase):
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
|
||||
def test_has_any_count(self) -> None:
|
||||
item_counts = {"Item 1": 1, "Item 2": 2}
|
||||
item_counts: dict[str, int | FieldResolver] = {"Item 1": 1, "Item 2": 2}
|
||||
rule = HasAnyCount(item_counts)
|
||||
resolved_rule = rule.resolve(self.world)
|
||||
self.world.register_rule_dependencies(resolved_rule)
|
||||
|
||||
for item_name, count in item_counts.items():
|
||||
item = self.world.create_item(item_name)
|
||||
for _ in range(count):
|
||||
num_items = resolve_field(count, self.world, int)
|
||||
for _ in range(num_items):
|
||||
self.assertFalse(resolved_rule(self.state))
|
||||
self.state.collect(item)
|
||||
self.assertTrue(resolved_rule(self.state))
|
||||
@@ -763,7 +773,7 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
|
||||
rule: ClassVar[Rule[Any]] = And(
|
||||
Or(
|
||||
Has("i1", count=4),
|
||||
Has("i1", count=FromOption(RangeOption)),
|
||||
HasFromList("i2", "i3", "i4", count=2),
|
||||
HasAnyCount({"i5": 2, "i6": 3}),
|
||||
options=[OptionFilter(ToggleOption, 0)],
|
||||
@@ -771,7 +781,7 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
Or(
|
||||
HasAll("i7", "i8"),
|
||||
HasAllCounts(
|
||||
{"i9": 1, "i10": 5},
|
||||
{"i9": 1, "i10": FromWorldAttr("instance_data.i10_count")},
|
||||
options=[OptionFilter(ToggleOption, 1, operator="ne")],
|
||||
filtered_resolution=True,
|
||||
),
|
||||
@@ -811,7 +821,14 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
"rule": "Has",
|
||||
"options": [],
|
||||
"filtered_resolution": False,
|
||||
"args": {"item_name": "i1", "count": 4},
|
||||
"args": {
|
||||
"item_name": "i1",
|
||||
"count": {
|
||||
"resolver": "FromOption",
|
||||
"option": "test.general.test_rule_builder.RangeOption",
|
||||
"field": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"rule": "HasFromList",
|
||||
@@ -848,7 +865,12 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
},
|
||||
],
|
||||
"filtered_resolution": True,
|
||||
"args": {"item_counts": {"i9": 1, "i10": 5}},
|
||||
"args": {
|
||||
"item_counts": {
|
||||
"i9": 1,
|
||||
"i10": {"resolver": "FromWorldAttr", "name": "instance_data.i10_count"},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"rule": "CanReachRegion",
|
||||
@@ -923,7 +945,7 @@ class TestSerialization(RuleBuilderTestCase):
|
||||
multiworld = setup_solo_multiworld(self.world_cls, steps=(), seed=0)
|
||||
world = multiworld.worlds[1]
|
||||
deserialized_rule = world.rule_from_dict(self.rule_dict)
|
||||
self.assertEqual(deserialized_rule, self.rule, str(deserialized_rule))
|
||||
self.assertEqual(deserialized_rule, self.rule, f"\n{deserialized_rule}\n{self.rule}")
|
||||
|
||||
|
||||
class TestExplain(RuleBuilderTestCase):
|
||||
@@ -1342,3 +1364,32 @@ class TestExplain(RuleBuilderTestCase):
|
||||
"& False)",
|
||||
)
|
||||
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}")
|
||||
|
||||
37
test/netutils/test_enum.py
Normal file
37
test/netutils/test_enum.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Verify that NetUtils' enums work correctly with all supported Python versions."""
|
||||
|
||||
import pickle
|
||||
import unittest
|
||||
from enum import Enum
|
||||
from typing import Type
|
||||
|
||||
from NetUtils import ClientStatus, HintStatus, SlotType
|
||||
from Utils import restricted_loads
|
||||
|
||||
|
||||
class Base:
|
||||
class DataEnumTest(unittest.TestCase):
|
||||
type: Type[Enum]
|
||||
value: Enum
|
||||
|
||||
def test_unpickle(self) -> None:
|
||||
"""Tests that enums used in multidata or multisave can be pickled and unpickled."""
|
||||
pickled = pickle.dumps(self.value)
|
||||
unpickled = restricted_loads(pickled)
|
||||
self.assertEqual(unpickled, self.value)
|
||||
self.assertIsInstance(unpickled, self.type)
|
||||
|
||||
|
||||
class HintStatusTest(Base.DataEnumTest):
|
||||
type = HintStatus
|
||||
value = HintStatus.HINT_AVOID
|
||||
|
||||
|
||||
class ClientStatusTest(Base.DataEnumTest):
|
||||
type = ClientStatus
|
||||
value = ClientStatus.CLIENT_GOAL
|
||||
|
||||
|
||||
class SlotTypeTest(Base.DataEnumTest):
|
||||
type = SlotType
|
||||
value = SlotType.player
|
||||
@@ -1,6 +1,8 @@
|
||||
import unittest
|
||||
|
||||
from Options import Choice, DefaultOnToggle, Toggle
|
||||
from collections import Counter
|
||||
|
||||
from Options import Choice, DefaultOnToggle, Toggle, OptionDict, OptionError, OptionSet, OptionList, OptionCounter
|
||||
|
||||
|
||||
class TestNumericOptions(unittest.TestCase):
|
||||
@@ -74,3 +76,97 @@ class TestNumericOptions(unittest.TestCase):
|
||||
self.assertTrue(toggle_string)
|
||||
self.assertTrue(toggle_int)
|
||||
self.assertTrue(toggle_alias)
|
||||
|
||||
|
||||
class TestContainerOptions(unittest.TestCase):
|
||||
def test_option_dict(self):
|
||||
class TestOptionDict(OptionDict):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_dict = {"D": "Foo"}
|
||||
test_option_dict = TestOptionDict(unknown_key_init_dict)
|
||||
self.assertRaises(OptionError, test_option_dict.verify_keys)
|
||||
|
||||
init_dict = {"A": "foo", "B": "bar"}
|
||||
test_option_dict = TestOptionDict(init_dict)
|
||||
|
||||
self.assertEqual(test_option_dict, init_dict) # Implicit value comparison
|
||||
self.assertEqual(test_option_dict["A"], "foo")
|
||||
self.assertIn("B", test_option_dict)
|
||||
self.assertNotIn("C", test_option_dict)
|
||||
self.assertRaises(KeyError, lambda: test_option_dict["C"])
|
||||
|
||||
def test_option_set(self):
|
||||
class TestOptionSet(OptionSet):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_set = {"D"}
|
||||
test_option_set = TestOptionSet(unknown_key_init_set)
|
||||
self.assertRaises(OptionError, test_option_set.verify_keys)
|
||||
|
||||
init_set = {"A", "B"}
|
||||
test_option_set = TestOptionSet(init_set)
|
||||
|
||||
self.assertEqual(test_option_set, init_set) # Implicit value comparison
|
||||
self.assertIn("B", test_option_set)
|
||||
self.assertNotIn("C", test_option_set)
|
||||
|
||||
def test_option_list(self):
|
||||
class TestOptionList(OptionList):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
unknown_key_init_list = ["D"]
|
||||
test_option_list = TestOptionList(unknown_key_init_list)
|
||||
self.assertRaises(OptionError, test_option_list.verify_keys)
|
||||
|
||||
init_list = ["A", "B"]
|
||||
test_option_list = TestOptionList(init_list)
|
||||
|
||||
self.assertEqual(test_option_list, init_list)
|
||||
self.assertIn("B", test_option_list)
|
||||
self.assertNotIn("C", test_option_list)
|
||||
|
||||
|
||||
def test_option_counter(self):
|
||||
class TestOptionCounter(OptionCounter):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
|
||||
max = 10
|
||||
min = 0
|
||||
|
||||
unknown_key_init_dict = {"D": 5}
|
||||
test_option_counter = TestOptionCounter(unknown_key_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_keys)
|
||||
|
||||
wrong_value_type_init_dict = {"A": "B"}
|
||||
self.assertRaises(TypeError, TestOptionCounter, wrong_value_type_init_dict)
|
||||
|
||||
violates_max_init_dict = {"A": 5, "B": 11}
|
||||
test_option_counter = TestOptionCounter(violates_max_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_values)
|
||||
|
||||
violates_min_init_dict = {"A": -1, "B": 5}
|
||||
test_option_counter = TestOptionCounter(violates_min_init_dict)
|
||||
self.assertRaises(OptionError, test_option_counter.verify_values)
|
||||
|
||||
init_dict = {"A": 0, "B": 10}
|
||||
test_option_counter = TestOptionCounter(init_dict)
|
||||
self.assertEqual(test_option_counter, Counter(init_dict))
|
||||
self.assertIn("A", test_option_counter)
|
||||
self.assertNotIn("C", test_option_counter)
|
||||
self.assertEqual(test_option_counter["A"], 0)
|
||||
self.assertEqual(test_option_counter["B"], 10)
|
||||
self.assertEqual(test_option_counter["C"], 0)
|
||||
|
||||
def test_culling_option_counter(self):
|
||||
class TestCullingCounter(OptionCounter):
|
||||
valid_keys = frozenset({"A", "B", "C"})
|
||||
cull_zeroes = True
|
||||
|
||||
init_dict = {"A": 0, "B": 10}
|
||||
test_option_counter = TestCullingCounter(init_dict)
|
||||
self.assertNotIn("A", test_option_counter)
|
||||
self.assertIn("B", test_option_counter)
|
||||
self.assertNotIn("C", test_option_counter)
|
||||
self.assertEqual(test_option_counter["A"], 0) # It's still a Counter! cull_zeroes is about "in" checks.
|
||||
self.assertEqual(test_option_counter, Counter({"B": 10}))
|
||||
|
||||
76
test/webhost/test_suuid.py
Normal file
76
test/webhost/test_suuid.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import math
|
||||
from typing import Any, Callable
|
||||
from typing_extensions import override
|
||||
from uuid import uuid4
|
||||
|
||||
from werkzeug.routing import BaseConverter
|
||||
|
||||
from . import TestBase
|
||||
|
||||
|
||||
class TestSUUID(TestBase):
|
||||
converter: BaseConverter
|
||||
filter: Callable[[Any], str]
|
||||
|
||||
@override
|
||||
def setUp(self) -> None:
|
||||
from werkzeug.routing import Map
|
||||
|
||||
super().setUp()
|
||||
self.converter = self.app.url_map.converters["suuid"](Map())
|
||||
self.filter = self.app.jinja_env.filters["suuid"] # type: ignore # defines how we use it, not what it can be
|
||||
|
||||
def test_is_reversible(self) -> None:
|
||||
u = uuid4()
|
||||
self.assertEqual(u, self.converter.to_python(self.converter.to_url(u)))
|
||||
s = "A" * 22 # uuid with all zeros
|
||||
self.assertEqual(s, self.converter.to_url(self.converter.to_python(s)))
|
||||
|
||||
def test_uuid_length(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("AAAA")
|
||||
|
||||
def test_padding(self) -> None:
|
||||
self.converter.to_python("A" * 22) # check that the correct value works
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 22 + "==") # converter should not allow padding
|
||||
|
||||
def test_empty(self) -> None:
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("")
|
||||
|
||||
def test_stray_equal_signs(self) -> None:
|
||||
self.converter.to_python("A" * 22) # check that the correct value works
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 22 + "==" + "AA") # the "==AA" should not be ignored, but error out
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python("A" * 20 + "==" + "AA") # the final "A"s should not be appended to the first "A"s
|
||||
|
||||
def test_stray_whitespace(self) -> None:
|
||||
s = "A" * 22
|
||||
self.converter.to_python(s) # check that the correct value works
|
||||
for char in " \t\r\n\v":
|
||||
for pos in (0, 11, 22):
|
||||
with self.subTest(char=char, pos=pos):
|
||||
s_with_whitespace = s[0:pos] + char * 4 + s[pos:] # insert 4 to make padding correct
|
||||
# check that the constructed s_with_whitespace is correct
|
||||
self.assertEqual(len(s_with_whitespace), len(s) + 4)
|
||||
self.assertEqual(s_with_whitespace[pos], char)
|
||||
# s_with_whitespace should be invalid as SUUID
|
||||
with self.assertRaises(ValueError):
|
||||
self.converter.to_python(s_with_whitespace)
|
||||
|
||||
def test_filter_returns_valid_string(self) -> None:
|
||||
u = uuid4()
|
||||
s = self.filter(u)
|
||||
self.assertIsInstance(s, str)
|
||||
self.assertNotIn("=", s)
|
||||
self.assertEqual(len(s), math.ceil(len(u.bytes) * 4 / 3))
|
||||
|
||||
def test_filter_is_same_as_converter(self) -> None:
|
||||
u = uuid4()
|
||||
self.assertEqual(self.filter(u), self.converter.to_url(u))
|
||||
|
||||
def test_filter_bad_type(self) -> None:
|
||||
with self.assertRaises(Exception): # currently the type is not checked directly, so any exception is valid
|
||||
self.filter(None)
|
||||
@@ -269,8 +269,9 @@ if not is_frozen():
|
||||
from Launcher import open_folder
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser("Build script for APWorlds")
|
||||
parser.add_argument("worlds", type=str, default=(), nargs="*", help="Names of APWorlds to build.")
|
||||
parser = argparse.ArgumentParser(prog="Build APWorlds", description="Build script for APWorlds")
|
||||
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)
|
||||
|
||||
if args.worlds:
|
||||
@@ -320,7 +321,9 @@ if not is_frozen():
|
||||
zf.write(pathlib.Path(world_directory, file), pathlib.Path(file_name, file))
|
||||
|
||||
zf.writestr(apworld.manifest_path, json.dumps(manifest))
|
||||
open_folder(apworlds_folder)
|
||||
|
||||
if not args.skip_open_folder:
|
||||
open_folder(apworlds_folder)
|
||||
|
||||
components.append(Component("Build APWorlds", func=_build_apworlds, cli=True,
|
||||
description="Build APWorlds from loose-file world folders."))
|
||||
|
||||
@@ -3,10 +3,10 @@ from Options import PerGameCommonOptions
|
||||
from .Locations import location_table, AdventureLocation, dragon_room_to_region
|
||||
|
||||
|
||||
def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
|
||||
def connect(multiworld: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True,
|
||||
one_way=False, name=None):
|
||||
source_region = world.get_region(source, player)
|
||||
target_region = world.get_region(target, player)
|
||||
source_region = multiworld.get_region(source, player)
|
||||
target_region = multiworld.get_region(target, player)
|
||||
|
||||
if name is None:
|
||||
name = source + " to " + target
|
||||
@@ -22,7 +22,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str, rule: call
|
||||
source_region.exits.append(connection)
|
||||
connection.connect(target_region)
|
||||
if not one_way:
|
||||
connect(world, player, target, source, rule, True)
|
||||
connect(multiworld, player, target, source, rule, True)
|
||||
|
||||
|
||||
def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player: int, dragon_rooms: []) -> None:
|
||||
|
||||
@@ -3,47 +3,47 @@ from worlds.generic.Rules import add_rule, set_rule, forbid_item
|
||||
|
||||
|
||||
def set_rules(self) -> None:
|
||||
world = self.multiworld
|
||||
multiworld = self.multiworld
|
||||
use_bat_logic = self.options.bat_logic.value == BatLogic.option_use_logic
|
||||
|
||||
set_rule(world.get_entrance("YellowCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("YellowCastlePort", self.player),
|
||||
lambda state: state.has("Yellow Key", self.player))
|
||||
set_rule(world.get_entrance("BlackCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("BlackCastlePort", self.player),
|
||||
lambda state: state.has("Black Key", self.player))
|
||||
set_rule(world.get_entrance("WhiteCastlePort", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastlePort", 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
|
||||
# be placed in a castle, which would require some additions to the rules when
|
||||
# use_bat_logic is true
|
||||
if not use_bat_logic:
|
||||
set_rule(world.get_entrance("WhiteCastleSecretPassage", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastleSecretPassage", self.player),
|
||||
lambda state: state.has("Bridge", self.player))
|
||||
set_rule(world.get_entrance("WhiteCastlePeekPassage", self.player),
|
||||
set_rule(multiworld.get_entrance("WhiteCastlePeekPassage", self.player),
|
||||
lambda state: state.has("Bridge", self.player) or
|
||||
state.has("Magnet", self.player))
|
||||
set_rule(world.get_entrance("BlackCastleVaultEntrance", self.player),
|
||||
set_rule(multiworld.get_entrance("BlackCastleVaultEntrance", self.player),
|
||||
lambda state: state.has("Bridge", self.player) or
|
||||
state.has("Magnet", self.player))
|
||||
|
||||
dragon_slay_check = self.options.dragon_slay_check.value
|
||||
if dragon_slay_check:
|
||||
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Yorgle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
set_rule(world.get_location("Slay Grundle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Grundle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
set_rule(world.get_location("Slay Rhindle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Rhindle", self.player),
|
||||
lambda state: state.has("Sword", self.player) and
|
||||
state.has("Right Difficulty Switch", self.player))
|
||||
else:
|
||||
set_rule(world.get_location("Slay Yorgle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Yorgle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
set_rule(world.get_location("Slay Grundle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Grundle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
set_rule(world.get_location("Slay Rhindle", self.player),
|
||||
set_rule(multiworld.get_location("Slay Rhindle", self.player),
|
||||
lambda state: state.has("Sword", self.player))
|
||||
|
||||
# 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
|
||||
# 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
|
||||
set_rule(world.get_entrance("CreditsWall", self.player),
|
||||
set_rule(multiworld.get_entrance("CreditsWall", self.player),
|
||||
lambda state: state.has("Bridge", self.player) and
|
||||
state.has("Black Key", self.player))
|
||||
|
||||
if not use_bat_logic:
|
||||
set_rule(world.get_entrance("CreditsToFarSide", self.player),
|
||||
set_rule(multiworld.get_entrance("CreditsToFarSide", self.player),
|
||||
lambda state: state.has("Magnet", self.player))
|
||||
|
||||
# bridge literally does not fit in this space, I think. I'll just exclude it
|
||||
forbid_item(world.get_location("Dungeon Vault", self.player), "Bridge", self.player)
|
||||
forbid_item(multiworld.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
|
||||
if not use_bat_logic:
|
||||
forbid_item(world.get_location("Dungeon Vault", self.player), "Magnet", self.player)
|
||||
forbid_item(world.get_location("Red Maze Vault Entrance", self.player), "Magnet", self.player)
|
||||
forbid_item(world.get_location("Credits Right Side", self.player), "Magnet", self.player)
|
||||
forbid_item(multiworld.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(multiworld.get_location("Credits Right Side", self.player), "Magnet", self.player)
|
||||
|
||||
# and obviously we don't want to start with the game already won
|
||||
forbid_item(world.get_location("Inside Yellow Castle", self.player), "Chalice", self.player)
|
||||
overworld = world.get_region("Overworld", self.player)
|
||||
forbid_item(multiworld.get_location("Inside Yellow Castle", self.player), "Chalice", self.player)
|
||||
overworld = multiworld.get_region("Overworld", self.player)
|
||||
|
||||
for loc in overworld.locations:
|
||||
forbid_item(loc, "Chalice", self.player)
|
||||
|
||||
add_rule(world.get_location("Chalice Home", self.player),
|
||||
add_rule(multiworld.get_location("Chalice Home", self.player),
|
||||
lambda state: state.has("Chalice", self.player) and state.has("Yellow Key", self.player))
|
||||
|
||||
# world.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY
|
||||
# multiworld.random.choice(overworld.locations).progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
# all_locations = world.get_locations(self.player).copy()
|
||||
# all_locations = multiworld.get_locations(self.player).copy()
|
||||
# while priority_count < get_num_items():
|
||||
# loc = world.random.choice(all_locations)
|
||||
# loc = multiworld.random.choice(all_locations)
|
||||
# if loc.progress_type == LocationProgressType.DEFAULT:
|
||||
# loc.progress_type = LocationProgressType.PRIORITY
|
||||
# priority_count += 1
|
||||
|
||||
@@ -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()}
|
||||
required_client_version: Tuple[int, int, int] = (0, 3, 9)
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
super().__init__(multiworld, player)
|
||||
self.rom_name: Optional[bytearray] = bytearray("", "utf8" )
|
||||
self.dragon_rooms: [int] = [0x14, 0x19, 0x4]
|
||||
self.dragon_slay_check: Optional[int] = 0
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
maseya-z3pr>=1.0.0rc1
|
||||
xxtea>=3.0.0
|
||||
maseya-z3pr==1.0.0rc1
|
||||
xxtea==3.7.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"game": "APQuest",
|
||||
"minimum_ap_version": "0.6.4",
|
||||
"world_version": "1.0.1",
|
||||
"minimum_ap_version": "0.6.7",
|
||||
"world_version": "2.0.0",
|
||||
"authors": ["NewSoupVi"]
|
||||
}
|
||||
|
||||
@@ -30,7 +30,10 @@
|
||||
C to fire available Confetti Cannons
|
||||
Number Keys + Backspace for Math Trap\n
|
||||
|
||||
Rebinding controls might be added in the future :)"""
|
||||
[b]Click to move also works![/b]
|
||||
|
||||
Click/tap Confetti Cannon to fire it
|
||||
Submit Math Trap solution in the command line at the bottom"""
|
||||
|
||||
<VolumeSliderView>:
|
||||
orientation: "horizontal"
|
||||
|
||||
@@ -4,8 +4,9 @@ from argparse import Namespace
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from CommonClient import CommonContext, gui_enabled, logger, server_loop
|
||||
from CommonClient import ClientCommandProcessor, CommonContext, logger, server_loop
|
||||
from NetUtils import ClientStatus
|
||||
from Utils import gui_enabled
|
||||
|
||||
from ..game.events import ConfettiFired, LocationClearedEvent, MathProblemSolved, MathProblemStarted, VictoryEvent
|
||||
from ..game.game import Game
|
||||
@@ -41,6 +42,16 @@ class ConnectionStatus(Enum):
|
||||
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):
|
||||
game = "APQuest"
|
||||
items_handling = 0b111 # full remote
|
||||
@@ -65,6 +76,7 @@ class APQuestContext(CommonContext):
|
||||
delay_intro_song: bool
|
||||
|
||||
ui: APQuestManager
|
||||
command_processor = APQuestClientCommandProcessor
|
||||
|
||||
def __init__(
|
||||
self, server_address: str | None = None, password: str | None = None, delay_intro_song: bool = False
|
||||
@@ -172,7 +184,6 @@ class APQuestContext(CommonContext):
|
||||
assert self.ap_quest_game is not None
|
||||
self.ap_quest_game.gameboard.fill_remote_location_content(remote_item_graphic_overrides)
|
||||
self.render()
|
||||
self.ui.game_view.bind_keyboard()
|
||||
|
||||
self.connection_status = ConnectionStatus.GAME_RUNNING
|
||||
self.ui.game_started()
|
||||
@@ -187,7 +198,7 @@ class APQuestContext(CommonContext):
|
||||
if self.ap_quest_game is None:
|
||||
raise RuntimeError("Tried to render before self.ap_quest_game was initialized.")
|
||||
|
||||
self.ui.render(self.ap_quest_game, self.player_sprite)
|
||||
self.ui.render(self.ap_quest_game, self.player_sprite, self.hard_mode)
|
||||
self.handle_game_events()
|
||||
|
||||
def location_checked_side_effects(self, location: int) -> None:
|
||||
@@ -244,6 +255,59 @@ class APQuestContext(CommonContext):
|
||||
self.ap_quest_game.input(input_key)
|
||||
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]":
|
||||
self.load_kv()
|
||||
return APQuestManager
|
||||
|
||||
@@ -4,29 +4,26 @@ from math import sqrt
|
||||
from random import choice, random
|
||||
from typing import Any
|
||||
|
||||
from kivy.core.window import Keyboard, Window
|
||||
from kivy.core.window import Window
|
||||
from kivy.graphics import Color, Triangle
|
||||
from kivy.graphics.instructions import Canvas
|
||||
from kivy.input import MotionEvent
|
||||
from kivy.uix.behaviors import ButtonBehavior
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
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 CommonClient import logger
|
||||
|
||||
from ..game.inputs import Input
|
||||
|
||||
|
||||
INPUT_MAP = {
|
||||
"up": Input.UP,
|
||||
INPUT_MAP_STR = {
|
||||
"w": Input.UP,
|
||||
"down": Input.DOWN,
|
||||
"s": Input.DOWN,
|
||||
"right": Input.RIGHT,
|
||||
"d": Input.RIGHT,
|
||||
"left": Input.LEFT,
|
||||
"a": Input.LEFT,
|
||||
"spacebar": Input.ACTION,
|
||||
" ": Input.ACTION,
|
||||
"c": Input.CONFETTI,
|
||||
"0": Input.ZERO,
|
||||
"1": Input.ONE,
|
||||
@@ -38,38 +35,52 @@ INPUT_MAP = {
|
||||
"7": Input.SEVEN,
|
||||
"8": Input.EIGHT,
|
||||
"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):
|
||||
_keyboard: Keyboard | None = None
|
||||
focused: int = 1
|
||||
input_function: Callable[[Input], None]
|
||||
|
||||
def __init__(self, input_function: Callable[[Input], None], **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self.input_function = input_function
|
||||
self.bind_keyboard()
|
||||
Window.bind(on_key_down=self._on_keyboard_down)
|
||||
Window.bind(on_touch_down=self.check_focus)
|
||||
self.opacity = 0.5
|
||||
|
||||
def on_touch_down(self, touch: MotionEvent) -> None:
|
||||
self.bind_keyboard()
|
||||
|
||||
def bind_keyboard(self) -> None:
|
||||
if self._keyboard is not None:
|
||||
def check_focus(self, _, touch, *args, **kwargs) -> None:
|
||||
if self.parent.collide_point(*touch.pos):
|
||||
self.focused += 1
|
||||
self.opacity = 1
|
||||
return
|
||||
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
|
||||
self._keyboard.bind(on_key_down=self._on_keyboard_down)
|
||||
|
||||
def _keyboard_closed(self) -> None:
|
||||
if self._keyboard is None:
|
||||
return
|
||||
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
|
||||
self._keyboard = None
|
||||
self.focused = 0
|
||||
self.opacity = 0.5
|
||||
|
||||
def _on_keyboard_down(self, _: Any, keycode: tuple[int, str], _1: Any, _2: Any) -> bool:
|
||||
if keycode[1] in INPUT_MAP:
|
||||
self.input_function(INPUT_MAP[keycode[1]])
|
||||
return True
|
||||
def force_focus(self) -> None:
|
||||
Window.release_keyboard()
|
||||
self.focused = 1
|
||||
self.opacity = 1
|
||||
|
||||
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):
|
||||
@@ -77,7 +88,7 @@ class APQuestGrid(GridLayout):
|
||||
parent_width, parent_height = self.parent.size
|
||||
|
||||
self_width_according_to_parent_height = parent_height * 12 / 11
|
||||
self_height_according_to_parent_width = parent_height * 11 / 12
|
||||
self_height_according_to_parent_width = parent_width * 11 / 12
|
||||
|
||||
if self_width_according_to_parent_height > parent_width:
|
||||
self.size = parent_width, self_height_according_to_parent_width
|
||||
@@ -203,13 +214,23 @@ class Confetti:
|
||||
return True
|
||||
|
||||
|
||||
class ConfettiView(MDRecycleView):
|
||||
class ConfettiView(Widget):
|
||||
confetti: list[Confetti]
|
||||
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(**kwargs)
|
||||
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:
|
||||
parent_width, parent_height = self.parent.size
|
||||
|
||||
@@ -254,3 +275,32 @@ class VolumeSliderView(BoxLayout):
|
||||
|
||||
class APQuestControlsView(BoxLayout):
|
||||
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
|
||||
|
||||
@@ -6,6 +6,7 @@ from kvui import GameManager, MDNavigationItemBase
|
||||
# isort: on
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from kivy._clock import ClockEvent
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.image import Image
|
||||
@@ -13,7 +14,16 @@ from kivy.uix.layout import Layout
|
||||
from kivymd.uix.recycleview import MDRecycleView
|
||||
|
||||
from ..game.game import Game
|
||||
from .custom_views import APQuestControlsView, APQuestGameView, APQuestGrid, ConfettiView, VolumeSliderView
|
||||
from ..game.graphics import Graphic
|
||||
from .custom_views import (
|
||||
APQuestControlsView,
|
||||
APQuestGameView,
|
||||
APQuestGrid,
|
||||
ConfettiView,
|
||||
TapIfConfettiCannonImage,
|
||||
TapImage,
|
||||
VolumeSliderView,
|
||||
)
|
||||
from .graphics import PlayerSprite, get_texture
|
||||
from .sounds import SoundManager
|
||||
|
||||
@@ -28,15 +38,17 @@ class APQuestManager(GameManager):
|
||||
lower_game_grid: GridLayout
|
||||
upper_game_grid: GridLayout
|
||||
|
||||
game_view: MDRecycleView
|
||||
game_view: MDRecycleView | None = None
|
||||
game_view_tab: MDNavigationItemBase
|
||||
|
||||
sound_manager: SoundManager
|
||||
|
||||
bottom_image_grid: list[list[Image]]
|
||||
top_image_grid: list[list[Image]]
|
||||
top_image_grid: list[list[TapImage]]
|
||||
confetti_view: ConfettiView
|
||||
|
||||
move_event: ClockEvent | None
|
||||
|
||||
bottom_grid_is_grass: bool
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
@@ -45,6 +57,7 @@ class APQuestManager(GameManager):
|
||||
self.sound_manager.allow_intro_to_play = not self.ctx.delay_intro_song
|
||||
self.top_image_grid = []
|
||||
self.bottom_image_grid = []
|
||||
self.move_event = None
|
||||
self.bottom_grid_is_grass = False
|
||||
|
||||
def allow_intro_song(self) -> None:
|
||||
@@ -71,25 +84,27 @@ class APQuestManager(GameManager):
|
||||
|
||||
def game_started(self) -> None:
|
||||
self.switch_to_game_tab()
|
||||
if self.game_view is not None:
|
||||
self.game_view.force_focus()
|
||||
self.sound_manager.game_started = True
|
||||
|
||||
def render(self, game: Game, player_sprite: PlayerSprite) -> None:
|
||||
self.setup_game_grid_if_not_setup(game.gameboard.size)
|
||||
def render(self, game: Game, player_sprite: PlayerSprite, hard_mode: bool) -> None:
|
||||
self.setup_game_grid_if_not_setup(game)
|
||||
|
||||
# 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, hard_mode)
|
||||
# Only now can we check whether a math problem is active
|
||||
self.render_background_game_grid(game.gameboard.size, game.active_math_problem is None)
|
||||
self.sound_manager.math_trap_active = game.active_math_problem is not None
|
||||
|
||||
self.render_item_column(game)
|
||||
|
||||
def render_gameboard(self, game: Game, player_sprite: PlayerSprite) -> None:
|
||||
def render_gameboard(self, game: Game, player_sprite: PlayerSprite, hard_mode: bool) -> None:
|
||||
rendered_gameboard = game.render()
|
||||
|
||||
for gameboard_row, image_row in zip(rendered_gameboard, self.top_image_grid, strict=False):
|
||||
for graphic, image in zip(gameboard_row, image_row[:11], strict=False):
|
||||
texture = get_texture(graphic, player_sprite)
|
||||
texture = get_texture(graphic, player_sprite, hard_mode)
|
||||
|
||||
if texture is None:
|
||||
image.opacity = 0
|
||||
@@ -104,6 +119,8 @@ class APQuestManager(GameManager):
|
||||
for item_graphic, image_row in zip(rendered_item_column, self.top_image_grid, strict=False):
|
||||
image = image_row[-1]
|
||||
|
||||
image.is_confetti_cannon = item_graphic == Graphic.CONFETTI_CANNON
|
||||
|
||||
texture = get_texture(item_graphic)
|
||||
if texture is None:
|
||||
image.opacity = 0
|
||||
@@ -136,23 +153,25 @@ class APQuestManager(GameManager):
|
||||
|
||||
self.bottom_grid_is_grass = grass
|
||||
|
||||
def setup_game_grid_if_not_setup(self, size: tuple[int, int]) -> None:
|
||||
def setup_game_grid_if_not_setup(self, game: Game) -> None:
|
||||
if self.upper_game_grid.children:
|
||||
return
|
||||
|
||||
self.top_image_grid = []
|
||||
self.bottom_image_grid = []
|
||||
|
||||
for _row in range(size[1]):
|
||||
size = game.gameboard.size
|
||||
|
||||
for row in range(size[1]):
|
||||
self.top_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))
|
||||
self.lower_game_grid.add_widget(bottom_image)
|
||||
self.bottom_image_grid[-1].append(bottom_image)
|
||||
|
||||
top_image = Image(fit_mode="fill")
|
||||
top_image = TapImage(lambda y=row, x=column: self.ctx.queue_auto_move(x, y), fit_mode="fill")
|
||||
self.upper_game_grid.add_widget(top_image)
|
||||
self.top_image_grid[-1].append(top_image)
|
||||
|
||||
@@ -160,11 +179,19 @@ class APQuestManager(GameManager):
|
||||
image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
|
||||
self.lower_game_grid.add_widget(image)
|
||||
|
||||
image2 = Image(fit_mode="fill", opacity=0)
|
||||
image2 = TapIfConfettiCannonImage(lambda: self.ctx.confetti_and_rerender(), fit_mode="fill", opacity=0)
|
||||
self.upper_game_grid.add_widget(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:
|
||||
container = super().build()
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import pkgutil
|
||||
from collections.abc import Buffer
|
||||
from enum import Enum
|
||||
from io import BytesIO
|
||||
from typing import Literal, NamedTuple, Protocol, cast
|
||||
|
||||
from kivy.uix.image import CoreImage
|
||||
from typing_extensions import Buffer
|
||||
|
||||
from CommonClient import logger
|
||||
|
||||
@@ -29,6 +29,7 @@ class RelatedTexture(NamedTuple):
|
||||
|
||||
|
||||
IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
||||
# Inanimates
|
||||
Graphic.WALL: RelatedTexture("inanimates.png", 16, 32, 16, 16),
|
||||
Graphic.BREAKABLE_BLOCK: RelatedTexture("inanimates.png", 32, 32, 16, 16),
|
||||
Graphic.CHEST: RelatedTexture("inanimates.png", 0, 16, 16, 16),
|
||||
@@ -37,29 +38,25 @@ IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
||||
Graphic.BUTTON_NOT_ACTIVATED: RelatedTexture("inanimates.png", 0, 0, 16, 16),
|
||||
Graphic.BUTTON_ACTIVATED: RelatedTexture("inanimates.png", 16, 0, 16, 16),
|
||||
Graphic.BUTTON_DOOR: RelatedTexture("inanimates.png", 32, 0, 16, 16),
|
||||
|
||||
# Enemies
|
||||
Graphic.NORMAL_ENEMY_1_HEALTH: RelatedTexture("normal_enemy.png", 0, 0, 16, 16),
|
||||
Graphic.NORMAL_ENEMY_2_HEALTH: RelatedTexture("normal_enemy.png", 16, 0, 16, 16),
|
||||
|
||||
Graphic.BOSS_5_HEALTH: RelatedTexture("boss.png", 16, 16, 16, 16),
|
||||
Graphic.BOSS_4_HEALTH: RelatedTexture("boss.png", 0, 16, 16, 16),
|
||||
Graphic.BOSS_3_HEALTH: RelatedTexture("boss.png", 32, 32, 16, 16),
|
||||
Graphic.BOSS_2_HEALTH: RelatedTexture("boss.png", 16, 32, 16, 16),
|
||||
Graphic.BOSS_1_HEALTH: RelatedTexture("boss.png", 0, 32, 16, 16),
|
||||
|
||||
# Items
|
||||
Graphic.EMPTY_HEART: RelatedTexture("hearts.png", 0, 0, 16, 16),
|
||||
Graphic.HEART: RelatedTexture("hearts.png", 16, 0, 16, 16),
|
||||
Graphic.HALF_HEART: RelatedTexture("hearts.png", 32, 0, 16, 16),
|
||||
|
||||
Graphic.REMOTE_ITEM: RelatedTexture("items.png", 0, 16, 16, 16),
|
||||
Graphic.CONFETTI_CANNON: RelatedTexture("items.png", 16, 16, 16, 16),
|
||||
Graphic.HAMMER: RelatedTexture("items.png", 32, 16, 16, 16),
|
||||
Graphic.KEY: RelatedTexture("items.png", 0, 0, 16, 16),
|
||||
Graphic.SHIELD: RelatedTexture("items.png", 16, 0, 16, 16),
|
||||
Graphic.SWORD: RelatedTexture("items.png", 32, 0, 16, 16),
|
||||
|
||||
Graphic.ITEMS_TEXT: "items_text.png",
|
||||
|
||||
# Numbers
|
||||
Graphic.ZERO: RelatedTexture("numbers.png", 0, 16, 16, 16),
|
||||
Graphic.ONE: RelatedTexture("numbers.png", 16, 16, 16, 16),
|
||||
Graphic.TWO: RelatedTexture("numbers.png", 32, 16, 16, 16),
|
||||
@@ -70,26 +67,29 @@ IMAGE_GRAPHICS: dict[Graphic, str | RelatedTexture] = {
|
||||
Graphic.SEVEN: RelatedTexture("numbers.png", 32, 0, 16, 16),
|
||||
Graphic.EIGHT: RelatedTexture("numbers.png", 48, 0, 16, 16),
|
||||
Graphic.NINE: RelatedTexture("numbers.png", 64, 0, 16, 16),
|
||||
|
||||
# Letters
|
||||
Graphic.LETTER_A: RelatedTexture("letters.png", 0, 16, 16, 16),
|
||||
Graphic.LETTER_E: RelatedTexture("letters.png", 16, 16, 16, 16),
|
||||
Graphic.LETTER_H: RelatedTexture("letters.png", 32, 16, 16, 16),
|
||||
Graphic.LETTER_I: RelatedTexture("letters.png", 0, 0, 16, 16),
|
||||
Graphic.LETTER_M: RelatedTexture("letters.png", 16, 0, 16, 16),
|
||||
Graphic.LETTER_T: RelatedTexture("letters.png", 32, 0, 16, 16),
|
||||
|
||||
# Mathematical symbols
|
||||
Graphic.DIVIDE: RelatedTexture("symbols.png", 0, 16, 16, 16),
|
||||
Graphic.EQUALS: RelatedTexture("symbols.png", 16, 16, 16, 16),
|
||||
Graphic.MINUS: RelatedTexture("symbols.png", 32, 16, 16, 16),
|
||||
Graphic.PLUS: RelatedTexture("symbols.png", 0, 0, 16, 16),
|
||||
Graphic.TIMES: RelatedTexture("symbols.png", 16, 0, 16, 16),
|
||||
# Other visual-only elements
|
||||
Graphic.ITEMS_TEXT: "items_text.png",
|
||||
Graphic.NO: RelatedTexture("symbols.png", 32, 0, 16, 16),
|
||||
|
||||
Graphic.UNKNOWN: RelatedTexture("symbols.png", 32, 0, 16, 16), # Same as "No"
|
||||
}
|
||||
|
||||
BACKGROUND_TILE = RelatedTexture("inanimates.png", 0, 32, 16, 16)
|
||||
|
||||
EASY_MODE_BOSS_2_HEALTH = RelatedTexture("boss.png", 16, 0, 16, 16)
|
||||
|
||||
|
||||
class PlayerSprite(Enum):
|
||||
HUMAN = 0
|
||||
@@ -160,13 +160,18 @@ def get_texture_by_identifier(texture_identifier: str | RelatedTexture) -> Textu
|
||||
return sub_texture
|
||||
|
||||
|
||||
def get_texture(graphic: Graphic | Literal["Grass"], player_sprite: PlayerSprite | None = None) -> Texture | None:
|
||||
def get_texture(
|
||||
graphic: Graphic | Literal["Grass"], player_sprite: PlayerSprite | None = None, hard_mode: bool = False
|
||||
) -> Texture | None:
|
||||
if graphic == Graphic.EMPTY:
|
||||
return None
|
||||
|
||||
if graphic == "Grass":
|
||||
return get_texture_by_identifier(BACKGROUND_TILE)
|
||||
|
||||
if graphic == Graphic.BOSS_2_HEALTH and not hard_mode:
|
||||
return get_texture_by_identifier(EASY_MODE_BOSS_2_HEALTH)
|
||||
|
||||
if graphic in IMAGE_GRAPHICS:
|
||||
return get_texture_by_identifier(IMAGE_GRAPHICS[graphic])
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import asyncio
|
||||
import pkgutil
|
||||
from asyncio import Task
|
||||
from collections.abc import Buffer
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from kivy import Config
|
||||
from kivy.core.audio import Sound, SoundLoader
|
||||
from typing_extensions import Buffer
|
||||
|
||||
from CommonClient import logger
|
||||
|
||||
@@ -85,7 +85,7 @@ class SoundManager:
|
||||
|
||||
def ensure_config(self) -> None:
|
||||
Config.adddefaultsection("APQuest")
|
||||
Config.setdefault("APQuest", "volume", 50)
|
||||
Config.setdefault("APQuest", "volume", 30)
|
||||
self.set_volume_percentage(Config.getint("APQuest", "volume"))
|
||||
|
||||
async def sound_manager_loop(self) -> None:
|
||||
@@ -149,6 +149,7 @@ class SoundManager:
|
||||
continue
|
||||
|
||||
if sound_name == audio_filename:
|
||||
sound.volume = self.volume_percentage / 100
|
||||
sound.play()
|
||||
self.update_background_music()
|
||||
higher_priority_sound_is_playing = True
|
||||
@@ -213,6 +214,7 @@ class SoundManager:
|
||||
# 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.
|
||||
if self.game_started and song.state == "stop":
|
||||
song.volume = self.current_background_music_volume * self.volume_percentage / 100
|
||||
song.play()
|
||||
song.seek(0)
|
||||
continue
|
||||
@@ -228,6 +230,7 @@ class SoundManager:
|
||||
|
||||
if self.current_background_music_volume != 0:
|
||||
if song.state == "stop":
|
||||
song.volume = self.current_background_music_volume * self.volume_percentage / 100
|
||||
song.play()
|
||||
song.seek(0)
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
- Die [APQuest-apworld](https://github.com/NewSoupVi/Archipelago/releases),
|
||||
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
|
||||
|
||||
Zuerst brauchst du einen Raum, mit dem du dich verbinden kannst.
|
||||
@@ -41,3 +46,15 @@ Du solltest jetzt verbunden sein und kannst APQuest spielen.
|
||||
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.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
- [The APQuest apworld](https://github.com/NewSoupVi/Archipelago/releases),
|
||||
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
|
||||
|
||||
First, you need a room to connect to. For this, you or someone you know has to generate a game.
|
||||
@@ -40,3 +45,14 @@ You should now be connected and able to play APQuest.
|
||||
The APQuest Client can seamlessly switch rooms without restarting.
|
||||
|
||||
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.
|
||||
|
||||
@@ -17,8 +17,10 @@ class Entity:
|
||||
|
||||
|
||||
class InteractableMixin:
|
||||
auto_move_attempt_passing_through = False
|
||||
|
||||
@abstractmethod
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
@@ -89,15 +91,16 @@ class Chest(Entity, InteractableMixin, LocationMixin):
|
||||
self.is_open = True
|
||||
self.update_solidity()
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
if self.has_given_content:
|
||||
return
|
||||
return False
|
||||
|
||||
if self.is_open:
|
||||
self.give_content(player)
|
||||
return
|
||||
return True
|
||||
|
||||
self.open()
|
||||
return True
|
||||
|
||||
def content_success(self) -> None:
|
||||
self.update_solidity()
|
||||
@@ -135,47 +138,59 @@ class Door(Entity):
|
||||
|
||||
|
||||
class KeyDoor(Door, InteractableMixin):
|
||||
auto_move_attempt_passing_through = True
|
||||
|
||||
closed_graphic = Graphic.KEY_DOOR
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
if self.is_open:
|
||||
return
|
||||
return False
|
||||
|
||||
if not player.has_item(Item.KEY):
|
||||
return
|
||||
return False
|
||||
|
||||
player.remove_item(Item.KEY)
|
||||
|
||||
self.open()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class BreakableBlock(Door, InteractableMixin):
|
||||
auto_move_attempt_passing_through = True
|
||||
|
||||
closed_graphic = Graphic.BREAKABLE_BLOCK
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
if self.is_open:
|
||||
return
|
||||
return False
|
||||
|
||||
if not player.has_item(Item.HAMMER):
|
||||
return
|
||||
return False
|
||||
|
||||
player.remove_item(Item.HAMMER)
|
||||
|
||||
self.open()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Bush(Door, InteractableMixin):
|
||||
auto_move_attempt_passing_through = True
|
||||
|
||||
closed_graphic = Graphic.BUSH
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
if self.is_open:
|
||||
return
|
||||
return False
|
||||
|
||||
if not player.has_item(Item.SWORD):
|
||||
return
|
||||
return False
|
||||
|
||||
self.open()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Button(Entity, InteractableMixin):
|
||||
solid = True
|
||||
@@ -186,12 +201,13 @@ class Button(Entity, InteractableMixin):
|
||||
def __init__(self, activates: ActivatableMixin) -> None:
|
||||
self.activates = activates
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
if self.activated:
|
||||
return
|
||||
return False
|
||||
|
||||
self.activated = True
|
||||
self.activates.activate(player)
|
||||
return True
|
||||
|
||||
@property
|
||||
def graphic(self) -> Graphic:
|
||||
@@ -240,9 +256,9 @@ class Enemy(Entity, InteractableMixin):
|
||||
return
|
||||
self.current_health = self.max_health
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
if self.dead:
|
||||
return
|
||||
return False
|
||||
|
||||
if player.has_item(Item.SWORD):
|
||||
self.current_health = max(0, self.current_health - 1)
|
||||
@@ -250,9 +266,10 @@ class Enemy(Entity, InteractableMixin):
|
||||
if self.current_health == 0:
|
||||
if not self.dead:
|
||||
self.die()
|
||||
return
|
||||
return True
|
||||
|
||||
player.damage(2)
|
||||
return True
|
||||
|
||||
@property
|
||||
def graphic(self) -> Graphic:
|
||||
@@ -270,13 +287,15 @@ class EnemyWithLoot(Enemy, LocationMixin):
|
||||
self.dead = True
|
||||
self.solid = not self.has_given_content
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
if self.dead:
|
||||
if not self.has_given_content:
|
||||
self.give_content(player)
|
||||
return
|
||||
return True
|
||||
return False
|
||||
|
||||
super().interact(player)
|
||||
return True
|
||||
|
||||
@property
|
||||
def graphic(self) -> Graphic:
|
||||
@@ -303,10 +322,12 @@ class FinalBoss(Enemy):
|
||||
}
|
||||
enemy_default_graphic = Graphic.BOSS_1_HEALTH
|
||||
|
||||
def interact(self, player: Player) -> None:
|
||||
def interact(self, player: Player) -> bool:
|
||||
dead_before = self.dead
|
||||
|
||||
super().interact(player)
|
||||
changed = super().interact(player)
|
||||
|
||||
if not dead_before and self.dead:
|
||||
player.victory()
|
||||
|
||||
return changed
|
||||
|
||||
@@ -23,6 +23,8 @@ class Game:
|
||||
active_math_problem: MathProblem | None
|
||||
active_math_problem_input: list[int] | None
|
||||
|
||||
auto_target_path: list[tuple[int, int]] = []
|
||||
|
||||
remotely_received_items: set[tuple[int, int, int]]
|
||||
|
||||
def __init__(
|
||||
@@ -32,6 +34,7 @@ class Game:
|
||||
self.gameboard = create_gameboard(hard_mode, hammer_exists, extra_chest)
|
||||
self.player = Player(self.gameboard, self.queued_events.append)
|
||||
self.active_math_problem = None
|
||||
self.active_math_problem_input = None
|
||||
self.remotely_received_items = set()
|
||||
|
||||
if random_object is None:
|
||||
@@ -94,29 +97,40 @@ class Game:
|
||||
|
||||
return tuple(graphics_array)
|
||||
|
||||
def attempt_player_movement(self, direction: Direction) -> None:
|
||||
def attempt_player_movement(self, direction: Direction, cancel_auto_move: bool = True) -> bool:
|
||||
if cancel_auto_move:
|
||||
self.cancel_auto_move()
|
||||
|
||||
self.player.facing = direction
|
||||
|
||||
delta_x, delta_y = direction.value
|
||||
new_x, new_y = self.player.current_x + delta_x, self.player.current_y + delta_y
|
||||
|
||||
if not self.gameboard.get_entity_at(new_x, new_y).solid:
|
||||
self.player.current_x = new_x
|
||||
self.player.current_y = new_y
|
||||
if self.gameboard.get_entity_at(new_x, new_y).solid:
|
||||
return False
|
||||
|
||||
def attempt_interact(self) -> None:
|
||||
self.player.current_x = new_x
|
||||
self.player.current_y = new_y
|
||||
return True
|
||||
|
||||
def attempt_interact(self) -> bool:
|
||||
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 = self.gameboard.get_entity_at(entity_x, entity_y)
|
||||
|
||||
if isinstance(entity, InteractableMixin):
|
||||
entity.interact(self.player)
|
||||
return entity.interact(self.player)
|
||||
|
||||
def attempt_fire_confetti_cannon(self) -> None:
|
||||
if self.player.has_item(Item.CONFETTI_CANNON):
|
||||
self.player.remove_item(Item.CONFETTI_CANNON)
|
||||
self.queued_events.append(ConfettiFired(self.player.current_x, self.player.current_y))
|
||||
return False
|
||||
|
||||
def attempt_fire_confetti_cannon(self) -> bool:
|
||||
if not self.player.has_item(Item.CONFETTI_CANNON):
|
||||
return False
|
||||
|
||||
self.player.remove_item(Item.CONFETTI_CANNON)
|
||||
self.queued_events.append(ConfettiFired(self.player.current_x, self.player.current_y))
|
||||
return True
|
||||
|
||||
def math_problem_success(self) -> None:
|
||||
self.active_math_problem = None
|
||||
@@ -154,6 +168,12 @@ class Game:
|
||||
self.active_math_problem_input.pop()
|
||||
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:
|
||||
if not self.gameboard.ready:
|
||||
return
|
||||
@@ -201,3 +221,47 @@ class Game:
|
||||
def force_clear_location(self, location_id: int) -> None:
|
||||
location = Location(location_id)
|
||||
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
|
||||
|
||||
@@ -15,6 +15,7 @@ from .entities import (
|
||||
EnemyWithLoot,
|
||||
Entity,
|
||||
FinalBoss,
|
||||
InteractableMixin,
|
||||
KeyDoor,
|
||||
LocationMixin,
|
||||
Wall,
|
||||
@@ -23,6 +24,7 @@ from .generate_math_problem import MathProblem
|
||||
from .graphics import DIGIT_TO_GRAPHIC, DIGIT_TO_GRAPHIC_ZERO_EMPTY, MATH_PROBLEM_TYPE_TO_GRAPHIC, Graphic
|
||||
from .items import Item
|
||||
from .locations import DEFAULT_CONTENT, Location
|
||||
from .path_finding import find_path_or_closest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .player import Player
|
||||
@@ -107,6 +109,21 @@ class Gameboard:
|
||||
|
||||
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(
|
||||
self, problem: MathProblem, current_input_digits: list[int], current_input_int: int | None
|
||||
) -> tuple[tuple[Graphic, ...], ...]:
|
||||
@@ -186,6 +203,23 @@ class Gameboard:
|
||||
entity = self.remote_entity_by_location_id[location]
|
||||
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
|
||||
def ready(self) -> bool:
|
||||
return self.content_filled
|
||||
@@ -212,7 +246,7 @@ def create_gameboard(hard_mode: bool, hammer_exists: bool, extra_chest: bool) ->
|
||||
breakable_block = BreakableBlock() if hammer_exists else Empty()
|
||||
|
||||
normal_enemy = EnemyWithLoot(2 if hard_mode else 1, Location.ENEMY_DROP)
|
||||
boss = FinalBoss(5 if hard_mode else 3)
|
||||
boss = FinalBoss(5 if hard_mode else 2)
|
||||
|
||||
gameboard = (
|
||||
(Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty(), Wall(), Empty(), Empty(), Empty()),
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import NamedTuple
|
||||
|
||||
_random = random.Random()
|
||||
|
||||
|
||||
class NumberChoiceConstraints(NamedTuple):
|
||||
num_1_min: int
|
||||
num_1_max: int
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 754 B |
84
worlds/apquest/game/path_finding.py
Normal file
84
worlds/apquest/game/path_finding.py
Normal file
@@ -0,0 +1,84 @@
|
||||
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 []
|
||||
@@ -2,12 +2,16 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import add_rule, set_rule
|
||||
from rule_builder.options import OptionFilter
|
||||
from rule_builder.rules import Has, HasAll, Rule
|
||||
|
||||
from .options import HardMode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .world import APQuestWorld
|
||||
|
||||
HAS_KEY = Has("Key") # Hmm, what could this be? A little foreshadowing perhaps? :) You'll find out if you keep reading!
|
||||
|
||||
|
||||
def set_all_rules(world: APQuestWorld) -> None:
|
||||
# In order for AP to generate an item layout that is actually possible for the player to complete,
|
||||
@@ -26,36 +30,46 @@ def set_all_entrance_rules(world: APQuestWorld) -> None:
|
||||
overworld_to_top_left_room = world.get_entrance("Overworld to Top Left Room")
|
||||
right_room_to_final_boss_room = world.get_entrance("Right Room to Final Boss Room")
|
||||
|
||||
# An access rule is a function. We can define this function like any other function.
|
||||
# This function must accept exactly one parameter: A "CollectionState".
|
||||
# A CollectionState describes the current progress of the players in the multiworld, i.e. what items they have,
|
||||
# which regions they've reached, etc.
|
||||
# In an access rule, we can ask whether the player has a collected a certain item.
|
||||
# We can do this via the state.has(...) function.
|
||||
# This function takes an item name, a player number, and an optional count parameter (more on that below)
|
||||
# Since a rule only takes a CollectionState parameter, but we also need the player number in the state.has call,
|
||||
# our function needs to be locally defined so that it has access to the player number from the outer scope.
|
||||
# In our case, we are inside a function that has access to the "world" parameter, so we can use world.player.
|
||||
def can_destroy_bush(state: CollectionState) -> bool:
|
||||
return state.has("Sword", world.player)
|
||||
# Now, let's make some rules!
|
||||
# First, let's handle the transition from the overworld to the bottom right room,
|
||||
# which requires slashing a bush with the Sword.
|
||||
# For this, we need a rule that says "player has a Sword".
|
||||
# We can use a "Has"-type rule from the rule_builder module for this.
|
||||
can_destroy_bush = Has("Sword")
|
||||
|
||||
# Now we can set our "can_destroy_bush" rule to our entrance which requires slashing a bush to clear the path.
|
||||
# One way to set rules is via the set_rule() function, which works on both Entrances and Locations.
|
||||
set_rule(overworld_to_bottom_right_room, can_destroy_bush)
|
||||
# Now we can set our "can_destroy_bush" rule to the entrance which requires slashing a bush to clear the path.
|
||||
# The easiest way to do this is by calling world.set_rule, which works for both Locations and Entrances.
|
||||
world.set_rule(overworld_to_bottom_right_room, can_destroy_bush)
|
||||
|
||||
# Because the function has to be defined locally, most worlds prefer the lambda syntax.
|
||||
set_rule(overworld_to_top_left_room, lambda state: state.has("Key", world.player))
|
||||
|
||||
# Conditions can depend on event items.
|
||||
set_rule(right_room_to_final_boss_room, lambda state: state.has("Top Left Room Button Pressed", world.player))
|
||||
# Conditions can also depend on event items.
|
||||
button_pressed = Has("Top Left Room Button Pressed")
|
||||
world.set_rule(right_room_to_final_boss_room, button_pressed)
|
||||
|
||||
# Some entrance rules may only apply if the player enabled certain options.
|
||||
# In our case, if the hammer option is enabled, we need to add the Hammer requirement to the Entrance from
|
||||
# Overworld to the Top Middle Room.
|
||||
if world.options.hammer:
|
||||
overworld_to_top_middle_room = world.get_entrance("Overworld to Top Middle Room")
|
||||
set_rule(overworld_to_top_middle_room, lambda state: state.has("Hammer", world.player))
|
||||
can_smash_brick = Has("Hammer")
|
||||
world.set_rule(overworld_to_top_middle_room, can_smash_brick)
|
||||
|
||||
# So far, we've been using "Has" from the Rule Builder to make our rules.
|
||||
# There is another way to make rules that you will see in a lot of older worlds.
|
||||
# A rule can just be a function that takes a "state" argument and returns a bool.
|
||||
# As a demonstration of what that looks like, let's do it with our final Entrance rule:
|
||||
world.set_rule(overworld_to_top_left_room, lambda state: state.has("Key", world.player))
|
||||
# This style is not really recommended anymore, though.
|
||||
# Notice how you have to explicitly capture world.player here so that the rule applies to the correct player?
|
||||
# Well, Rule Builder does this part for you, inside of world.set_rule.
|
||||
# This doesn't just result in shorter code, it also means you can define rules statically (at the module level).
|
||||
# APQuest opts to create its Rule objects locally, but just to show what this would look like,
|
||||
# we'll re-set the "Overworld to Top Left Room" rule to a constant defined at the top of this file:
|
||||
world.set_rule(overworld_to_top_left_room, HAS_KEY)
|
||||
|
||||
# Beyond these structural advantages,
|
||||
# Rule Builder also allows the core AP code to do a lot of under-the-hood optimizations.
|
||||
# Rule Builder is quite comprehensive, and even if you have really esoteric rules,
|
||||
# you can make custom rules by subclassing CustomRule.
|
||||
|
||||
def set_all_location_rules(world: APQuestWorld) -> None:
|
||||
# Location rules work no differently from Entrance rules.
|
||||
@@ -67,65 +81,72 @@ def set_all_location_rules(world: APQuestWorld) -> None:
|
||||
# So, we need to set requirements on the Locations themselves.
|
||||
# Since combat is a bit more complicated, we'll use this chance to cover some advanced access rule concepts.
|
||||
|
||||
# Sometimes, you may want to have different rules depending on the player's chosen options.
|
||||
# There is a wrong way to do this, and a right way to do this. Let's do the wrong way first.
|
||||
# In "set_all_entrance_rules", we had a rule for a location that doesn't always exist.
|
||||
# In this case, we had to check for its existence (by checking the player's chosen options) before setting the rule.
|
||||
# Other times, you may have a situation where a location can have two different rules depending on the options.
|
||||
# In our case, the enemy in the right room has more health if hard mode is selected,
|
||||
# so ontop of the Sword, the player will either need one more health or a Shield in hard mode.
|
||||
# First, let's make our sword condition.
|
||||
can_defeat_basic_enemy: Rule = Has("Sword")
|
||||
|
||||
# Next, we'll check whether hard mode has been chosen in the player options.
|
||||
if world.options.hard_mode:
|
||||
# We'll make the condition for "Has a Shield or a Health Upgrade".
|
||||
# We can chain two "Has" conditions together with the | operator to make "Has Shield or has Health Upgrade".
|
||||
can_withstand_a_hit = Has("Shield") | Has("Health Upgrade")
|
||||
|
||||
# Now, we chain this rule to our Sword rule.
|
||||
# Since we want both conditions to be true, in this case, we have to chain them in an "and" way.
|
||||
# For this, we can use the & operator.
|
||||
can_defeat_basic_enemy = can_defeat_basic_enemy & can_withstand_a_hit
|
||||
|
||||
# Finally, we set our rule onto the Right Room Eney Drop location.
|
||||
right_room_enemy = world.get_location("Right Room Enemy Drop")
|
||||
world.set_rule(right_room_enemy, can_defeat_basic_enemy)
|
||||
|
||||
# DON'T DO THIS!!!!
|
||||
set_rule(
|
||||
right_room_enemy,
|
||||
lambda state: (
|
||||
state.has("Sword", world.player)
|
||||
and (not world.options.hard_mode or state.has_any(("Shield", "Health Upgrade"), world.player))
|
||||
),
|
||||
)
|
||||
# DON'T DO THIS!!!!
|
||||
# For the final boss, we also need to chain multiple conditions.
|
||||
# First of all, you always need a Sword and a Shield.
|
||||
# So far, we used the | and & operators to chain "Has" rules.
|
||||
# Instead, we can also use HasAny for an or-chain of items, or HasAll for an and-chain of items.
|
||||
has_sword_and_shield: Rule = HasAll("Sword", "Shield")
|
||||
|
||||
# Now, what's actually wrong with this? It works perfectly fine, right?
|
||||
# If hard mode disabled, Sword is enough. If hard mode is enabled, we also need a Shield or a Health Upgrade.
|
||||
# The access rule we just wrote does this correctly, so what's the problem?
|
||||
# The problem is performance.
|
||||
# Most of your world code doesn't need to be perfectly performant, since it just runs once per slot.
|
||||
# However, access rules in particular are by far the hottest code path in Archipelago.
|
||||
# An access rule will potentially be called thousands or even millions of times over the course of one generation.
|
||||
# As a result, access rules are the one place where it's really worth putting in some effort to optimize.
|
||||
# What's the performance problem here?
|
||||
# Every time our access rule is called, it has to evaluate whether world.options.hard_mode is True or False.
|
||||
# Wouldn't it be better if in easy mode, the access rule only checked for Sword to begin with?
|
||||
# Wouldn't it also be better if in hard mode, it already knew it had to check Shield and Health Upgrade as well?
|
||||
# Well, we can achieve this by doing the "if world.options.hard_mode" check outside the set_rule call,
|
||||
# and instead having two *different* set_rule calls depending on which case we're in.
|
||||
# In hard mode, the player also needs both Health Upgrades to survive long enough to defeat the boss.
|
||||
# For this, we can use the optional "count" parameter for "Has".
|
||||
has_both_health_upgrades = Has("Health Upgrade", count=2)
|
||||
|
||||
if world.options.hard_mode:
|
||||
# If you have multiple conditions, you can obviously chain them via "or" or "and".
|
||||
# However, there are also the nice helper functions "state.has_any" and "state.has_all".
|
||||
set_rule(
|
||||
right_room_enemy,
|
||||
lambda state: (
|
||||
state.has("Sword", world.player) and state.has_any(("Shield", "Health Upgrade"), world.player)
|
||||
),
|
||||
)
|
||||
else:
|
||||
set_rule(right_room_enemy, lambda state: state.has("Sword", world.player))
|
||||
# Previously, we used an "if world.options.hard_mode" condition to check if we should apply the extra requirement.
|
||||
# However, if you're comfortable with boolean logic, there is another way.
|
||||
# OptionFilter is a rule component which isn't a "Rule" on its own, but when used in a boolean expression with
|
||||
# rules, it acts like True if the option has the specified value, and acts like False otherwise.
|
||||
hard_mode_is_off = OptionFilter(HardMode, False)
|
||||
|
||||
# Another way to chain multiple conditions is via the add_rule function.
|
||||
# This makes the access rules a bit slower though, so it should only be used if your structure justifies it.
|
||||
# In our case, it's pretty useful because hard mode and easy mode have different requirements.
|
||||
# So with this option-checking rule component in hand, we can write our boss condition like this:
|
||||
can_defeat_final_boss = has_sword_and_shield & (hard_mode_is_off | has_both_health_upgrades)
|
||||
# If you're not as comfortable with boolean logic, it might be somewhat confusing why this is correct.
|
||||
# There is nothing wrong with using "if" conditions to check for options, if you find that easier to understand.
|
||||
|
||||
# Finally, we apply the rule to our "Final Boss Defeated" event location.
|
||||
final_boss = world.get_location("Final Boss Defeated")
|
||||
|
||||
# For the "known" requirements, it's still better to chain them using a normal "and" condition.
|
||||
add_rule(final_boss, lambda state: state.has_all(("Sword", "Shield"), world.player))
|
||||
|
||||
if world.options.hard_mode:
|
||||
# You can check for multiple copies of an item by using the optional count parameter of state.has().
|
||||
add_rule(final_boss, lambda state: state.has("Health Upgrade", world.player, 2))
|
||||
world.set_rule(final_boss, can_defeat_final_boss)
|
||||
|
||||
|
||||
def set_completion_condition(world: APQuestWorld) -> None:
|
||||
# Finally, we need to set a completion condition for our world, defining what the player needs to win the game.
|
||||
# For this, we can use world.set_completion_rule.
|
||||
# You can just set a completion condition directly like any other condition, referencing items the player receives:
|
||||
world.multiworld.completion_condition[world.player] = lambda state: state.has_all(("Sword", "Shield"), world.player)
|
||||
world.set_completion_rule(HasAll("Sword", "Shield"))
|
||||
|
||||
# In our case, we went for the Victory event design pattern (see create_events() in locations.py).
|
||||
# So lets undo what we just did, and instead set the completion condition to:
|
||||
world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player)
|
||||
world.set_completion_rule(Has("Victory"))
|
||||
|
||||
|
||||
# One final comment about rules:
|
||||
# If your world exclusively uses Rule Builder rules (like APQuest), it's worth trying CachedRuleBuilderWorld.
|
||||
# CachedRuleBuilderWorld is a subclass of World that has a bunch of caching magic to make rules faster.
|
||||
# Just have your world class subclass CachedRuleBuilderWorld instead of World:
|
||||
# class APQuestWorld(CachedRuleBuilderWorld): ...
|
||||
# This may speed up your world, or it may make it slower.
|
||||
# The exact factors are complex and not well understood, but there is no harm in trying it.
|
||||
# Generate a few seeds and see if there is a noticeable difference!
|
||||
# If you're wondering, author has checked: APQuest is too simple to see any benefits, so we'll stick with "World".
|
||||
|
||||
@@ -271,7 +271,7 @@ item_table = {
|
||||
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.JELLY_EGG: ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed
|
||||
ItemNames.URCHIN_COSTUME: ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
|
||||
ItemNames.URCHIN_COSTUME: ItemData(698034, 1, ItemType.PROGRESSION, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
|
||||
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.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.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.JELLY_EGG, ItemNames.URCHIN_COSTUME, ItemNames.BABY_WALKER,
|
||||
ItemNames.RAINBOW_MUSHROOM, ItemNames.RAINBOW_MUSHROOM, ItemNames.RAINBOW_MUSHROOM,
|
||||
ItemNames.JELLY_EGG, ItemNames.BABY_WALKER, ItemNames.RAINBOW_MUSHROOM,
|
||||
ItemNames.RAINBOW_MUSHROOM, ItemNames.RAINBOW_MUSHROOM, ItemNames.FISH_OIL,
|
||||
ItemNames.LEAF_POULTICE, ItemNames.LEAF_POULTICE, ItemNames.LEAF_POULTICE,
|
||||
ItemNames.LEECHING_POULTICE, ItemNames.LEECHING_POULTICE, ItemNames.ARCANE_POULTICE,
|
||||
ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT, ItemNames.ROTTEN_MEAT,
|
||||
|
||||
@@ -37,7 +37,7 @@ def _has_li(state: CollectionState, player: int) -> bool:
|
||||
DAMAGING_ITEMS:Iterable[str] = [
|
||||
ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM,
|
||||
ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA,
|
||||
ItemNames.BABY_BLASTER
|
||||
ItemNames.BABY_BLASTER, ItemNames.URCHIN_COSTUME
|
||||
]
|
||||
|
||||
def _has_damaging_item(state: CollectionState, player: int, damaging_items:Iterable[str] = DAMAGING_ITEMS) -> bool:
|
||||
|
||||
@@ -76,7 +76,7 @@ class AquariaWorld(World):
|
||||
item_name_groups = {
|
||||
"Damage": {ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM,
|
||||
ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA,
|
||||
ItemNames.BABY_BLASTER},
|
||||
ItemNames.BABY_BLASTER, ItemNames.URCHIN_COSTUME},
|
||||
"Light": {ItemNames.SUN_FORM, ItemNames.BABY_DUMBO}
|
||||
}
|
||||
"""Grouping item make it easier to find them"""
|
||||
|
||||
@@ -116,7 +116,7 @@ def versum_hill_rave(state: CollectionState, player: int, limit: bool, glitched:
|
||||
else:
|
||||
return (
|
||||
graffitiL(state, player, limit, 85)
|
||||
and graffitiXL(state, player, limit, 48)
|
||||
and graffitiXL(state, player, limit, 49)
|
||||
)
|
||||
else:
|
||||
return (
|
||||
|
||||
5
worlds/bomb_rush_cyberfunk/archipelago.json
Normal file
5
worlds/bomb_rush_cyberfunk/archipelago.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"game": "Bomb Rush Cyberfunk",
|
||||
"world_version": "1.0.6",
|
||||
"authors": ["TRPG"]
|
||||
}
|
||||
@@ -16,213 +16,213 @@ from .Locations import (
|
||||
)
|
||||
|
||||
|
||||
def create_regions(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
menu_region = Region("Menu", player, world, "Aranearum")
|
||||
world.regions.append(menu_region)
|
||||
def create_regions(multiworld: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
menu_region = Region("Menu", player, multiworld, "Aranearum")
|
||||
multiworld.regions.append(menu_region)
|
||||
|
||||
start_camp_region = Region("Start Camp", player, world)
|
||||
start_camp_region = Region("Start Camp", player, multiworld)
|
||||
start_camp_region.add_locations(loc_start_camp, CCCharlesLocation)
|
||||
world.regions.append(start_camp_region)
|
||||
multiworld.regions.append(start_camp_region)
|
||||
|
||||
tony_tiddle_mission_region = Region("Tony Tiddle Mission", player, world)
|
||||
tony_tiddle_mission_region = Region("Tony Tiddle Mission", player, multiworld)
|
||||
tony_tiddle_mission_region.add_locations(loc_tony_tiddle_mission, CCCharlesLocation)
|
||||
world.regions.append(tony_tiddle_mission_region)
|
||||
multiworld.regions.append(tony_tiddle_mission_region)
|
||||
|
||||
barn_region = Region("Barn", player, world)
|
||||
barn_region = Region("Barn", player, multiworld)
|
||||
barn_region.add_locations(loc_barn, CCCharlesLocation)
|
||||
world.regions.append(barn_region)
|
||||
multiworld.regions.append(barn_region)
|
||||
|
||||
candice_mission_region = Region("Candice Mission", player, world)
|
||||
candice_mission_region = Region("Candice Mission", player, multiworld)
|
||||
candice_mission_region.add_locations(loc_candice_mission, CCCharlesLocation)
|
||||
world.regions.append(candice_mission_region)
|
||||
multiworld.regions.append(candice_mission_region)
|
||||
|
||||
tutorial_house_region = Region("Tutorial House", player, world)
|
||||
tutorial_house_region = Region("Tutorial House", player, multiworld)
|
||||
tutorial_house_region.add_locations(loc_tutorial_house, CCCharlesLocation)
|
||||
world.regions.append(tutorial_house_region)
|
||||
multiworld.regions.append(tutorial_house_region)
|
||||
|
||||
swamp_edges_region = Region("Swamp Edges", player, world)
|
||||
swamp_edges_region = Region("Swamp Edges", player, multiworld)
|
||||
swamp_edges_region.add_locations(loc_swamp_edges, CCCharlesLocation)
|
||||
world.regions.append(swamp_edges_region)
|
||||
multiworld.regions.append(swamp_edges_region)
|
||||
|
||||
swamp_mission_region = Region("Swamp Mission", player, world)
|
||||
swamp_mission_region = Region("Swamp Mission", player, multiworld)
|
||||
swamp_mission_region.add_locations(loc_swamp_mission, CCCharlesLocation)
|
||||
world.regions.append(swamp_mission_region)
|
||||
multiworld.regions.append(swamp_mission_region)
|
||||
|
||||
junkyard_area_region = Region("Junkyard Area", player, world)
|
||||
junkyard_area_region = Region("Junkyard Area", player, multiworld)
|
||||
junkyard_area_region.add_locations(loc_junkyard_area, CCCharlesLocation)
|
||||
world.regions.append(junkyard_area_region)
|
||||
multiworld.regions.append(junkyard_area_region)
|
||||
|
||||
south_house_region = Region("South House", player, world)
|
||||
south_house_region = Region("South House", player, multiworld)
|
||||
south_house_region.add_locations(loc_south_house, CCCharlesLocation)
|
||||
world.regions.append(south_house_region)
|
||||
multiworld.regions.append(south_house_region)
|
||||
|
||||
junkyard_shed_region = Region("Junkyard Shed", player, world)
|
||||
junkyard_shed_region = Region("Junkyard Shed", player, multiworld)
|
||||
junkyard_shed_region.add_locations(loc_junkyard_shed, CCCharlesLocation)
|
||||
world.regions.append(junkyard_shed_region)
|
||||
multiworld.regions.append(junkyard_shed_region)
|
||||
|
||||
military_base_region = Region("Military Base", player, world)
|
||||
military_base_region = Region("Military Base", player, multiworld)
|
||||
military_base_region.add_locations(loc_military_base, CCCharlesLocation)
|
||||
world.regions.append(military_base_region)
|
||||
multiworld.regions.append(military_base_region)
|
||||
|
||||
south_mine_outside_region = Region("South Mine Outside", player, world)
|
||||
south_mine_outside_region = Region("South Mine Outside", player, multiworld)
|
||||
south_mine_outside_region.add_locations(loc_south_mine_outside, CCCharlesLocation)
|
||||
world.regions.append(south_mine_outside_region)
|
||||
multiworld.regions.append(south_mine_outside_region)
|
||||
|
||||
south_mine_inside_region = Region("South Mine Inside", player, world)
|
||||
south_mine_inside_region = Region("South Mine Inside", player, multiworld)
|
||||
south_mine_inside_region.add_locations(loc_south_mine_inside, CCCharlesLocation)
|
||||
world.regions.append(south_mine_inside_region)
|
||||
multiworld.regions.append(south_mine_inside_region)
|
||||
|
||||
middle_station_region = Region("Middle Station", player, world)
|
||||
middle_station_region = Region("Middle Station", player, multiworld)
|
||||
middle_station_region.add_locations(loc_middle_station, CCCharlesLocation)
|
||||
world.regions.append(middle_station_region)
|
||||
multiworld.regions.append(middle_station_region)
|
||||
|
||||
canyon_region = Region("Canyon", player, world)
|
||||
canyon_region = Region("Canyon", player, multiworld)
|
||||
canyon_region.add_locations(loc_canyon, CCCharlesLocation)
|
||||
world.regions.append(canyon_region)
|
||||
multiworld.regions.append(canyon_region)
|
||||
|
||||
watchtower_region = Region("Watchtower", player, world)
|
||||
watchtower_region = Region("Watchtower", player, multiworld)
|
||||
watchtower_region.add_locations(loc_watchtower, CCCharlesLocation)
|
||||
world.regions.append(watchtower_region)
|
||||
multiworld.regions.append(watchtower_region)
|
||||
|
||||
boulder_field_region = Region("Boulder Field", player, world)
|
||||
boulder_field_region = Region("Boulder Field", player, multiworld)
|
||||
boulder_field_region.add_locations(loc_boulder_field, CCCharlesLocation)
|
||||
world.regions.append(boulder_field_region)
|
||||
multiworld.regions.append(boulder_field_region)
|
||||
|
||||
haunted_house_region = Region("Haunted House", player, world)
|
||||
haunted_house_region = Region("Haunted House", player, multiworld)
|
||||
haunted_house_region.add_locations(loc_haunted_house, CCCharlesLocation)
|
||||
world.regions.append(haunted_house_region)
|
||||
multiworld.regions.append(haunted_house_region)
|
||||
|
||||
santiago_house_region = Region("Santiago House", player, world)
|
||||
santiago_house_region = Region("Santiago House", player, multiworld)
|
||||
santiago_house_region.add_locations(loc_santiago_house, CCCharlesLocation)
|
||||
world.regions.append(santiago_house_region)
|
||||
multiworld.regions.append(santiago_house_region)
|
||||
|
||||
port_region = Region("Port", player, world)
|
||||
port_region = Region("Port", player, multiworld)
|
||||
port_region.add_locations(loc_port, CCCharlesLocation)
|
||||
world.regions.append(port_region)
|
||||
multiworld.regions.append(port_region)
|
||||
|
||||
trench_house_region = Region("Trench House", player, world)
|
||||
trench_house_region = Region("Trench House", player, multiworld)
|
||||
trench_house_region.add_locations(loc_trench_house, CCCharlesLocation)
|
||||
world.regions.append(trench_house_region)
|
||||
multiworld.regions.append(trench_house_region)
|
||||
|
||||
doll_woods_region = Region("Doll Woods", player, world)
|
||||
doll_woods_region = Region("Doll Woods", player, multiworld)
|
||||
doll_woods_region.add_locations(loc_doll_woods, CCCharlesLocation)
|
||||
world.regions.append(doll_woods_region)
|
||||
multiworld.regions.append(doll_woods_region)
|
||||
|
||||
lost_stairs_region = Region("Lost Stairs", player, world)
|
||||
lost_stairs_region = Region("Lost Stairs", player, multiworld)
|
||||
lost_stairs_region.add_locations(loc_lost_stairs, CCCharlesLocation)
|
||||
world.regions.append(lost_stairs_region)
|
||||
multiworld.regions.append(lost_stairs_region)
|
||||
|
||||
east_house_region = Region("East House", player, world)
|
||||
east_house_region = Region("East House", player, multiworld)
|
||||
east_house_region.add_locations(loc_east_house, CCCharlesLocation)
|
||||
world.regions.append(east_house_region)
|
||||
multiworld.regions.append(east_house_region)
|
||||
|
||||
rockets_testing_ground_region = Region("Rockets Testing Ground", player, world)
|
||||
rockets_testing_ground_region = Region("Rockets Testing Ground", player, multiworld)
|
||||
rockets_testing_ground_region.add_locations(loc_rockets_testing_ground, CCCharlesLocation)
|
||||
world.regions.append(rockets_testing_ground_region)
|
||||
multiworld.regions.append(rockets_testing_ground_region)
|
||||
|
||||
rockets_testing_bunker_region = Region("Rockets Testing Bunker", player, world)
|
||||
rockets_testing_bunker_region = Region("Rockets Testing Bunker", player, multiworld)
|
||||
rockets_testing_bunker_region.add_locations(loc_rockets_testing_bunker, CCCharlesLocation)
|
||||
world.regions.append(rockets_testing_bunker_region)
|
||||
multiworld.regions.append(rockets_testing_bunker_region)
|
||||
|
||||
workshop_region = Region("Workshop", player, world)
|
||||
workshop_region = Region("Workshop", player, multiworld)
|
||||
workshop_region.add_locations(loc_workshop, CCCharlesLocation)
|
||||
world.regions.append(workshop_region)
|
||||
multiworld.regions.append(workshop_region)
|
||||
|
||||
east_tower_region = Region("East Tower", player, world)
|
||||
east_tower_region = Region("East Tower", player, multiworld)
|
||||
east_tower_region.add_locations(loc_east_tower, CCCharlesLocation)
|
||||
world.regions.append(east_tower_region)
|
||||
multiworld.regions.append(east_tower_region)
|
||||
|
||||
lighthouse_region = Region("Lighthouse", player, world)
|
||||
lighthouse_region = Region("Lighthouse", player, multiworld)
|
||||
lighthouse_region.add_locations(loc_lighthouse, CCCharlesLocation)
|
||||
world.regions.append(lighthouse_region)
|
||||
multiworld.regions.append(lighthouse_region)
|
||||
|
||||
north_mine_outside_region = Region("North Mine Outside", player, world)
|
||||
north_mine_outside_region = Region("North Mine Outside", player, multiworld)
|
||||
north_mine_outside_region.add_locations(loc_north_mine_outside, CCCharlesLocation)
|
||||
world.regions.append(north_mine_outside_region)
|
||||
multiworld.regions.append(north_mine_outside_region)
|
||||
|
||||
north_mine_inside_region = Region("North Mine Inside", player, world)
|
||||
north_mine_inside_region = Region("North Mine Inside", player, multiworld)
|
||||
north_mine_inside_region.add_locations(loc_north_mine_inside, CCCharlesLocation)
|
||||
world.regions.append(north_mine_inside_region)
|
||||
multiworld.regions.append(north_mine_inside_region)
|
||||
|
||||
wood_bridge_region = Region("Wood Bridge", player, world)
|
||||
wood_bridge_region = Region("Wood Bridge", player, multiworld)
|
||||
wood_bridge_region.add_locations(loc_wood_bridge, CCCharlesLocation)
|
||||
world.regions.append(wood_bridge_region)
|
||||
multiworld.regions.append(wood_bridge_region)
|
||||
|
||||
museum_region = Region("Museum", player, world)
|
||||
museum_region = Region("Museum", player, multiworld)
|
||||
museum_region.add_locations(loc_museum, CCCharlesLocation)
|
||||
world.regions.append(museum_region)
|
||||
multiworld.regions.append(museum_region)
|
||||
|
||||
barbed_shelter_region = Region("Barbed Shelter", player, world)
|
||||
barbed_shelter_region = Region("Barbed Shelter", player, multiworld)
|
||||
barbed_shelter_region.add_locations(loc_barbed_shelter, CCCharlesLocation)
|
||||
world.regions.append(barbed_shelter_region)
|
||||
multiworld.regions.append(barbed_shelter_region)
|
||||
|
||||
west_beach_region = Region("West Beach", player, world)
|
||||
west_beach_region = Region("West Beach", player, multiworld)
|
||||
west_beach_region.add_locations(loc_west_beach, CCCharlesLocation)
|
||||
world.regions.append(west_beach_region)
|
||||
multiworld.regions.append(west_beach_region)
|
||||
|
||||
church_region = Region("Church", player, world)
|
||||
church_region = Region("Church", player, multiworld)
|
||||
church_region.add_locations(loc_church, CCCharlesLocation)
|
||||
world.regions.append(church_region)
|
||||
multiworld.regions.append(church_region)
|
||||
|
||||
west_cottage_region = Region("West Cottage", player, world)
|
||||
west_cottage_region = Region("West Cottage", player, multiworld)
|
||||
west_cottage_region.add_locations(loc_west_cottage, CCCharlesLocation)
|
||||
world.regions.append(west_cottage_region)
|
||||
multiworld.regions.append(west_cottage_region)
|
||||
|
||||
caravan_region = Region("Caravan", player, world)
|
||||
caravan_region = Region("Caravan", player, multiworld)
|
||||
caravan_region.add_locations(loc_caravan, CCCharlesLocation)
|
||||
world.regions.append(caravan_region)
|
||||
multiworld.regions.append(caravan_region)
|
||||
|
||||
trailer_cabin_region = Region("Trailer Cabin", player, world)
|
||||
trailer_cabin_region = Region("Trailer Cabin", player, multiworld)
|
||||
trailer_cabin_region.add_locations(loc_trailer_cabin, CCCharlesLocation)
|
||||
world.regions.append(trailer_cabin_region)
|
||||
multiworld.regions.append(trailer_cabin_region)
|
||||
|
||||
towers_region = Region("Towers", player, world)
|
||||
towers_region = Region("Towers", player, multiworld)
|
||||
towers_region.add_locations(loc_towers, CCCharlesLocation)
|
||||
world.regions.append(towers_region)
|
||||
multiworld.regions.append(towers_region)
|
||||
|
||||
north_beach_region = Region("North beach", player, world)
|
||||
north_beach_region = Region("North beach", player, multiworld)
|
||||
north_beach_region.add_locations(loc_north_beach, CCCharlesLocation)
|
||||
world.regions.append(north_beach_region)
|
||||
multiworld.regions.append(north_beach_region)
|
||||
|
||||
mine_shaft_region = Region("Mine Shaft", player, world)
|
||||
mine_shaft_region = Region("Mine Shaft", player, multiworld)
|
||||
mine_shaft_region.add_locations(loc_mine_shaft, CCCharlesLocation)
|
||||
world.regions.append(mine_shaft_region)
|
||||
multiworld.regions.append(mine_shaft_region)
|
||||
|
||||
mob_camp_region = Region("Mob Camp", player, world)
|
||||
mob_camp_region = Region("Mob Camp", player, multiworld)
|
||||
mob_camp_region.add_locations(loc_mob_camp, CCCharlesLocation)
|
||||
world.regions.append(mob_camp_region)
|
||||
multiworld.regions.append(mob_camp_region)
|
||||
|
||||
mob_camp_locked_room_region = Region("Mob Camp Locked Room", player, world)
|
||||
mob_camp_locked_room_region = Region("Mob Camp Locked Room", player, multiworld)
|
||||
mob_camp_locked_room_region.add_locations(loc_mob_camp_locked_room, CCCharlesLocation)
|
||||
world.regions.append(mob_camp_locked_room_region)
|
||||
multiworld.regions.append(mob_camp_locked_room_region)
|
||||
|
||||
mine_elevator_exit_region = Region("Mine Elevator Exit", player, world)
|
||||
mine_elevator_exit_region = Region("Mine Elevator Exit", player, multiworld)
|
||||
mine_elevator_exit_region.add_locations(loc_mine_elevator_exit, CCCharlesLocation)
|
||||
world.regions.append(mine_elevator_exit_region)
|
||||
multiworld.regions.append(mine_elevator_exit_region)
|
||||
|
||||
mountain_ruin_outside_region = Region("Mountain Ruin Outside", player, world)
|
||||
mountain_ruin_outside_region = Region("Mountain Ruin Outside", player, multiworld)
|
||||
mountain_ruin_outside_region.add_locations(loc_mountain_ruin_outside, CCCharlesLocation)
|
||||
world.regions.append(mountain_ruin_outside_region)
|
||||
multiworld.regions.append(mountain_ruin_outside_region)
|
||||
|
||||
mountain_ruin_inside_region = Region("Mountain Ruin Inside", player, world)
|
||||
mountain_ruin_inside_region = Region("Mountain Ruin Inside", player, multiworld)
|
||||
mountain_ruin_inside_region.add_locations(loc_mountain_ruin_inside, CCCharlesLocation)
|
||||
world.regions.append(mountain_ruin_inside_region)
|
||||
multiworld.regions.append(mountain_ruin_inside_region)
|
||||
|
||||
prism_temple_region = Region("Prism Temple", player, world)
|
||||
prism_temple_region = Region("Prism Temple", player, multiworld)
|
||||
prism_temple_region.add_locations(loc_prism_temple, CCCharlesLocation)
|
||||
world.regions.append(prism_temple_region)
|
||||
multiworld.regions.append(prism_temple_region)
|
||||
|
||||
pickle_val_region = Region("Pickle Val", player, world)
|
||||
pickle_val_region = Region("Pickle Val", player, multiworld)
|
||||
pickle_val_region.add_locations(loc_pickle_val, CCCharlesLocation)
|
||||
world.regions.append(pickle_val_region)
|
||||
multiworld.regions.append(pickle_val_region)
|
||||
|
||||
shrine_near_temple_region = Region("Shrine Near Temple", player, world)
|
||||
shrine_near_temple_region = Region("Shrine Near Temple", player, multiworld)
|
||||
shrine_near_temple_region.add_locations(loc_shrine_near_temple, CCCharlesLocation)
|
||||
world.regions.append(shrine_near_temple_region)
|
||||
multiworld.regions.append(shrine_near_temple_region)
|
||||
|
||||
morse_bunker_region = Region("Morse Bunker", player, world)
|
||||
morse_bunker_region = Region("Morse Bunker", player, multiworld)
|
||||
morse_bunker_region.add_locations(loc_morse_bunker, CCCharlesLocation)
|
||||
world.regions.append(morse_bunker_region)
|
||||
multiworld.regions.append(morse_bunker_region)
|
||||
|
||||
# Place "Victory" event at "Final Boss" location
|
||||
loc_final_boss = CCCharlesLocation(player, "Final Boss", None, prism_temple_region)
|
||||
|
||||
@@ -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)
|
||||
|
||||
def set_rules(world: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
def set_rules(multiworld: MultiWorld, options: CCCharlesOptions, player: int) -> None:
|
||||
# Tony Tiddle
|
||||
set_rule(world.get_entrance("Barn Door", player),
|
||||
set_rule(multiworld.get_entrance("Barn Door", player),
|
||||
lambda state: state.has("Barn Key", player))
|
||||
|
||||
# Candice
|
||||
set_rule(world.get_entrance("Tutorial House Door", player),
|
||||
set_rule(multiworld.get_entrance("Tutorial House Door", player),
|
||||
lambda state: state.has("Candice's Key", player))
|
||||
|
||||
# Lizbeth Murkwater
|
||||
set_rule(world.get_location("Swamp Lizbeth Murkwater Mission End", player),
|
||||
set_rule(multiworld.get_location("Swamp Lizbeth Murkwater Mission End", player),
|
||||
lambda state: state.has("Dead Fish", player))
|
||||
|
||||
# Daryl
|
||||
set_rule(world.get_location("Junkyard Area Chest Ancient Tablet", player),
|
||||
set_rule(multiworld.get_location("Junkyard Area Chest Ancient Tablet", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Junkyard Area Daryl Mission End", player),
|
||||
set_rule(multiworld.get_location("Junkyard Area Daryl Mission End", player),
|
||||
lambda state: state.has("Ancient Tablet", player))
|
||||
|
||||
# South House
|
||||
set_rule(world.get_location("South House Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("South House Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("South House Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# South Mine
|
||||
set_rule(world.get_entrance("South Mine Gate", player),
|
||||
set_rule(multiworld.get_entrance("South Mine Gate", player),
|
||||
lambda state: state.has("South Mine Key", player))
|
||||
|
||||
set_rule(world.get_location("South Mine Inside Green Paint Can", player),
|
||||
set_rule(multiworld.get_location("South Mine Inside Green Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Theodore
|
||||
set_rule(world.get_location("Middle Station Theodore Mission End", player),
|
||||
set_rule(multiworld.get_location("Middle Station Theodore Mission End", player),
|
||||
lambda state: state.has("Blue Box", player))
|
||||
|
||||
# Watchtower
|
||||
set_rule(world.get_location("Watchtower Pink Paint Can", player),
|
||||
set_rule(multiworld.get_location("Watchtower Pink Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Sasha
|
||||
set_rule(world.get_location("Haunted House Sasha Mission End", player),
|
||||
set_rule(multiworld.get_location("Haunted House Sasha Mission End", player),
|
||||
lambda state: state.has("Page Drawing", player, 8))
|
||||
|
||||
# Santiago
|
||||
set_rule(world.get_location("Port Santiago Mission End", player),
|
||||
set_rule(multiworld.get_location("Port Santiago Mission End", player),
|
||||
lambda state: state.has("Journal", player))
|
||||
|
||||
# Trench House
|
||||
set_rule(world.get_location("Trench House Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Trench House Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("Trench House Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# East House
|
||||
set_rule(world.get_location("East House Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("East House Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("East House Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Rocket Testing Bunker
|
||||
set_rule(world.get_entrance("Stuck Bunker Door", player),
|
||||
set_rule(multiworld.get_entrance("Stuck Bunker Door", player),
|
||||
lambda state: state.has("Timed Dynamite", player))
|
||||
|
||||
# John Smith
|
||||
set_rule(world.get_location("Workshop John Smith Mission End", player),
|
||||
set_rule(multiworld.get_location("Workshop John Smith Mission End", player),
|
||||
lambda state: state.has("Box of Rockets", player))
|
||||
|
||||
# Claire
|
||||
set_rule(world.get_location("Lighthouse Claire Mission End", player),
|
||||
set_rule(multiworld.get_location("Lighthouse Claire Mission End", player),
|
||||
lambda state: state.has("Breaker", player, 4))
|
||||
|
||||
# North Mine
|
||||
set_rule(world.get_entrance("North Mine Gate", player),
|
||||
set_rule(multiworld.get_entrance("North Mine Gate", player),
|
||||
lambda state: state.has("North Mine Key", player))
|
||||
|
||||
set_rule(world.get_location("North Mine Inside Blue Paint Can", player),
|
||||
set_rule(multiworld.get_location("North Mine Inside Blue Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Paul
|
||||
set_rule(world.get_location("Museum Paul Mission End", player),
|
||||
set_rule(multiworld.get_location("Museum Paul Mission End", 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
|
||||
|
||||
# West Beach
|
||||
set_rule(world.get_location("West Beach Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("West Beach Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("West Beach Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Caravan
|
||||
set_rule(world.get_location("Caravan Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Caravan Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Caravan Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Ronny
|
||||
set_rule(world.get_location("Towers Ronny Mission End", player),
|
||||
set_rule(multiworld.get_location("Towers Ronny Mission End", player),
|
||||
lambda state: state.has("Employment Contracts", player))
|
||||
|
||||
# North Beach
|
||||
set_rule(world.get_location("North Beach Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("North Beach Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("North Beach Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("North Beach Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("North Beach Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Mine Shaft
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 6", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 6", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Mine Shaft Chest Scraps 7", player),
|
||||
set_rule(multiworld.get_location("Mine Shaft Chest Scraps 7", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Mob Camp
|
||||
set_rule(world.get_entrance("Mob Camp Locked Door", player),
|
||||
set_rule(multiworld.get_entrance("Mob Camp Locked Door", player),
|
||||
lambda state: state.has("Mob Camp Key", player))
|
||||
|
||||
set_rule(world.get_location("Mob Camp Locked Room Stolen Bob", player),
|
||||
set_rule(multiworld.get_location("Mob Camp Locked Room Stolen Bob", player),
|
||||
lambda state: state.has("Broken Bob", player))
|
||||
|
||||
# Mountain Ruin
|
||||
set_rule(world.get_entrance("Mountain Ruin Gate", player),
|
||||
set_rule(multiworld.get_entrance("Mountain Ruin Gate", player),
|
||||
lambda state: state.has("Mountain Ruin Key", player))
|
||||
|
||||
set_rule(world.get_location("Mountain Ruin Inside Red Paint Can", player),
|
||||
set_rule(multiworld.get_location("Mountain Ruin Inside Red Paint Can", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Prism Temple
|
||||
set_rule(world.get_location("Prism Temple Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Prism Temple Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Prism Temple Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Prism Temple Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Prism Temple Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Prism Temple Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Pickle Lady
|
||||
set_rule(world.get_location("Pickle Val Jar of Pickles", player),
|
||||
set_rule(multiworld.get_location("Pickle Val Jar of Pickles", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Pickle Val Pickle Lady Mission End", player),
|
||||
set_rule(multiworld.get_location("Pickle Val Pickle Lady Mission End", player),
|
||||
lambda state: state.has("Jar of Pickles", player))
|
||||
|
||||
# Morse Bunker
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 1", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 1", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 2", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 2", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 3", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 3", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 4", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 4", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
set_rule(world.get_location("Morse Bunker Chest Scraps 5", player),
|
||||
set_rule(multiworld.get_location("Morse Bunker Chest Scraps 5", player),
|
||||
lambda state: state.has("Lockpicks", player))
|
||||
|
||||
# Add rules to reach the "Go mode"
|
||||
set_rule(world.get_location("Final Boss", player),
|
||||
set_rule(multiworld.get_location("Final Boss", player),
|
||||
lambda state: state.has("Temple Key", player)
|
||||
and state.has("Green Egg", player)
|
||||
and state.has("Blue Egg", player)
|
||||
and state.has("Red Egg", player))
|
||||
world.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# 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
|
||||
@@ -1,229 +0,0 @@
|
||||
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)
|
||||
@@ -1,52 +0,0 @@
|
||||
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}
|
||||
@@ -1,27 +0,0 @@
|
||||
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.
|
||||
@@ -1,115 +0,0 @@
|
||||
|
||||
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,
|
||||
]
|
||||
@@ -1,337 +0,0 @@
|
||||
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()}
|
||||
@@ -1,21 +0,0 @@
|
||||
# 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"
|
||||
@@ -1,375 +0,0 @@
|
||||
# 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"
|
||||
@@ -1,203 +0,0 @@
|
||||
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
|
||||
@@ -1,954 +0,0 @@
|
||||
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
|
||||
@@ -1,744 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,31 +0,0 @@
|
||||
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)
|
||||
@@ -1,233 +0,0 @@
|
||||
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)
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"game": "Donkey Kong Country 3",
|
||||
"authors": [ "PoryGone" ],
|
||||
"minimum_ap_version": "0.6.3",
|
||||
"world_version": "1.1.0"
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,162 +0,0 @@
|
||||
# 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. \
|
||||

|
||||
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.
|
||||
@@ -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,
|
||||
coin_bundle_size: int, player: int, world: MultiWorld):
|
||||
coin_bundle_size: int, player: int, multiworld: MultiWorld):
|
||||
if not has_campaign_basic:
|
||||
return
|
||||
|
||||
region_menu.exits += [Entrance(player, "DLC Quest Basic", region_menu)]
|
||||
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, world, 4)
|
||||
region_move_right = create_region_and_locations_basic("Move Right", locations_move_right, ["Moving"], player, multiworld, 4)
|
||||
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 += conditional_location(has_item_shuffle, "Sword")
|
||||
create_region_and_locations_basic("Movement Pack", locations_movement_pack, ["Tree", "Cloud"], player, world, 46)
|
||||
create_region_and_locations_basic("Movement Pack", locations_movement_pack, ["Tree", "Cloud"], player, multiworld, 46)
|
||||
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, world, 60)
|
||||
create_region_and_locations_basic("Psychological Warfare", ["West Cave Sheep"], ["Cloud Double Jump"], player, world, 100)
|
||||
create_region_and_locations_basic("Behind Tree", locations_behind_tree, ["Behind Tree Double Jump", "Forest Entrance"], player, multiworld, 60)
|
||||
create_region_and_locations_basic("Psychological Warfare", ["West Cave Sheep"], ["Cloud Double Jump"], player, multiworld, 100)
|
||||
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, world, 50)
|
||||
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, world, 10)
|
||||
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 Cave", ["Top Hat Sheep"], [], player, multiworld, 9)
|
||||
create_region_and_locations_basic("Double Jump Total Left Roof", ["North West Ceiling Sheep"], [], player, multiworld, 10)
|
||||
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, world, 89)
|
||||
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, world, 171)
|
||||
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, world, 203)
|
||||
region_final_boss_room = create_region_and_locations_basic("The Final Boss Room", ["Finish the Fight Pack"], [], player, world)
|
||||
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("True Double Jump Behind Tree", ["Double Jump Floating Sheep", "Cutscene Sheep"], [], player, multiworld, 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 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 Part 2", ["Forest High Sheep"], [], player, multiworld, 203)
|
||||
region_final_boss_room = create_region_and_locations_basic("The Final Boss Room", ["Finish the Fight Pack"], [], player, multiworld)
|
||||
|
||||
create_victory_event(region_final_boss_room, "Winning Basic", "Victory Basic", player)
|
||||
|
||||
connect_entrances_basic(player, world)
|
||||
connect_entrances_basic(player, multiworld)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
def connect_entrances_basic(player, world):
|
||||
world.get_entrance("DLC Quest Basic", player).connect(world.get_region("Move Right", player))
|
||||
world.get_entrance("Moving", player).connect(world.get_region("Movement Pack", player))
|
||||
world.get_entrance("Tree", player).connect(world.get_region("Behind Tree", player))
|
||||
world.get_entrance("Cloud", player).connect(world.get_region("Psychological Warfare", player))
|
||||
world.get_entrance("Cloud Double Jump", player).connect(world.get_region("Double Jump Total Left", player))
|
||||
world.get_entrance("Cave Tree", player).connect(world.get_region("Double Jump Total Left Cave", player))
|
||||
world.get_entrance("Cave Roof", player).connect(world.get_region("Double Jump Total Left Roof", player))
|
||||
world.get_entrance("Forest Entrance", player).connect(world.get_region("The Forest", player))
|
||||
world.get_entrance("Behind Tree Double Jump", player).connect(world.get_region("Double Jump Behind Tree", player))
|
||||
world.get_entrance("Behind Ogre", player).connect(world.get_region("The Final Boss Room", player))
|
||||
world.get_entrance("Forest Double Jump", player).connect(world.get_region("The Forest with double Jump", player))
|
||||
world.get_entrance("Forest True Double Jump", player).connect(world.get_region("The Forest with double Jump Part 2", player))
|
||||
world.get_entrance("True Double Jump", player).connect(world.get_region("True Double Jump Behind Tree", player))
|
||||
def connect_entrances_basic(player, multiworld):
|
||||
multiworld.get_entrance("DLC Quest Basic", player).connect(multiworld.get_region("Move Right", player))
|
||||
multiworld.get_entrance("Moving", player).connect(multiworld.get_region("Movement Pack", player))
|
||||
multiworld.get_entrance("Tree", player).connect(multiworld.get_region("Behind Tree", player))
|
||||
multiworld.get_entrance("Cloud", player).connect(multiworld.get_region("Psychological Warfare", player))
|
||||
multiworld.get_entrance("Cloud Double Jump", player).connect(multiworld.get_region("Double Jump Total Left", player))
|
||||
multiworld.get_entrance("Cave Tree", player).connect(multiworld.get_region("Double Jump Total Left Cave", player))
|
||||
multiworld.get_entrance("Cave Roof", player).connect(multiworld.get_region("Double Jump Total Left Roof", player))
|
||||
multiworld.get_entrance("Forest Entrance", player).connect(multiworld.get_region("The Forest", player))
|
||||
multiworld.get_entrance("Behind Tree Double Jump", player).connect(multiworld.get_region("Double Jump Behind Tree", player))
|
||||
multiworld.get_entrance("Behind Ogre", player).connect(multiworld.get_region("The Final Boss Room", player))
|
||||
multiworld.get_entrance("Forest Double Jump", player).connect(multiworld.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))
|
||||
multiworld.get_entrance("True Double Jump", player).connect(multiworld.get_region("True Double Jump Behind Tree", player))
|
||||
|
||||
|
||||
def connect_entrances_lfod(multiworld, player):
|
||||
|
||||
@@ -18,251 +18,251 @@ def has_enough_coin_freemium(player: int, coin: int):
|
||||
return lambda state: state.prog_items[player][" coins freemium"] >= coin
|
||||
|
||||
|
||||
def set_rules(world, player, world_options: Options.DLCQuestOptions):
|
||||
set_basic_rules(world_options, player, world)
|
||||
set_lfod_rules(world_options, player, world)
|
||||
set_completion_condition(world_options, player, world)
|
||||
def set_rules(multiworld, player, world_options: Options.DLCQuestOptions):
|
||||
set_basic_rules(world_options, player, multiworld)
|
||||
set_lfod_rules(world_options, player, multiworld)
|
||||
set_completion_condition(world_options, player, multiworld)
|
||||
|
||||
|
||||
def set_basic_rules(world_options, player, world):
|
||||
def set_basic_rules(world_options, player, multiworld):
|
||||
if world_options.campaign == Options.Campaign.option_live_freemium_or_die:
|
||||
return
|
||||
set_basic_entrance_rules(player, world)
|
||||
set_basic_self_obtained_items_rules(world_options, player, world)
|
||||
set_basic_shuffled_items_rules(world_options, player, world)
|
||||
set_double_jump_glitchless_rules(world_options, player, world)
|
||||
set_easy_double_jump_glitch_rules(world_options, player, world)
|
||||
self_basic_coinsanity_funded_purchase_rules(world_options, player, world)
|
||||
set_basic_self_funded_purchase_rules(world_options, player, world)
|
||||
self_basic_win_condition(world_options, player, world)
|
||||
set_basic_entrance_rules(player, multiworld)
|
||||
set_basic_self_obtained_items_rules(world_options, player, multiworld)
|
||||
set_basic_shuffled_items_rules(world_options, player, multiworld)
|
||||
set_double_jump_glitchless_rules(world_options, player, multiworld)
|
||||
set_easy_double_jump_glitch_rules(world_options, player, multiworld)
|
||||
self_basic_coinsanity_funded_purchase_rules(world_options, player, multiworld)
|
||||
set_basic_self_funded_purchase_rules(world_options, player, multiworld)
|
||||
self_basic_win_condition(world_options, player, multiworld)
|
||||
|
||||
|
||||
def set_basic_entrance_rules(player, world):
|
||||
set_rule(world.get_entrance("Moving", player),
|
||||
def set_basic_entrance_rules(player, multiworld):
|
||||
set_rule(multiworld.get_entrance("Moving", player),
|
||||
lambda state: state.has("Movement Pack", player))
|
||||
set_rule(world.get_entrance("Cloud", player),
|
||||
set_rule(multiworld.get_entrance("Cloud", player),
|
||||
lambda state: state.has("Psychological Warfare Pack", player))
|
||||
set_rule(world.get_entrance("Forest Entrance", player),
|
||||
set_rule(multiworld.get_entrance("Forest Entrance", player),
|
||||
lambda state: state.has("Map Pack", player))
|
||||
set_rule(world.get_entrance("Forest True Double Jump", player),
|
||||
set_rule(multiworld.get_entrance("Forest True Double Jump", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
|
||||
|
||||
def set_basic_self_obtained_items_rules(world_options, player, world):
|
||||
def set_basic_self_obtained_items_rules(world_options, player, multiworld):
|
||||
if world_options.item_shuffle != Options.ItemShuffle.option_disabled:
|
||||
return
|
||||
set_rule(world.get_entrance("Behind Ogre", player),
|
||||
set_rule(multiworld.get_entrance("Behind Ogre", player),
|
||||
lambda state: state.has("Gun Pack", player))
|
||||
|
||||
if world_options.time_is_money == Options.TimeIsMoney.option_required:
|
||||
set_rule(world.get_entrance("Tree", player),
|
||||
set_rule(multiworld.get_entrance("Tree", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
set_rule(world.get_entrance("Cave Tree", player),
|
||||
set_rule(multiworld.get_entrance("Cave Tree", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
set_rule(world.get_location("Shepherd Sheep", player),
|
||||
set_rule(multiworld.get_location("Shepherd Sheep", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
set_rule(world.get_location("North West Ceiling Sheep", player),
|
||||
set_rule(multiworld.get_location("North West Ceiling Sheep", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
set_rule(world.get_location("North West Alcove Sheep", player),
|
||||
set_rule(multiworld.get_location("North West Alcove Sheep", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
set_rule(world.get_location("West Cave Sheep", player),
|
||||
set_rule(multiworld.get_location("West Cave Sheep", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
|
||||
|
||||
def set_basic_shuffled_items_rules(world_options, player, world):
|
||||
def set_basic_shuffled_items_rules(world_options, player, multiworld):
|
||||
if world_options.item_shuffle != Options.ItemShuffle.option_shuffled:
|
||||
return
|
||||
set_rule(world.get_entrance("Behind Ogre", player),
|
||||
set_rule(multiworld.get_entrance("Behind Ogre", player),
|
||||
lambda state: state.has("DLC Quest: Progressive Weapon", player, 2))
|
||||
set_rule(world.get_entrance("Tree", player),
|
||||
set_rule(multiworld.get_entrance("Tree", player),
|
||||
lambda state: state.has("DLC Quest: Progressive Weapon", player))
|
||||
set_rule(world.get_entrance("Cave Tree", player),
|
||||
set_rule(multiworld.get_entrance("Cave Tree", player),
|
||||
lambda state: state.has("DLC Quest: Progressive Weapon", player))
|
||||
set_rule(world.get_entrance("True Double Jump", player),
|
||||
set_rule(multiworld.get_entrance("True Double Jump", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
set_rule(world.get_location("Shepherd Sheep", player),
|
||||
set_rule(multiworld.get_location("Shepherd Sheep", player),
|
||||
lambda state: state.has("DLC Quest: Progressive Weapon", player))
|
||||
set_rule(world.get_location("North West Ceiling Sheep", player),
|
||||
set_rule(multiworld.get_location("North West Ceiling Sheep", player),
|
||||
lambda state: state.has("DLC Quest: Progressive Weapon", player))
|
||||
set_rule(world.get_location("North West Alcove Sheep", player),
|
||||
set_rule(multiworld.get_location("North West Alcove Sheep", player),
|
||||
lambda state: state.has("DLC Quest: Progressive Weapon", player))
|
||||
set_rule(world.get_location("West Cave Sheep", player),
|
||||
set_rule(multiworld.get_location("West Cave Sheep", player),
|
||||
lambda state: state.has("DLC Quest: Progressive Weapon", player))
|
||||
set_rule(world.get_location("Gun", player),
|
||||
set_rule(multiworld.get_location("Gun", player),
|
||||
lambda state: state.has("Gun Pack", player))
|
||||
|
||||
if world_options.time_is_money == Options.TimeIsMoney.option_required:
|
||||
set_rule(world.get_location("Sword", player),
|
||||
set_rule(multiworld.get_location("Sword", player),
|
||||
lambda state: state.has("Time is Money Pack", player))
|
||||
|
||||
|
||||
def set_double_jump_glitchless_rules(world_options, player, world):
|
||||
def set_double_jump_glitchless_rules(world_options, player, multiworld):
|
||||
if world_options.double_jump_glitch != Options.DoubleJumpGlitch.option_none:
|
||||
return
|
||||
set_rule(world.get_entrance("Cloud Double Jump", player),
|
||||
set_rule(multiworld.get_entrance("Cloud Double Jump", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
set_rule(world.get_entrance("Forest Double Jump", player),
|
||||
set_rule(multiworld.get_entrance("Forest Double Jump", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
|
||||
|
||||
def set_easy_double_jump_glitch_rules(world_options, player, world):
|
||||
def set_easy_double_jump_glitch_rules(world_options, player, multiworld):
|
||||
if world_options.double_jump_glitch == Options.DoubleJumpGlitch.option_all:
|
||||
return
|
||||
set_rule(world.get_entrance("Behind Tree Double Jump", player),
|
||||
set_rule(multiworld.get_entrance("Behind Tree Double Jump", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
set_rule(world.get_entrance("Cave Roof", player),
|
||||
set_rule(multiworld.get_entrance("Cave Roof", player),
|
||||
lambda state: state.has("Double Jump Pack", player))
|
||||
|
||||
|
||||
def self_basic_coinsanity_funded_purchase_rules(world_options, player, world):
|
||||
def self_basic_coinsanity_funded_purchase_rules(world_options, player, multiworld):
|
||||
if world_options.coinsanity != Options.CoinSanity.option_coin:
|
||||
return
|
||||
if world_options.coinbundlequantity == -1:
|
||||
self_basic_coinsanity_piece_rules(player, world)
|
||||
self_basic_coinsanity_piece_rules(player, multiworld)
|
||||
return
|
||||
number_of_bundle = math.floor(825 / world_options.coinbundlequantity)
|
||||
for i in range(number_of_bundle):
|
||||
|
||||
item_coin = f"DLC Quest: {world_options.coinbundlequantity * (i + 1)} Coin"
|
||||
set_rule(world.get_location(item_coin, player),
|
||||
set_rule(multiworld.get_location(item_coin, player),
|
||||
has_enough_coin(player, world_options.coinbundlequantity * (i + 1)))
|
||||
if 825 % world_options.coinbundlequantity != 0:
|
||||
set_rule(world.get_location("DLC Quest: 825 Coin", player),
|
||||
set_rule(multiworld.get_location("DLC Quest: 825 Coin", player),
|
||||
has_enough_coin(player, 825))
|
||||
|
||||
set_rule(world.get_location("Movement Pack", player),
|
||||
set_rule(multiworld.get_location("Movement Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(4 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Animation Pack", player),
|
||||
set_rule(multiworld.get_location("Animation Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Audio Pack", player),
|
||||
set_rule(multiworld.get_location("Audio Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Pause Menu Pack", player),
|
||||
set_rule(multiworld.get_location("Pause Menu Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Time is Money Pack", player),
|
||||
set_rule(multiworld.get_location("Time is Money Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(20 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Double Jump Pack", player),
|
||||
set_rule(multiworld.get_location("Double Jump Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(100 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Pet Pack", player),
|
||||
set_rule(multiworld.get_location("Pet Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Sexy Outfits Pack", player),
|
||||
set_rule(multiworld.get_location("Sexy Outfits Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Top Hat Pack", player),
|
||||
set_rule(multiworld.get_location("Top Hat Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Map Pack", player),
|
||||
set_rule(multiworld.get_location("Map Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(140 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Gun Pack", player),
|
||||
set_rule(multiworld.get_location("Gun Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(75 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("The Zombie Pack", player),
|
||||
set_rule(multiworld.get_location("The Zombie Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Night Map Pack", player),
|
||||
set_rule(multiworld.get_location("Night Map Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(75 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Psychological Warfare Pack", player),
|
||||
set_rule(multiworld.get_location("Psychological Warfare Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(50 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Armor for your Horse Pack", player),
|
||||
set_rule(multiworld.get_location("Armor for your Horse Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(250 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Finish the Fight Pack", player),
|
||||
set_rule(multiworld.get_location("Finish the Fight Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
|
||||
|
||||
def set_basic_self_funded_purchase_rules(world_options, player, world):
|
||||
def set_basic_self_funded_purchase_rules(world_options, player, multiworld):
|
||||
if world_options.coinsanity != Options.CoinSanity.option_none:
|
||||
return
|
||||
set_rule(world.get_location("Movement Pack", player),
|
||||
set_rule(multiworld.get_location("Movement Pack", player),
|
||||
has_enough_coin(player, 4))
|
||||
set_rule(world.get_location("Animation Pack", player),
|
||||
set_rule(multiworld.get_location("Animation Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
set_rule(world.get_location("Audio Pack", player),
|
||||
set_rule(multiworld.get_location("Audio Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
set_rule(world.get_location("Pause Menu Pack", player),
|
||||
set_rule(multiworld.get_location("Pause Menu Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
set_rule(world.get_location("Time is Money Pack", player),
|
||||
set_rule(multiworld.get_location("Time is Money Pack", player),
|
||||
has_enough_coin(player, 20))
|
||||
set_rule(world.get_location("Double Jump Pack", player),
|
||||
set_rule(multiworld.get_location("Double Jump Pack", player),
|
||||
has_enough_coin(player, 100))
|
||||
set_rule(world.get_location("Pet Pack", player),
|
||||
set_rule(multiworld.get_location("Pet Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
set_rule(world.get_location("Sexy Outfits Pack", player),
|
||||
set_rule(multiworld.get_location("Sexy Outfits Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
set_rule(world.get_location("Top Hat Pack", player),
|
||||
set_rule(multiworld.get_location("Top Hat Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
set_rule(world.get_location("Map Pack", player),
|
||||
set_rule(multiworld.get_location("Map Pack", player),
|
||||
has_enough_coin(player, 140))
|
||||
set_rule(world.get_location("Gun Pack", player),
|
||||
set_rule(multiworld.get_location("Gun Pack", player),
|
||||
has_enough_coin(player, 75))
|
||||
set_rule(world.get_location("The Zombie Pack", player),
|
||||
set_rule(multiworld.get_location("The Zombie Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
set_rule(world.get_location("Night Map Pack", player),
|
||||
set_rule(multiworld.get_location("Night Map Pack", player),
|
||||
has_enough_coin(player, 75))
|
||||
set_rule(world.get_location("Psychological Warfare Pack", player),
|
||||
set_rule(multiworld.get_location("Psychological Warfare Pack", player),
|
||||
has_enough_coin(player, 50))
|
||||
set_rule(world.get_location("Armor for your Horse Pack", player),
|
||||
set_rule(multiworld.get_location("Armor for your Horse Pack", player),
|
||||
has_enough_coin(player, 250))
|
||||
set_rule(world.get_location("Finish the Fight Pack", player),
|
||||
set_rule(multiworld.get_location("Finish the Fight Pack", player),
|
||||
has_enough_coin(player, 5))
|
||||
|
||||
|
||||
def self_basic_win_condition(world_options, player, world):
|
||||
def self_basic_win_condition(world_options, player, multiworld):
|
||||
if world_options.ending_choice == Options.EndingChoice.option_any:
|
||||
set_rule(world.get_location("Winning Basic", player),
|
||||
set_rule(multiworld.get_location("Winning Basic", player),
|
||||
lambda state: state.has("Finish the Fight Pack", player))
|
||||
if world_options.ending_choice == Options.EndingChoice.option_true:
|
||||
set_rule(world.get_location("Winning Basic", player),
|
||||
set_rule(multiworld.get_location("Winning Basic", player),
|
||||
lambda state: state.has("Armor for your Horse Pack", player) and state.has("Finish the Fight Pack",
|
||||
player))
|
||||
|
||||
|
||||
def set_lfod_rules(world_options, player, world):
|
||||
def set_lfod_rules(world_options, player, multiworld):
|
||||
if world_options.campaign == Options.Campaign.option_basic:
|
||||
return
|
||||
set_lfod_entrance_rules(player, world)
|
||||
set_boss_door_requirements_rules(player, world)
|
||||
set_lfod_self_obtained_items_rules(world_options, player, world)
|
||||
set_lfod_shuffled_items_rules(world_options, player, world)
|
||||
self_lfod_coinsanity_funded_purchase_rules(world_options, player, world)
|
||||
set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, world)
|
||||
set_lfod_entrance_rules(player, multiworld)
|
||||
set_boss_door_requirements_rules(player, multiworld)
|
||||
set_lfod_self_obtained_items_rules(world_options, player, multiworld)
|
||||
set_lfod_shuffled_items_rules(world_options, player, multiworld)
|
||||
self_lfod_coinsanity_funded_purchase_rules(world_options, player, multiworld)
|
||||
set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, multiworld)
|
||||
|
||||
|
||||
def set_lfod_entrance_rules(player, world):
|
||||
set_rule(world.get_entrance("Wall Jump Entrance", player),
|
||||
def set_lfod_entrance_rules(player, multiworld):
|
||||
set_rule(multiworld.get_entrance("Wall Jump Entrance", player),
|
||||
lambda state: state.has("Wall Jump Pack", player))
|
||||
set_rule(world.get_entrance("Harmless Plants", player),
|
||||
set_rule(multiworld.get_entrance("Harmless Plants", player),
|
||||
lambda state: state.has("Harmless Plants Pack", player))
|
||||
set_rule(world.get_entrance("Name Change Entrance", player),
|
||||
set_rule(multiworld.get_entrance("Name Change Entrance", player),
|
||||
lambda state: state.has("Name Change Pack", player))
|
||||
set_rule(world.get_entrance("Cut Content Entrance", player),
|
||||
set_rule(multiworld.get_entrance("Cut Content Entrance", player),
|
||||
lambda state: state.has("Cut Content Pack", player))
|
||||
set_rule(world.get_entrance("Blizzard", player),
|
||||
set_rule(multiworld.get_entrance("Blizzard", player),
|
||||
lambda state: state.has("Season Pass", player))
|
||||
set_rule(world.get_location("I Get That Reference!", player),
|
||||
set_rule(multiworld.get_location("I Get That Reference!", player),
|
||||
lambda state: state.has("Death of Comedy Pack", player))
|
||||
set_rule(world.get_location("Story is Important", player),
|
||||
set_rule(multiworld.get_location("Story is Important", player),
|
||||
lambda state: state.has("DLC NPC Pack", player))
|
||||
|
||||
|
||||
def set_boss_door_requirements_rules(player, world):
|
||||
def set_boss_door_requirements_rules(player, multiworld):
|
||||
sword_1 = "Big Sword Pack"
|
||||
sword_2 = "Really Big Sword Pack"
|
||||
sword_3 = "Unfathomable Sword Pack"
|
||||
|
||||
big_sword_location = world.get_location(sword_1, player)
|
||||
really_big_sword_location = world.get_location(sword_2, player)
|
||||
unfathomable_sword_location = world.get_location(sword_3, player)
|
||||
big_sword_location = multiworld.get_location(sword_1, player)
|
||||
really_big_sword_location = multiworld.get_location(sword_2, player)
|
||||
unfathomable_sword_location = multiworld.get_location(sword_3, player)
|
||||
|
||||
big_sword_valid_locations = [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, world):
|
||||
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_3, player) or unfathomable_sword_during_boss_fight))
|
||||
set_rule(world.get_entrance("Boss Door", player), has_3_swords)
|
||||
set_rule(multiworld.get_entrance("Boss Door", player), has_3_swords)
|
||||
|
||||
|
||||
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"))
|
||||
|
||||
|
||||
def set_lfod_shuffled_items_rules(world_options, player, world):
|
||||
def set_lfod_shuffled_items_rules(world_options, player, multiworld):
|
||||
if world_options.item_shuffle != Options.ItemShuffle.option_shuffled:
|
||||
return
|
||||
set_rule(world.get_entrance("Vines", player),
|
||||
set_rule(multiworld.get_entrance("Vines", player),
|
||||
lambda state: state.has("Live Freemium or Die: Progressive Weapon", player))
|
||||
set_rule(world.get_entrance("Behind Rocks", player),
|
||||
set_rule(multiworld.get_entrance("Behind Rocks", player),
|
||||
lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2))
|
||||
set_rule(world.get_entrance("Pickaxe Hard Cave", player),
|
||||
set_rule(multiworld.get_entrance("Pickaxe Hard Cave", player),
|
||||
lambda state: state.has("Live Freemium or Die: Progressive Weapon", player, 2))
|
||||
|
||||
set_rule(world.get_location("Wooden Sword", player),
|
||||
set_rule(multiworld.get_location("Wooden Sword", player),
|
||||
lambda state: state.has("Incredibly Important Pack", player))
|
||||
set_rule(world.get_location("Pickaxe", player),
|
||||
set_rule(multiworld.get_location("Pickaxe", player),
|
||||
lambda state: state.has("Humble Indie Bindle", player))
|
||||
set_rule(world.get_location("Humble Indie Bindle", player),
|
||||
set_rule(multiworld.get_location("Humble Indie Bindle", player),
|
||||
lambda state: state.has("Box of Various Supplies", player) and
|
||||
state.can_reach("Cut Content", 'region', player))
|
||||
set_rule(world.get_location("Box of Various Supplies", player),
|
||||
set_rule(multiworld.get_location("Box of Various Supplies", player),
|
||||
lambda state: state.can_reach("Cut Content", 'region', player))
|
||||
|
||||
|
||||
def self_lfod_coinsanity_funded_purchase_rules(world_options, player, world):
|
||||
def self_lfod_coinsanity_funded_purchase_rules(world_options, player, multiworld):
|
||||
if world_options.coinsanity != Options.CoinSanity.option_coin:
|
||||
return
|
||||
if world_options.coinbundlequantity == -1:
|
||||
self_lfod_coinsanity_piece_rules(player, world)
|
||||
self_lfod_coinsanity_piece_rules(player, multiworld)
|
||||
return
|
||||
number_of_bundle = math.floor(889 / world_options.coinbundlequantity)
|
||||
for i in range(number_of_bundle):
|
||||
|
||||
item_coin_freemium = f"Live Freemium or Die: {world_options.coinbundlequantity * (i + 1)} Coin"
|
||||
set_rule(world.get_location(item_coin_freemium, player),
|
||||
set_rule(multiworld.get_location(item_coin_freemium, player),
|
||||
has_enough_coin_freemium(player, world_options.coinbundlequantity * (i + 1)))
|
||||
if 889 % world_options.coinbundlequantity != 0:
|
||||
set_rule(world.get_location("Live Freemium or Die: 889 Coin", player),
|
||||
set_rule(multiworld.get_location("Live Freemium or Die: 889 Coin", player),
|
||||
has_enough_coin_freemium(player, 889))
|
||||
|
||||
add_rule(world.get_entrance("Boss Door", player),
|
||||
add_rule(multiworld.get_entrance("Boss Door", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(200 / world_options.coinbundlequantity)))
|
||||
|
||||
set_rule(world.get_location("Particles Pack", player),
|
||||
set_rule(multiworld.get_location("Particles Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Day One Patch Pack", player),
|
||||
set_rule(multiworld.get_location("Day One Patch Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Checkpoint Pack", player),
|
||||
set_rule(multiworld.get_location("Checkpoint Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Incredibly Important Pack", player),
|
||||
set_rule(multiworld.get_location("Incredibly Important Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(15 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Wall Jump Pack", player),
|
||||
set_rule(multiworld.get_location("Wall Jump Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(35 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Health Bar Pack", player),
|
||||
set_rule(multiworld.get_location("Health Bar Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Parallax Pack", player),
|
||||
set_rule(multiworld.get_location("Parallax Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(5 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Harmless Plants Pack", player),
|
||||
set_rule(multiworld.get_location("Harmless Plants Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(130 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Death of Comedy Pack", player),
|
||||
set_rule(multiworld.get_location("Death of Comedy Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(15 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Canadian Dialog Pack", player),
|
||||
set_rule(multiworld.get_location("Canadian Dialog Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(10 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("DLC NPC Pack", player),
|
||||
set_rule(multiworld.get_location("DLC NPC Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(15 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Cut Content Pack", player),
|
||||
set_rule(multiworld.get_location("Cut Content Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(40 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Name Change Pack", player),
|
||||
set_rule(multiworld.get_location("Name Change Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(150 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Season Pass", player),
|
||||
set_rule(multiworld.get_location("Season Pass", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(199 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("High Definition Next Gen Pack", player),
|
||||
set_rule(multiworld.get_location("High Definition Next Gen Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(20 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Increased HP Pack", player),
|
||||
set_rule(multiworld.get_location("Increased HP Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(10 / world_options.coinbundlequantity)))
|
||||
set_rule(world.get_location("Remove Ads Pack", player),
|
||||
set_rule(multiworld.get_location("Remove Ads Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Bundle", player,
|
||||
math.ceil(25 / world_options.coinbundlequantity)))
|
||||
|
||||
|
||||
def set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, world):
|
||||
def set_lfod_self_funded_purchase_rules(world_options, has_enough_coin_freemium, player, multiworld):
|
||||
if world_options.coinsanity != Options.CoinSanity.option_none:
|
||||
return
|
||||
add_rule(world.get_entrance("Boss Door", player),
|
||||
add_rule(multiworld.get_entrance("Boss Door", player),
|
||||
has_enough_coin_freemium(player, 200))
|
||||
|
||||
set_rule(world.get_location("Particles Pack", player),
|
||||
set_rule(multiworld.get_location("Particles Pack", player),
|
||||
has_enough_coin_freemium(player, 5))
|
||||
set_rule(world.get_location("Day One Patch Pack", player),
|
||||
set_rule(multiworld.get_location("Day One Patch Pack", player),
|
||||
has_enough_coin_freemium(player, 5))
|
||||
set_rule(world.get_location("Checkpoint Pack", player),
|
||||
set_rule(multiworld.get_location("Checkpoint Pack", player),
|
||||
has_enough_coin_freemium(player, 5))
|
||||
set_rule(world.get_location("Incredibly Important Pack", player),
|
||||
set_rule(multiworld.get_location("Incredibly Important Pack", player),
|
||||
has_enough_coin_freemium(player, 15))
|
||||
set_rule(world.get_location("Wall Jump Pack", player),
|
||||
set_rule(multiworld.get_location("Wall Jump Pack", player),
|
||||
has_enough_coin_freemium(player, 35))
|
||||
set_rule(world.get_location("Health Bar Pack", player),
|
||||
set_rule(multiworld.get_location("Health Bar Pack", player),
|
||||
has_enough_coin_freemium(player, 5))
|
||||
set_rule(world.get_location("Parallax Pack", player),
|
||||
set_rule(multiworld.get_location("Parallax Pack", player),
|
||||
has_enough_coin_freemium(player, 5))
|
||||
set_rule(world.get_location("Harmless Plants Pack", player),
|
||||
set_rule(multiworld.get_location("Harmless Plants Pack", player),
|
||||
has_enough_coin_freemium(player, 130))
|
||||
set_rule(world.get_location("Death of Comedy Pack", player),
|
||||
set_rule(multiworld.get_location("Death of Comedy Pack", player),
|
||||
has_enough_coin_freemium(player, 15))
|
||||
set_rule(world.get_location("Canadian Dialog Pack", player),
|
||||
set_rule(multiworld.get_location("Canadian Dialog Pack", player),
|
||||
has_enough_coin_freemium(player, 10))
|
||||
set_rule(world.get_location("DLC NPC Pack", player),
|
||||
set_rule(multiworld.get_location("DLC NPC Pack", player),
|
||||
has_enough_coin_freemium(player, 15))
|
||||
set_rule(world.get_location("Cut Content Pack", player),
|
||||
set_rule(multiworld.get_location("Cut Content Pack", player),
|
||||
has_enough_coin_freemium(player, 40))
|
||||
set_rule(world.get_location("Name Change Pack", player),
|
||||
set_rule(multiworld.get_location("Name Change Pack", player),
|
||||
has_enough_coin_freemium(player, 150))
|
||||
set_rule(world.get_location("Season Pass", player),
|
||||
set_rule(multiworld.get_location("Season Pass", player),
|
||||
has_enough_coin_freemium(player, 199))
|
||||
set_rule(world.get_location("High Definition Next Gen Pack", player),
|
||||
set_rule(multiworld.get_location("High Definition Next Gen Pack", player),
|
||||
has_enough_coin_freemium(player, 20))
|
||||
set_rule(world.get_location("Increased HP Pack", player),
|
||||
set_rule(multiworld.get_location("Increased HP Pack", player),
|
||||
has_enough_coin_freemium(player, 10))
|
||||
set_rule(world.get_location("Remove Ads Pack", player),
|
||||
set_rule(multiworld.get_location("Remove Ads Pack", player),
|
||||
has_enough_coin_freemium(player, 25))
|
||||
|
||||
|
||||
def set_completion_condition(world_options, player, world):
|
||||
def set_completion_condition(world_options, player, multiworld):
|
||||
if world_options.campaign == Options.Campaign.option_basic:
|
||||
world.completion_condition[player] = lambda state: state.has("Victory Basic", player)
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Victory Basic", player)
|
||||
if world_options.campaign == Options.Campaign.option_live_freemium_or_die:
|
||||
world.completion_condition[player] = lambda state: state.has("Victory Freemium", player)
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Victory Freemium", player)
|
||||
if world_options.campaign == Options.Campaign.option_both:
|
||||
world.completion_condition[player] = lambda state: state.has("Victory Basic", player) and state.has(
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Victory Basic", player) and state.has(
|
||||
"Victory Freemium", player)
|
||||
|
||||
|
||||
def self_basic_coinsanity_piece_rules(player, world):
|
||||
def self_basic_coinsanity_piece_rules(player, multiworld):
|
||||
for i in range(1,8251):
|
||||
|
||||
item_coin = f"DLC Quest: {i} Coin Piece"
|
||||
set_rule(world.get_location(item_coin, player),
|
||||
set_rule(multiworld.get_location(item_coin, player),
|
||||
has_enough_coin(player, math.ceil(i / 10)))
|
||||
|
||||
set_rule(world.get_location("Movement Pack", player),
|
||||
set_rule(multiworld.get_location("Movement Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 40))
|
||||
set_rule(world.get_location("Animation Pack", player),
|
||||
set_rule(multiworld.get_location("Animation Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Audio Pack", player),
|
||||
set_rule(multiworld.get_location("Audio Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Pause Menu Pack", player),
|
||||
set_rule(multiworld.get_location("Pause Menu Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Time is Money Pack", player),
|
||||
set_rule(multiworld.get_location("Time is Money Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 200))
|
||||
set_rule(world.get_location("Double Jump Pack", player),
|
||||
set_rule(multiworld.get_location("Double Jump Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 100))
|
||||
set_rule(world.get_location("Pet Pack", player),
|
||||
set_rule(multiworld.get_location("Pet Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Sexy Outfits Pack", player),
|
||||
set_rule(multiworld.get_location("Sexy Outfits Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Top Hat Pack", player),
|
||||
set_rule(multiworld.get_location("Top Hat Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Map Pack", player),
|
||||
set_rule(multiworld.get_location("Map Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 1400))
|
||||
set_rule(world.get_location("Gun Pack", player),
|
||||
set_rule(multiworld.get_location("Gun Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 750))
|
||||
set_rule(world.get_location("The Zombie Pack", player),
|
||||
set_rule(multiworld.get_location("The Zombie Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Night Map Pack", player),
|
||||
set_rule(multiworld.get_location("Night Map Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 750))
|
||||
set_rule(world.get_location("Psychological Warfare Pack", player),
|
||||
set_rule(multiworld.get_location("Psychological Warfare Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 500))
|
||||
set_rule(world.get_location("Armor for your Horse Pack", player),
|
||||
set_rule(multiworld.get_location("Armor for your Horse Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 2500))
|
||||
set_rule(world.get_location("Finish the Fight Pack", player),
|
||||
set_rule(multiworld.get_location("Finish the Fight Pack", player),
|
||||
lambda state: state.has("DLC Quest: Coin Piece", player, 50))
|
||||
|
||||
|
||||
def self_lfod_coinsanity_piece_rules(player, world):
|
||||
def self_lfod_coinsanity_piece_rules(player, multiworld):
|
||||
for i in range(1, 8891):
|
||||
|
||||
item_coin_freemium = f"Live Freemium or Die: {i} Coin Piece"
|
||||
set_rule(world.get_location(item_coin_freemium, player),
|
||||
set_rule(multiworld.get_location(item_coin_freemium, player),
|
||||
has_enough_coin_freemium(player, math.ceil(i / 10)))
|
||||
|
||||
add_rule(world.get_entrance("Boss Door", player),
|
||||
add_rule(multiworld.get_entrance("Boss Door", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 2000))
|
||||
|
||||
set_rule(world.get_location("Particles Pack", player),
|
||||
set_rule(multiworld.get_location("Particles Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Day One Patch Pack", player),
|
||||
set_rule(multiworld.get_location("Day One Patch Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Checkpoint Pack", player),
|
||||
set_rule(multiworld.get_location("Checkpoint Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Incredibly Important Pack", player),
|
||||
set_rule(multiworld.get_location("Incredibly Important Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150))
|
||||
set_rule(world.get_location("Wall Jump Pack", player),
|
||||
set_rule(multiworld.get_location("Wall Jump Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 350))
|
||||
set_rule(world.get_location("Health Bar Pack", player),
|
||||
set_rule(multiworld.get_location("Health Bar Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Parallax Pack", player),
|
||||
set_rule(multiworld.get_location("Parallax Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 50))
|
||||
set_rule(world.get_location("Harmless Plants Pack", player),
|
||||
set_rule(multiworld.get_location("Harmless Plants Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1300))
|
||||
set_rule(world.get_location("Death of Comedy Pack", player),
|
||||
set_rule(multiworld.get_location("Death of Comedy Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150))
|
||||
set_rule(world.get_location("Canadian Dialog Pack", player),
|
||||
set_rule(multiworld.get_location("Canadian Dialog Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100))
|
||||
set_rule(world.get_location("DLC NPC Pack", player),
|
||||
set_rule(multiworld.get_location("DLC NPC Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 150))
|
||||
set_rule(world.get_location("Cut Content Pack", player),
|
||||
set_rule(multiworld.get_location("Cut Content Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 400))
|
||||
set_rule(world.get_location("Name Change Pack", player),
|
||||
set_rule(multiworld.get_location("Name Change Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 1500))
|
||||
set_rule(world.get_location("Season Pass", player),
|
||||
set_rule(multiworld.get_location("Season Pass", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 199))
|
||||
set_rule(world.get_location("High Definition Next Gen Pack", player),
|
||||
set_rule(multiworld.get_location("High Definition Next Gen Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 20))
|
||||
set_rule(world.get_location("Increased HP Pack", player),
|
||||
set_rule(multiworld.get_location("Increased HP Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 100))
|
||||
set_rule(world.get_location("Remove Ads Pack", player),
|
||||
set_rule(multiworld.get_location("Remove Ads Pack", player),
|
||||
lambda state: state.has("Live Freemium or Die: Coin Piece", player, 250))
|
||||
|
||||
@@ -37,7 +37,7 @@ class FactorioWeb(WebWorld):
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Berserker, Farrak Kilhn"]
|
||||
["Berserker", "Farrak Kilhn"]
|
||||
)]
|
||||
option_groups = option_groups
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- Factorio: [Factorio Official Website](https://factorio.com)
|
||||
- Needed by Players and Hosts
|
||||
- Players will need to download an additional copy of Factorio for Archipelago to use. If you already own the game, you can download another copy for free by logging into the website.
|
||||
|
||||
##### Server Hosts
|
||||
|
||||
@@ -38,9 +39,13 @@ Validator page: [Yaml Validation Page](/check)
|
||||
Connecting to someone else's game is the simplest way to play Factorio with Archipelago. It allows multiple people to
|
||||
play in a single world, all contributing to the completion of the seed.
|
||||
|
||||
1. Acquire the Archipelago mod for this seed. It should be named `AP_*.zip`, where `*` is the seed number.
|
||||
2. Copy the mod file into your Factorio `mods` folder, which by default is located at:
|
||||
`C:\Users\<YourUserName>\AppData\Roaming\Factorio\mods`
|
||||
**Note:** If nobody else on the multiworld is joining your Factorio game, then you are
|
||||
hosting your own. This includes cases where more than one person is playing Factorio, but
|
||||
all on separate worlds.
|
||||
|
||||
1. Acquire the Archipelago mod for this seed. Once you create the multiworld on the Archipelago website, there will be a link next to your name in the "Download Link" column for your Archipelago room. It will be called `AP-<seed>-<playernumber>-<playername>_<archipelago_version>.zip`.
|
||||
2. Copy the mod file into your Factorio `mods` folder, which by default is located at:
|
||||
`C:\Users\<YourUserName>\AppData\Roaming\Factorio\mods`. If you're on Linux, it's located at `~/.factorio/mods`.
|
||||
3. Get the server address from the person hosting the game you are joining.
|
||||
4. Launch Factorio
|
||||
5. Click on "Multiplayer" in the main menu
|
||||
@@ -66,8 +71,8 @@ This guide uses the following terms to refer to the software:
|
||||
### What a Playable State Looks Like
|
||||
|
||||
- An Archipelago Server
|
||||
- The generated Factorio Mod, created as a result of running `ArchipelagoGenerate.exe`
|
||||
- One running instance of `ArchipelagoFactorioClient.exe` (the Archipelago Client) per Factorio world
|
||||
- The generated Factorio Mod, created as a result of running `ArchipelagoGenerate.exe` or downloaded from
|
||||
- One running instance of the Archipelago Client per Factorio world
|
||||
- A running modded Factorio Server, which should have been started by the Archipelago Client automatically
|
||||
- A running modded Factorio Client
|
||||
|
||||
@@ -75,7 +80,7 @@ This guide uses the following terms to refer to the software:
|
||||
|
||||
To play Factorio with Archipelago, a dedicated server setup is required. This dedicated Factorio Server must be
|
||||
installed separately from your main Factorio Client installation. The recommended way to install two instances of
|
||||
Factorio on your computer is to download the Factorio installer file directly from
|
||||
Factorio on your computer is to download the standalone Factorio file directly from
|
||||
factorio.com: [Factorio Official Website Download Page](https://factorio.com/download).
|
||||
|
||||
#### If you purchased Factorio on Steam, GOG, etc.
|
||||
@@ -88,7 +93,7 @@ Factorio product code. This will allow you to download the game directly from th
|
||||
|
||||
It is recommended to download the standalone version of Factorio for use as a dedicated server. Doing so prevents any
|
||||
potential conflicts with your currently-installed version of Factorio. Download the file by clicking on the button
|
||||
appropriate to your operating system, and extract the folder to a convenient location. The best place to do this for
|
||||
appropriate to your operating system, and extract the folder to a convenient location. The best place to do this for
|
||||
Archipelago is to place the extracted game folder into the `Archipelago` directory and rename it to just be "Factorio".
|
||||
|
||||
|
||||
@@ -100,9 +105,9 @@ have logged in, you may close the game.
|
||||
|
||||
#### Configure your Archipelago Installation
|
||||
|
||||
If you did not place the Factorio standalone in your Archipelago installation, you must modify your `host.yaml` file
|
||||
inside your Archipelago installation directory so that it points to your standalone Factorio executable. Here is an
|
||||
example of the appropriate setup, note the double `\\` are required:
|
||||
If you did not place the Factorio standalone in your Archipelago installation, you must modify your `host.yaml` file
|
||||
inside your Archipelago installation directory so that it points to your standalone Factorio executable. Here is an
|
||||
example of the appropriate setup, note the double `\\` are required if you are on Windows:
|
||||
|
||||
```yaml
|
||||
factorio_options:
|
||||
@@ -115,12 +120,11 @@ This allows you to host your own Factorio game.
|
||||
|
||||
1. Obtain the Factorio mod for this Archipelago seed. It should be named `AP_*.zip`, where `*` is the seed number.
|
||||
2. Install the mod into your Factorio Server by copying the zip file into the `mods` folder.
|
||||
3. Install the mod into your Factorio Client by copying the zip file into the `mods` folder, which is likely located
|
||||
at `C:\Users\YourName\AppData\Roaming\Factorio\mods`.
|
||||
4. Obtain the Archipelago Server address from the website's host room, or from the server host.
|
||||
5. Run your Archipelago Client, which is named `ArchipelagoFactorioClient.exe`. This was installed along with
|
||||
Archipelago if you chose to include it during the installation process.
|
||||
6. Enter `/connect [server-address]` into the input box at the bottom of the Archipelago Client and press "Enter"
|
||||
3. Disable Space Age on your server by editing `mods-list.json` in the server's `mods` folder, going to the Space Age section, and changing `enabled: true` to `enabled: false`.
|
||||
4. Install the mod into your Factorio Client by copying the zip file into the `mods` folder, which is likely located
|
||||
at `C:\Users\YourName\AppData\Roaming\Factorio\mods`. If you're on Linux, it will be located at `~/.factorio/mods`.
|
||||
5. Obtain the Archipelago Server address from the website's host room, or from the server host.
|
||||
6. Open your Archipelago client. Type `/connect <server-address>` into the input box at the bottom of the client and press enter.
|
||||
|
||||

|
||||
|
||||
@@ -138,9 +142,10 @@ For more information about the commands you can use, see the [Commands Guide](/t
|
||||
## Allowing Other People to Join Your Game
|
||||
|
||||
1. Ensure your Archipelago Client is running.
|
||||
2. Ensure port `34197` is forwarded to the computer running the Archipelago Client.
|
||||
3. Obtain your IP address by visiting whatismyip.com: [WhatIsMyIP Website](https://whatismyip.com/).
|
||||
4. Provide your IP address to anyone you want to join your game, and have them follow the steps for
|
||||
2. Ensure port `34197` is open on your computer's firewall.
|
||||
3. Ensure port `34197` is forwarded to the computer running the Archipelago Client. This can be done in your router's settings.
|
||||
4. Obtain your IP address by visiting whatismyip.com: [WhatIsMyIP Website](https://whatismyip.com/).
|
||||
5. Provide your IP address to anyone you want to join your game, and have them follow the steps for
|
||||
"Connecting to Someone Else's Factorio Game" above.
|
||||
|
||||
## Enabling Peaceful Mode
|
||||
@@ -210,8 +215,8 @@ contents of this file may help you troubleshoot an issue on your own and is vita
|
||||
in Archipelago.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Alternate Tutorial by
|
||||
Umenen: [Factorio (Steam) Archipelago Setup Guide for Windows](https://docs.google.com/document/d/1yZPAaXB-QcetD8FJsmsFrenAHO5V6Y2ctMAyIoT9jS4)
|
||||
- Factorio Speedrun Guide: [Factorio Speedrun Guide by Nefrums](https://www.youtube.com/watch?v=ExLrmK1c7tA)
|
||||
- Factorio Wiki: [Factorio Official Wiki](https://wiki.factorio.com/)
|
||||
- [Archipelago Factorio Cheat Sheet](https://docs.google.com/spreadsheets/d/1317PZU957IVWCG9jUUklyHAl82BhrKIriynSZJ0AGNg/edit?gid=0#gid=0)
|
||||
|
||||
@@ -1 +1 @@
|
||||
factorio-rcon-py>=2.1.2
|
||||
factorio-rcon-py==2.1.3
|
||||
|
||||
@@ -43,7 +43,7 @@ class FaxanaduWorld(World):
|
||||
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}
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
self.filler_ratios: Dict[str, int] = {
|
||||
item.name: item.count
|
||||
for item in Items.items
|
||||
@@ -51,7 +51,7 @@ class FaxanaduWorld(World):
|
||||
}
|
||||
# Remove poison by default to respect itemlinking
|
||||
self.filler_ratios["Poison"] = 0
|
||||
super().__init__(world, player)
|
||||
super().__init__(multiworld, player)
|
||||
|
||||
def create_regions(self):
|
||||
Regions.create_regions(self)
|
||||
|
||||
@@ -41,8 +41,8 @@ class FF1Locations:
|
||||
|
||||
@staticmethod
|
||||
def create_menu_region(player: int, locations: Dict[str, int],
|
||||
rules: Dict[str, List[List[str]]], world: MultiWorld) -> Region:
|
||||
menu_region = Region("Menu", player, world)
|
||||
rules: Dict[str, List[List[str]]], multiworld: MultiWorld) -> Region:
|
||||
menu_region = Region("Menu", player, multiworld)
|
||||
for name, address in locations.items():
|
||||
location = Location(player, name, address, menu_region)
|
||||
## TODO REMOVE WHEN LOGIC FOR TOFR IS CORRECT
|
||||
|
||||
@@ -50,8 +50,8 @@ class FF1World(World):
|
||||
|
||||
web = FF1Web()
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
super().__init__(multiworld, player)
|
||||
self.locked_items = []
|
||||
self.locked_locations = []
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ def process_rules(spot, access):
|
||||
add_rule(spot, lambda state: state.has_all(access, spot.player))
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, room_id=None, locations=None, links=None):
|
||||
def create_region(multiworld: MultiWorld, player: int, name: str, room_id=None, locations=None, links=None):
|
||||
if links is None:
|
||||
links = []
|
||||
ret = Region(name, player, world)
|
||||
ret = Region(name, player, multiworld)
|
||||
if locations:
|
||||
for location in locations:
|
||||
location.parent_region = ret
|
||||
|
||||
@@ -52,24 +52,3 @@ class GenericWorld(World):
|
||||
if name == "Nothing":
|
||||
return Item(name, ItemClassification.filler, -1, self.player)
|
||||
raise InvalidItemError(name)
|
||||
|
||||
@deprecated("worlds.generic.PlandoItem is deprecated and will be removed in the next version. "
|
||||
"Use Options.PlandoItem(s) instead.")
|
||||
class PlandoItem(NamedTuple):
|
||||
item: str
|
||||
location: str
|
||||
world: Union[bool, str] = False # False -> own world, True -> not own world
|
||||
from_pool: bool = True # if item should be removed from item pool
|
||||
force: str = 'silent' # false -> warns if item not successfully placed. true -> errors out on failure to place item.
|
||||
|
||||
def warn(self, warning: str):
|
||||
if self.force in ['true', 'fail', 'failure', 'none', 'false', 'warn', 'warning']:
|
||||
logging.warning(f'{warning}')
|
||||
else:
|
||||
logging.debug(f'{warning}')
|
||||
|
||||
def failed(self, warning: str, exception=Exception):
|
||||
if self.force in ['true', 'fail', 'failure']:
|
||||
raise exception(warning)
|
||||
else:
|
||||
self.warn(warning)
|
||||
|
||||
@@ -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.
|
||||
## Prerequisite Software
|
||||
Here is a list of software to install and source code to download.
|
||||
1. Python 3.11 "universal2" or newer from the [macOS Python downloads page](https://www.python.org/downloads/macos/).
|
||||
1. Python 3.11.9 "universal2" or newer from the [macOS Python downloads page](https://www.python.org/downloads/macos/).
|
||||
**Python 3.14 is not supported yet.**
|
||||
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).
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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)
|
||||
|
||||
|
||||
1
worlds/hk/archipelago.json
Normal file
1
worlds/hk/archipelago.json
Normal file
@@ -0,0 +1 @@
|
||||
{"game": "Hollow Knight", "world_version": "0.0.0"}
|
||||
@@ -126,7 +126,7 @@ def enter_hylemxylem(state: CollectionState, player: int) -> bool:
|
||||
|
||||
|
||||
def set_rules(hylics2world):
|
||||
world = hylics2world.multiworld
|
||||
multiworld = hylics2world.multiworld
|
||||
player = hylics2world.player
|
||||
|
||||
extra = hylics2world.options.extra_items_in_logic
|
||||
@@ -135,23 +135,23 @@ def set_rules(hylics2world):
|
||||
start_location = hylics2world.options.start_location
|
||||
|
||||
# Afterlife
|
||||
add_rule(world.get_location("Afterlife: TV", player),
|
||||
add_rule(multiworld.get_location("Afterlife: TV", player),
|
||||
lambda state: cave_key(state, player))
|
||||
|
||||
# New Muldul
|
||||
add_rule(world.get_location("New Muldul: Underground Chest", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Underground Chest", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("New Muldul: TV", player),
|
||||
add_rule(multiworld.get_location("New Muldul: TV", player),
|
||||
lambda state: house_key(state, player))
|
||||
add_rule(world.get_location("New Muldul: Upper House Chest 1", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Upper House Chest 1", player),
|
||||
lambda state: upper_house_key(state, player))
|
||||
add_rule(world.get_location("New Muldul: Upper House Chest 2", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Upper House Chest 2", player),
|
||||
lambda state: upper_house_key(state, player))
|
||||
add_rule(world.get_location("New Muldul: Pot above Vault", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Pot above Vault", player),
|
||||
lambda state: air_dash(state, player))
|
||||
|
||||
# New Muldul Vault
|
||||
add_rule(world.get_location("New Muldul: Rescued Blerol 1", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 1", player),
|
||||
lambda state: (
|
||||
(
|
||||
(
|
||||
@@ -165,7 +165,7 @@ def set_rules(hylics2world):
|
||||
)
|
||||
or enter_hylemxylem(state, player)
|
||||
))
|
||||
add_rule(world.get_location("New Muldul: Rescued Blerol 2", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 2", player),
|
||||
lambda state: (
|
||||
(
|
||||
(
|
||||
@@ -179,194 +179,194 @@ def set_rules(hylics2world):
|
||||
)
|
||||
or enter_hylemxylem(state, player)
|
||||
))
|
||||
add_rule(world.get_location("New Muldul: Vault Left Chest", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Left Chest", player),
|
||||
lambda state: enter_hylemxylem(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Right Chest", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Right Chest", player),
|
||||
lambda state: enter_hylemxylem(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Bomb", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Bomb", player),
|
||||
lambda state: enter_hylemxylem(state, player))
|
||||
|
||||
# Viewax's Edifice
|
||||
add_rule(world.get_location("Viewax's Edifice: Canopic Jar", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Canopic Jar", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Cave Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Cave Sarcophagus", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Shielded Key", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Shielded Key", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Shielded Key", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Shielded Key", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Tower Pot", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Tower Pot", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Tower Jar", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Tower Jar", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Tower Chest", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Tower Chest", player),
|
||||
lambda state: (
|
||||
paddle(state, player)
|
||||
and tower_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Viewax's Edifice: Viewax Pot", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Viewax Pot", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Defeat Viewax", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Defeat Viewax", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: TV", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: TV", player),
|
||||
lambda state: (
|
||||
paddle(state, player)
|
||||
and jail_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Viewax's Edifice: Sage Fridge", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Sage Fridge", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Sage Item 1", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Sage Item 1", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Sage Item 2", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Sage Item 2", player),
|
||||
lambda state: air_dash(state, player))
|
||||
|
||||
# Arcade 1
|
||||
add_rule(world.get_location("Arcade 1: Key", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Key", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Coin Dash", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Coin Dash", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Burrito Alcove 1", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Burrito Alcove 1", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Burrito Alcove 2", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Burrito Alcove 2", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Behind Spikes Banana", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Behind Spikes Banana", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Pyramid Banana", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Pyramid Banana", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Moving Platforms Muscle Applique", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Moving Platforms Muscle Applique", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Bed Banana", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Bed Banana", player),
|
||||
lambda state: paddle(state, player))
|
||||
|
||||
# Airship
|
||||
add_rule(world.get_location("Airship: Talk to Somsnosa", player),
|
||||
add_rule(multiworld.get_location("Airship: Talk to Somsnosa", player),
|
||||
lambda state: worm_room_key(state, player))
|
||||
|
||||
# Foglast
|
||||
add_rule(world.get_location("Foglast: Underground Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Foglast: Underground Sarcophagus", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Foglast: Shielded Key", player),
|
||||
add_rule(multiworld.get_location("Foglast: Shielded Key", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Foglast: TV", player),
|
||||
add_rule(multiworld.get_location("Foglast: TV", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and clicker(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Foglast: Buy Clicker", player),
|
||||
add_rule(multiworld.get_location("Foglast: Buy Clicker", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Foglast: Shielded Chest", player),
|
||||
add_rule(multiworld.get_location("Foglast: Shielded Chest", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Foglast: Cave Fridge", player),
|
||||
add_rule(multiworld.get_location("Foglast: Cave Fridge", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Foglast: Roof Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Foglast: Roof Sarcophagus", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and bridge_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Foglast: Under Lair Sarcophagus 1", player),
|
||||
add_rule(multiworld.get_location("Foglast: Under Lair Sarcophagus 1", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and bridge_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Foglast: Under Lair Sarcophagus 2", player),
|
||||
add_rule(multiworld.get_location("Foglast: Under Lair Sarcophagus 2", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and bridge_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Foglast: Under Lair Sarcophagus 3", player),
|
||||
add_rule(multiworld.get_location("Foglast: Under Lair Sarcophagus 3", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and bridge_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Foglast: Sage Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Foglast: Sage Sarcophagus", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and bridge_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Foglast: Sage Item 1", player),
|
||||
add_rule(multiworld.get_location("Foglast: Sage Item 1", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and bridge_key(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Foglast: Sage Item 2", player),
|
||||
add_rule(multiworld.get_location("Foglast: Sage Item 2", player),
|
||||
lambda state: (
|
||||
air_dash(state, player)
|
||||
and bridge_key(state, player)
|
||||
))
|
||||
|
||||
# Drill Castle
|
||||
add_rule(world.get_location("Drill Castle: Island Banana", player),
|
||||
add_rule(multiworld.get_location("Drill Castle: Island Banana", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Drill Castle: Island Pot", player),
|
||||
add_rule(multiworld.get_location("Drill Castle: Island Pot", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Drill Castle: Cave Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Drill Castle: Cave Sarcophagus", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Drill Castle: TV", player),
|
||||
add_rule(multiworld.get_location("Drill Castle: TV", player),
|
||||
lambda state: air_dash(state, player))
|
||||
|
||||
# Sage Labyrinth
|
||||
add_rule(world.get_location("Sage Labyrinth: Sage Item 1", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Sage Item 1", player),
|
||||
lambda state: deep_key(state, player))
|
||||
add_rule(world.get_location("Sage Labyrinth: Sage Item 2", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Sage Item 2", player),
|
||||
lambda state: deep_key(state, player))
|
||||
add_rule(world.get_location("Sage Labyrinth: Sage Left Arm", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Sage Left Arm", player),
|
||||
lambda state: deep_key(state, player))
|
||||
add_rule(world.get_location("Sage Labyrinth: Sage Right Arm", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Sage Right Arm", player),
|
||||
lambda state: deep_key(state, player))
|
||||
add_rule(world.get_location("Sage Labyrinth: Sage Left Leg", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Sage Left Leg", player),
|
||||
lambda state: deep_key(state, player))
|
||||
add_rule(world.get_location("Sage Labyrinth: Sage Right Leg", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Sage Right Leg", player),
|
||||
lambda state: deep_key(state, player))
|
||||
|
||||
# Sage Airship
|
||||
add_rule(world.get_location("Sage Airship: TV", player),
|
||||
add_rule(multiworld.get_location("Sage Airship: TV", player),
|
||||
lambda state: all_tokens(state, player))
|
||||
|
||||
# Hylemxylem
|
||||
add_rule(world.get_location("Hylemxylem: Upper Chamber Banana", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Upper Chamber Banana", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Across Upper Reservoir Chest", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Across Upper Reservoir Chest", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Chest", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Drained Lower Reservoir Chest", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Burrito 1", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Drained Lower Reservoir Burrito 1", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Burrito 2", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Drained Lower Reservoir Burrito 2", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 1", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Pot 1", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 2", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Pot 2", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 3", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Pot 3", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Sarcophagus", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 1", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Drained Upper Reservoir Burrito 1", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 2", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Drained Upper Reservoir Burrito 2", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 3", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Drained Upper Reservoir Burrito 3", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Upper Reservoir Hole Key", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Upper Reservoir Hole Key", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
|
||||
# extra rules if Extra Items in Logic is enabled
|
||||
if extra:
|
||||
for i in world.get_region("Foglast", player).entrances:
|
||||
for i in multiworld.get_region("Foglast", player).entrances:
|
||||
add_rule(i, lambda state: charge_up(state, player))
|
||||
for i in world.get_region("Sage Airship", player).entrances:
|
||||
for i in multiworld.get_region("Sage Airship", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
charge_up(state, player)
|
||||
and paper_cup(state, player)
|
||||
and worm_room_key(state, player)
|
||||
))
|
||||
for i in world.get_region("Hylemxylem", player).entrances:
|
||||
for i in multiworld.get_region("Hylemxylem", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
charge_up(state, player)
|
||||
and paper_cup(state, player)
|
||||
))
|
||||
|
||||
add_rule(world.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
|
||||
lambda state: (
|
||||
charge_up(state, player)
|
||||
and paper_cup(state, player)
|
||||
@@ -374,9 +374,9 @@ def set_rules(hylics2world):
|
||||
|
||||
# extra rules if Shuffle Party Members is enabled
|
||||
if party:
|
||||
for i in world.get_region("Arcade Island", player).entrances:
|
||||
for i in multiworld.get_region("Arcade Island", player).entrances:
|
||||
add_rule(i, lambda state: party_3(state, player))
|
||||
for i in world.get_region("Foglast", player).entrances:
|
||||
for i in multiworld.get_region("Foglast", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
party_3(state, player)
|
||||
or (
|
||||
@@ -384,197 +384,197 @@ def set_rules(hylics2world):
|
||||
and jail_key(state, player)
|
||||
)
|
||||
))
|
||||
for i in world.get_region("Sage Airship", player).entrances:
|
||||
for i in multiworld.get_region("Sage Airship", player).entrances:
|
||||
add_rule(i, lambda state: party_3(state, player))
|
||||
for i in world.get_region("Hylemxylem", player).entrances:
|
||||
for i in multiworld.get_region("Hylemxylem", player).entrances:
|
||||
add_rule(i, lambda state: party_3(state, player))
|
||||
|
||||
add_rule(world.get_location("Viewax's Edifice: Defeat Viewax", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Defeat Viewax", player),
|
||||
lambda state: party_2(state, player))
|
||||
add_rule(world.get_location("New Muldul: Rescued Blerol 1", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 1", player),
|
||||
lambda state: party_2(state, player))
|
||||
add_rule(world.get_location("New Muldul: Rescued Blerol 2", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Rescued Blerol 2", player),
|
||||
lambda state: party_2(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Left Chest", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Left Chest", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Right Chest", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Right Chest", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Bomb", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Bomb", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("Juice Ranch: Battle with Somsnosa", player),
|
||||
add_rule(multiworld.get_location("Juice Ranch: Battle with Somsnosa", player),
|
||||
lambda state: party_2(state, player))
|
||||
add_rule(world.get_location("Juice Ranch: Somsnosa Joins", player),
|
||||
add_rule(multiworld.get_location("Juice Ranch: Somsnosa Joins", player),
|
||||
lambda state: party_2(state, player))
|
||||
add_rule(world.get_location("Airship: Talk to Somsnosa", player),
|
||||
add_rule(multiworld.get_location("Airship: Talk to Somsnosa", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
|
||||
add_rule(multiworld.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
|
||||
lambda state: party_3(state, player))
|
||||
|
||||
# extra rules if Shuffle Red Medallions is enabled
|
||||
if medallion:
|
||||
add_rule(world.get_location("New Muldul: Upper House Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Upper House Medallion", player),
|
||||
lambda state: upper_house_key(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Rear Left Medallion", player),
|
||||
lambda state: (
|
||||
enter_foglast(state, player)
|
||||
and bridge_key(state, player)
|
||||
and air_dash(state, player)
|
||||
))
|
||||
add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Rear Right Medallion", player),
|
||||
lambda state: (
|
||||
enter_foglast(state, player)
|
||||
and bridge_key(state, player)
|
||||
and air_dash(state, player)
|
||||
))
|
||||
add_rule(world.get_location("New Muldul: Vault Center Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Center Medallion", player),
|
||||
lambda state: (
|
||||
enter_foglast(state, player)
|
||||
and bridge_key(state, player)
|
||||
and air_dash(state, player)
|
||||
))
|
||||
add_rule(world.get_location("New Muldul: Vault Front Left Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Front Left Medallion", player),
|
||||
lambda state: (
|
||||
enter_foglast(state, player)
|
||||
and bridge_key(state, player)
|
||||
and air_dash(state, player)
|
||||
))
|
||||
add_rule(world.get_location("New Muldul: Vault Front Right Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Front Right Medallion", player),
|
||||
lambda state: (
|
||||
enter_foglast(state, player)
|
||||
and bridge_key(state, player)
|
||||
and air_dash(state, player)
|
||||
))
|
||||
add_rule(world.get_location("Viewax's Edifice: Fort Wall Medallion", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Fort Wall Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Jar Medallion", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Jar Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Viewax's Edifice: Sage Chair Medallion", player),
|
||||
add_rule(multiworld.get_location("Viewax's Edifice: Sage Chair Medallion", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Lonely Medallion", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Lonely Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Alcove Medallion", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Alcove Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Arcade 1: Lava Medallion", player),
|
||||
add_rule(multiworld.get_location("Arcade 1: Lava Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Foglast: Under Lair Medallion", player),
|
||||
add_rule(multiworld.get_location("Foglast: Under Lair Medallion", player),
|
||||
lambda state: bridge_key(state, player))
|
||||
add_rule(world.get_location("Foglast: Mid-Air Medallion", player),
|
||||
add_rule(multiworld.get_location("Foglast: Mid-Air Medallion", player),
|
||||
lambda state: air_dash(state, player))
|
||||
add_rule(world.get_location("Foglast: Top of Tower Medallion", player),
|
||||
add_rule(multiworld.get_location("Foglast: Top of Tower Medallion", player),
|
||||
lambda state: paddle(state, player))
|
||||
add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Medallion", player),
|
||||
add_rule(multiworld.get_location("Hylemxylem: Lower Reservoir Hole Medallion", player),
|
||||
lambda state: upper_chamber_key(state, player))
|
||||
|
||||
# extra rules if Shuffle Red Medallions and Party Shuffle are enabled
|
||||
if party and medallion:
|
||||
add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Rear Left Medallion", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Rear Right Medallion", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Center Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Center Medallion", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Front Left Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Front Left Medallion", player),
|
||||
lambda state: party_3(state, player))
|
||||
add_rule(world.get_location("New Muldul: Vault Front Right Medallion", player),
|
||||
add_rule(multiworld.get_location("New Muldul: Vault Front Right Medallion", player),
|
||||
lambda state: party_3(state, player))
|
||||
|
||||
# entrances
|
||||
for i in world.get_region("Airship", player).entrances:
|
||||
for i in multiworld.get_region("Airship", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Arcade Island", player).entrances:
|
||||
for i in multiworld.get_region("Arcade Island", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
airship(state, player)
|
||||
and air_dash(state, player)
|
||||
))
|
||||
for i in world.get_region("Worm Pod", player).entrances:
|
||||
for i in multiworld.get_region("Worm Pod", player).entrances:
|
||||
add_rule(i, lambda state: enter_wormpod(state, player))
|
||||
for i in world.get_region("Foglast", player).entrances:
|
||||
for i in multiworld.get_region("Foglast", player).entrances:
|
||||
add_rule(i, lambda state: enter_foglast(state, player))
|
||||
for i in world.get_region("Sage Labyrinth", player).entrances:
|
||||
for i in multiworld.get_region("Sage Labyrinth", player).entrances:
|
||||
add_rule(i, lambda state: skull_bomb(state, player))
|
||||
for i in world.get_region("Sage Airship", player).entrances:
|
||||
for i in multiworld.get_region("Sage Airship", player).entrances:
|
||||
add_rule(i, lambda state: enter_sageship(state, player))
|
||||
for i in world.get_region("Hylemxylem", player).entrances:
|
||||
for i in multiworld.get_region("Hylemxylem", player).entrances:
|
||||
add_rule(i, lambda state: enter_hylemxylem(state, player))
|
||||
|
||||
# random start logic (default)
|
||||
if start_location == "waynehouse":
|
||||
# entrances
|
||||
for i in world.get_region("Viewax", player).entrances:
|
||||
for i in multiworld.get_region("Viewax", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
air_dash(state, player)
|
||||
and airship(state, player)
|
||||
))
|
||||
for i in world.get_region("TV Island", player).entrances:
|
||||
for i in multiworld.get_region("TV Island", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Shield Facility", player).entrances:
|
||||
for i in multiworld.get_region("Shield Facility", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Juice Ranch", player).entrances:
|
||||
for i in multiworld.get_region("Juice Ranch", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
# random start logic (Viewax's Edifice)
|
||||
elif start_location == "viewaxs_edifice":
|
||||
for i in world.get_region("Waynehouse", player).entrances:
|
||||
for i in multiworld.get_region("Waynehouse", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
air_dash(state, player)
|
||||
or airship(state, player)
|
||||
))
|
||||
for i in world.get_region("New Muldul", player).entrances:
|
||||
for i in multiworld.get_region("New Muldul", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
air_dash(state, player)
|
||||
or airship(state, player)
|
||||
))
|
||||
for i in world.get_region("New Muldul Vault", player).entrances:
|
||||
for i in multiworld.get_region("New Muldul Vault", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
air_dash(state, player)
|
||||
or airship(state, player)
|
||||
))
|
||||
for i in world.get_region("Drill Castle", player).entrances:
|
||||
for i in multiworld.get_region("Drill Castle", player).entrances:
|
||||
add_rule(i, lambda state: (
|
||||
air_dash(state, player)
|
||||
or airship(state, player)
|
||||
))
|
||||
for i in world.get_region("TV Island", player).entrances:
|
||||
for i in multiworld.get_region("TV Island", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Shield Facility", player).entrances:
|
||||
for i in multiworld.get_region("Shield Facility", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Juice Ranch", player).entrances:
|
||||
for i in multiworld.get_region("Juice Ranch", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Sage Labyrinth", player).entrances:
|
||||
for i in multiworld.get_region("Sage Labyrinth", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
# start logic (TV Island)
|
||||
elif start_location == "tv_island":
|
||||
for i in world.get_region("Waynehouse", player).entrances:
|
||||
for i in multiworld.get_region("Waynehouse", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("New Muldul", player).entrances:
|
||||
for i in multiworld.get_region("New Muldul", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("New Muldul Vault", player).entrances:
|
||||
for i in multiworld.get_region("New Muldul Vault", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Drill Castle", player).entrances:
|
||||
for i in multiworld.get_region("Drill Castle", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Viewax", player).entrances:
|
||||
for i in multiworld.get_region("Viewax", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Shield Facility", player).entrances:
|
||||
for i in multiworld.get_region("Shield Facility", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Juice Ranch", player).entrances:
|
||||
for i in multiworld.get_region("Juice Ranch", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Sage Labyrinth", player).entrances:
|
||||
for i in multiworld.get_region("Sage Labyrinth", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
# start logic (Shield Facility)
|
||||
elif start_location == "shield_facility":
|
||||
for i in world.get_region("Waynehouse", player).entrances:
|
||||
for i in multiworld.get_region("Waynehouse", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("New Muldul", player).entrances:
|
||||
for i in multiworld.get_region("New Muldul", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("New Muldul Vault", player).entrances:
|
||||
for i in multiworld.get_region("New Muldul Vault", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Drill Castle", player).entrances:
|
||||
for i in multiworld.get_region("Drill Castle", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Viewax", player).entrances:
|
||||
for i in multiworld.get_region("Viewax", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("TV Island", player).entrances:
|
||||
for i in multiworld.get_region("TV Island", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
for i in world.get_region("Sage Labyrinth", player).entrances:
|
||||
for i in multiworld.get_region("Sage Labyrinth", player).entrances:
|
||||
add_rule(i, lambda state: airship(state, player))
|
||||
|
||||
@@ -5,11 +5,11 @@ from enum import IntFlag
|
||||
from typing import Any, ClassVar, Dict, Iterator, List, Set, Tuple, Type
|
||||
|
||||
import settings
|
||||
from BaseClasses import Item, ItemClassification, Location, MultiWorld, Region, Tutorial
|
||||
from BaseClasses import CollectionRule, Item, ItemClassification, Location, MultiWorld, Region, Tutorial
|
||||
from Options import PerGameCommonOptions
|
||||
from Utils import __version__
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from worlds.generic.Rules import add_rule, CollectionRule, set_rule
|
||||
from worlds.generic.Rules import add_rule, set_rule
|
||||
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 .Locations import l2ac_location_name_to_id, L2ACLocation
|
||||
|
||||
@@ -478,7 +478,7 @@ def space_zone_2_boss(state, player):
|
||||
|
||||
def space_zone_2_coins(state, player, coins):
|
||||
auto_scroll = is_auto_scroll(state, player, "Space Zone 2")
|
||||
reachable_coins = 12
|
||||
reachable_coins = 9
|
||||
if state.has_any(["Mushroom", "Fire Flower", "Carrot", "Space Physics"], player):
|
||||
reachable_coins += 15
|
||||
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))):
|
||||
reachable_coins += 3
|
||||
if state.has("Space Physics", player):
|
||||
reachable_coins += 79
|
||||
reachable_coins += 82
|
||||
if not auto_scroll:
|
||||
reachable_coins += 21
|
||||
return coins <= reachable_coins
|
||||
|
||||
@@ -192,7 +192,7 @@ class MessengerRules:
|
||||
or (self.has_dart(state) and self.has_wingsuit(state)),
|
||||
# Dark Cave
|
||||
"Dark Cave - Right -> Dark Cave - Left":
|
||||
lambda state: state.has("Candle", self.player) and self.has_dart(state),
|
||||
lambda state: state.has("Candle", self.player) and self.has_dart(state) and self.has_wingsuit(state),
|
||||
# Riviere Turquoise
|
||||
"Riviere Turquoise - Waterfall Shop -> Riviere Turquoise - Flower Flight Checkpoint":
|
||||
lambda state: self.has_dart(state) or (
|
||||
|
||||
@@ -95,10 +95,10 @@ class MM3World(World):
|
||||
web = MM3WebWorld()
|
||||
rom_name: bytearray
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
self.rom_name = bytearray()
|
||||
self.rom_name_available_event = threading.Event()
|
||||
super().__init__(world, player)
|
||||
super().__init__(multiworld, player)
|
||||
self.weapon_damage = deepcopy(weapon_damage)
|
||||
self.wily_4_weapons: dict[int, list[int]] = {}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"game": "Mega Man 3",
|
||||
"authors": ["Silvris"],
|
||||
"world_version": "0.1.7",
|
||||
"world_version": "0.1.8",
|
||||
"minimum_ap_version": "0.6.4"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user