80 Commits
Author SHA1 Message Date
karafruit 029e8de852 Log teleporter replacements instead of printing to STDOUT 2026-01-24 20:58:20 -06:00
karafruit ca9af423db Add pyproject.toml 2026-01-24 13:36:21 -06:00
karafruit 84adf1dd89 Fix back of Tavern rules 2025-04-03 16:26:01 -05:00
karafruit 1fcdbba066 Require sword to use quake for killing deadrocks 2025-03-31 15:08:20 -05:00
karafruit 106eadc26d Add some fixes for TT logic 2025-03-27 05:42:59 -05:00
karafruit d6a16236f5 Update base rom 2025-03-26 15:56:45 -05:00
karafruit e6d24db03e Include playthrough in JSON spoiler 2025-03-08 18:32:18 -06:00
karafruit 9679b182ac Update baserom 2025-03-08 00:10:36 -06:00
karafruit 816ddb63e2 Update baserom 2025-03-06 13:01:28 -06:00
karafruit fc12f2627b Update regex strings to be raw strings 2025-03-06 10:44:51 -06:00
karafruit 1d84a6b348 Add --outputname arg 2025-03-06 10:27:12 -06:00
karafruit 1df52ecf6e Replace teleporters that can't be placed with red rupees 2025-03-05 19:23:40 -06:00
karafruit 88224a5aca Add spoiler json output option 2025-03-05 19:06:19 -06:00
karafruit 8ecf4e530a Add special logic 2025-02-25 19:20:03 -06:00
karafruit 1c492a9fd3 Massage item pool 2024-11-25 13:38:35 -06:00
cassidy 96c00321dd Merge pull request #10 from KrisDavie/er-br-pendants-fix
fix: Make pendants consistent between in-game, spoiler and memory
2024-03-19 13:37:03 -04:00
KrisDavie cf7acd2b05 fix: Make pendants consistent between in-game, spoiler and memory 2024-03-17 10:48:09 +01:00
cassidoxa 8a1ac4d451 Fix map reveal typo 2023-11-17 17:49:37 -05:00
cassidoxa aa0a559381 Update ROM 2023-09-17 14:44:05 -04:00
cassidoxa 64196216a4 Update ROM 2023-09-09 12:23:09 -04:00
cassidoxa 228026dc24 Update ROM 2023-09-08 09:25:21 -04:00
cassidoxa 71610e9aa7 Fix typo 2023-09-07 00:28:00 -04:00
cassidoxa d4a76cc9be Remove extra unused ganonhunt sign text 2023-09-05 21:18:59 -04:00
cassidoxa f53c5b7127 Update ROM 2023-09-04 22:39:40 -04:00
cassidoxa 4b9159dd0a Fix Ganon requirement sign 2023-09-04 15:58:49 -04:00
cassidoxa 1862ed0b64 Update ROM for small palette fix 2023-09-03 15:46:44 -04:00
cassidoxa cb5737a670 Fix completionist
Update ROM
2023-09-02 23:37:43 -04:00
cassidoxa 409bab1f20 Update ROM 2023-08-31 16:40:45 -04:00
cassidoxa 55896cd3d9 Update ROM 2023-08-31 15:22:20 -04:00
cassidoxa eeba10fd8b Update ROM 2023-08-30 23:46:15 -04:00
cassidoxa 408e9c2fc6 Update ROM 2023-08-29 16:01:23 -04:00
cassidoxa 5b9f7675e8 Update ROM 2023-08-26 23:25:30 -04:00
cassidoxa e4e0a9f5a1 Add fastrom disable option 2023-08-26 20:31:02 -04:00
cassidoxa aaaa7b9239 Update ROM 2023-08-26 14:39:42 -04:00
cassidoxa 5a6aa4abed Update ROM 2023-08-26 01:45:50 -04:00
cassidoxa 21a6f3634b Fix Link Face
Update ROM
2023-08-25 01:56:01 -04:00
cassidoxa ad59be106a Update ROM 2023-08-22 16:24:12 -04:00
cassidoxa efe5b09c31 Update for new GT/Ganon/Ped system writes 2023-08-21 20:56:41 -04:00
cassidoxa 03d1de90d3 Fix pendants 2023-08-21 12:44:59 -04:00
cassidoxa 4f6437156a Revert 100% locations logic changes 2023-08-20 22:16:28 -04:00
cassidoxa f7e0a2aa24 Revert "100% Locations logic cleanup"
This reverts commit 6ca3803076.
2023-08-20 21:01:43 -04:00
cassidoxa 69a9988a6a Update rom 2023-08-20 11:50:52 -04:00
cassidoxa 03e6cedc1a Fix league winners 2023-08-19 17:48:55 -04:00
cassidoxa 2c19a205e1 Tournament winners 2023-08-19 16:55:40 -04:00
cassidoxa 4071cbc702 Use vanilla keys in no-logic when keys aren't shuffled outside dungeons 2023-08-18 00:46:57 -04:00
cassidoxa 3226e067be Update rom 2023-08-16 00:12:54 -04:00
cassidoxa 72de30826c Fix item counter HUD for completionist 2023-08-14 14:26:16 -04:00
cassidoxa 2d955b451e Heart colors 2023-08-12 21:04:19 -04:00
cassidoxa ff4963c056 Text remapping
Fix bombos tablet logic
2023-08-11 20:16:00 -04:00
cassidoxa b1a33492b3 Merge branch 'AVIANART-alttpr-com' into alttpr-com 2023-08-07 22:43:19 -04:00
cassidoxa a26b71f23f Merge branch 'alttpr-com' of https://github.com/AVIANART/ALttPEntranceRandomizer into AVIANART-alttpr-com 2023-08-07 22:42:56 -04:00
cassidoxa da0aa08744 Update ROM 2023-08-07 19:02:27 -04:00
Cody e41b44aa9e Update Items.py
Fix some typos, update hookshot text
2023-08-07 17:32:41 -04:00
cassidoxa 74196cab7f Fix crystal offsets 2023-08-07 15:14:43 -04:00
cassidoxa 940b7f59fd Free Crystals
Zelda mirror fix
Use free dungeon items always
2023-08-07 13:47:52 -04:00
Cody Bailey ef38de7827 Make item hint text less ambiguous 2023-07-30 18:17:40 -04:00
cassidoxa 6ca3803076 100% Locations logic cleanup 2023-06-14 16:55:49 -04:00
cassidoxa 07e8577907 Update deprecated collections imports 2023-06-14 16:42:51 -04:00
cassidoxa c4e6fbb8cd Ganonhunt 2023-06-12 13:52:17 -04:00
cassidoxa 0c871a2239 Update ROM 2023-06-11 00:07:23 -04:00
cassidoxa 6d19f6c8d1 Completionist mode
100% Locations logic fixes
2023-06-09 19:22:42 -04:00
cassidoxa b24c118572 InitialSram: Fix starting sword level in swordless mode 2023-03-30 15:22:37 -04:00
cassidoxa edd251bcad Remove LW/DW light cone options and logic
Add dots to ped credits sequence
2023-03-12 17:52:53 -04:00
cassidoxa 5f74519a24 Update base rom 2023-02-12 18:58:50 -05:00
cassidoxa 6586fc3c7f Remove triforce text via community suggestion 2023-01-25 20:05:50 -05:00
cassidoxa 89ffa115b4 New font 2022-12-15 19:49:16 -05:00
cassidoxa ba4eff1692 Fix Waterfall of Wishing connection not requiring flippers 2022-11-24 20:01:17 -05:00
cassidoxa 1b8592b500 Require 4 keys to go from crystaroller to TR dark maze.
But not the other way around. To prevent some silly edge case I forgot
2022-11-24 19:32:25 -05:00
cassidoxa 0e3aee1211 Add menu boss icon for no logic but not regular key shuffle modes 2022-11-24 19:27:42 -05:00
compiling cecba98b46 Move Red Shield Shop to LW_walkable_entrances, since you can't walk out with the bomb. 2022-11-24 19:20:19 -05:00
cassidoxa 9581cba352 Merge branch 'inverted-portals-er' into alttpr-com 2022-11-24 19:18:10 -05:00
cassidoxa 298b7e5294 Merge branch 'aga_door_glitched' into alttpr-com 2022-11-24 19:17:16 -05:00
cassidoxa 1abae11178 Fix Aga Tower/SW curtains
Update ROM
2022-11-24 19:07:00 -05:00
cassidoxa a9364beaa0 Add TR bomb door pre-opening
Update base rom
2022-10-31 15:45:04 -04:00
cassidoxa 58dc7f96c2 Combine starting max ammo and capacity upgrades into single capacity 2022-08-08 17:02:54 -04:00
cassidoxa a464fee64b Fix Swordless 2022-04-20 22:10:51 -04:00
cassidoxa 89bf67119f Fix cap upgrades 2022-04-20 20:11:32 -04:00
cassidoxa 8795a4b393 Add initial SRAM class and refactor to use it 2022-02-20 00:21:33 -05:00
cassidy e1afd4374f Remove HC escape exit barriers in glitched modes and vanilla shuffle
Unlock Aga Tower door during escape in glitched modes
2021-12-22 23:20:28 -05:00
cassidy 4b534fe185 Remove diggable portals in inverted 2021-10-21 22:26:47 -04:00
691 changed files with 11547 additions and 84664 deletions
-1
View File
@@ -1 +0,0 @@
* text=auto
@@ -1,51 +0,0 @@
name: Prepare AppVersion
description: Prepare AppVersion document for later use
runs:
using: "composite"
steps:
# checkout commit
- name: Checkout commit
shell: bash
run: |
echo "Checkout commit"
- name: Checkout commit
uses: actions/checkout@v4.1.4
# Set Run Number
- name: Set Run Number
shell: bash
run: |
echo "Set Run Number"
- name: Set Run Number
id: set_run_number
shell: bash
run: |
GITHUB_RUN_NUMBER="${{ github.run_number }}a${{ github.run_attempt }}"
echo "github_run_number=$GITHUB_RUN_NUMBER" >> $GITHUB_OUTPUT
# Prepare AppVersion
#TODO: source/classes/appversion.py writes the tag format
- name: 💬Prepare AppVersion
shell: bash
run: |
echo "💬Prepare AppVersion"
- name: Prepare AppVersion
shell: bash
env:
OS_NAME: ${{ inputs.os-name }}
GITHUB_RUN_NUMBER: ${{ steps.set_run_number.outputs.github_run_number }}
run: |
python -m source.classes.appversion
python ./resources/ci/common/prepare_appversion.py
# upload appversion artifact for later step
- name: 🔼Upload AppVersion Artifact
shell: bash
run: |
echo "🔼Upload AppVersion Artifact"
- name: 🔼Upload AppVersion Artifact
uses: actions/upload-artifact@v4.3.3
with:
name: appversion
path: ./resources/app/meta/manifests/app_version.txt
-88
View File
@@ -1,88 +0,0 @@
name: Build
description: Build app
inputs:
calling-job:
required: true
description: Job that's calling this one
os-name:
required: true
description: OS to run on
python-version:
required: true
description: Python version to install
runs:
using: "composite"
steps:
# checkout commit
- name: Checkout commit
shell: bash
run: |
echo "Checkout commit"
- name: Checkout commit
uses: actions/checkout@v4.1.4
# get parent dir
- name: Get Parent Directory
id: parentDir
uses: ./.github/actions/get-parent-dir
# try to get UPX
- name: Get UPX
shell: bash
run: |
echo "Get UPX"
- name: Get UPX
shell: bash
env:
OS_NAME: ${{ inputs.os-name }}
UPX_VERSION: "4.2.3"
run: |
python ./resources/ci/common/get_upx.py
# run build.py
- name: 💬Build Binaries
shell: bash
run: |
echo "💬Build Binaries"
- name: Build Binaries
shell: bash
run: |
pip install pyinstaller
python -m source.meta.build
# upload problem children
# - name: 🔼Upload Problem Children Artifact
# shell: bash
# run: |
# echo "🔼Upload Problem Children Artifact"
# - name: 🔼Upload Problem Children Artifact
# uses: actions/upload-artifact@v4.3.3
# with:
# name: problemchildren-${{ inputs.os-name }}-py${{ inputs.python-version }}
# path: ./resources/app/meta/manifests/excluded_dlls.json
# if-no-files-found: ignore # 'warn' or 'ignore' are also available, defaults to `warn`
# prepare binary artifact for later step
- name: 💬Prepare Binary Artifact
shell: bash
run: |
echo "💬Prepare Binary Artifact"
- name: Prepare Binary Artifact
shell: bash
env:
OS_NAME: ${{ inputs.os-name }}
run: |
python ./resources/ci/common/prepare_binary.py
# upload binary artifact for later step
- name: 🔼Upload Binary Artifact
shell: bash
run: |
echo "🔼Upload Binary Artifact"
- name: 🔼Upload Binary Artifact
uses: actions/upload-artifact@v4.3.3
with:
name: binary-${{ inputs.os-name }}-py${{ inputs.python-version }}
path: ${{ steps.parentDir.outputs.parentDir }}/artifact
if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn`
-41
View File
@@ -1,41 +0,0 @@
name: 📁Get Parent Directory
description: Get Parent Directory
outputs:
parentDirNotWin:
description: "Parent Directory (!Windows)"
value: ${{ steps.parentDirNotWin.outputs.value }}
parentDir:
description: "Parent Directory (Windows)"
value: ${{ steps.parentDir.outputs.value }}
#########
# actions
#########
# mad9000/actions-find-and-replace-string@5
runs:
using: "composite"
steps:
# get parent directory
- name: Get Repo Name
uses: mad9000/actions-find-and-replace-string@5
id: repoName
with:
source: ${{ github.repository }}
find: "${{ github.repository_owner }}/"
replace: ""
- name: 📁Get Parent Directory Path (!Windows)
uses: mad9000/actions-find-and-replace-string@5
id: parentDirNotWin
with:
source: ${{ github.workspace }}
find: "${{ steps.repoName.outputs.value }}/${{ steps.repoName.outputs.value }}"
replace: ${{ steps.repoName.outputs.value }}
- name: 📁Get Parent Directory Path (Windows)
uses: mad9000/actions-find-and-replace-string@5
id: parentDir
with:
source: ${{ steps.parentDirNotWin.outputs.value }}
find: '${{ steps.repoName.outputs.value }}\${{ steps.repoName.outputs.value }}'
replace: ${{ steps.repoName.outputs.value }}
-49
View File
@@ -1,49 +0,0 @@
name: 💿Install
description: Install app
inputs:
calling-job:
required: true
description: Job that's calling this one
os-name:
required: true
description: OS to run on
python-version:
required: true
description: Python version to install
#########
# actions
#########
# actions/checkout@v4.1.4
# actions/setup-python@v5.1.0
# actions/upload-artifact@v4.3.3
runs:
using: "composite"
steps:
# install python
- name: 💿Install Python
uses: actions/setup-python@v5.1.0
with:
python-version: ${{ inputs.python-version }}
# install modules via pip
- name: 💿Install Modules
shell: bash
env:
OS_NAME: ${{ inputs.os-name }}
run: |
echo "Install Modules"
python ./resources/ci/common/get_pipline.py
# print pipline
- name: PipLine
shell: bash
run: |
echo "PipLine"
cat ./resources/user/meta/manifests/pipline.txt
# upload pipline
- name: 🔼Upload PipLine
uses: actions/upload-artifact@v4.3.3
with:
name: pipline-${{ inputs.calling-job }}-${{ inputs.os-name }}-py${{ inputs.python-version }}
path: ./resources/user/meta/manifests
if: contains(inputs.calling-job, 'test')
@@ -1,77 +0,0 @@
name: 📀->📦Prepare Release
description: Prepare Release for Deployment
inputs:
os-name:
required: true
description: OS to run on
python-version:
required: true
description: Python version to install
#########
# actions
#########
# Artheau/SpriteSomething/get-parent-dir
# actions/checkout@v4.1.4
# actions/download-artifact@v4.1.7
runs:
using: "composite"
steps:
# checkout commit
- name: ✔️Checkout commit
shell: bash
run: |
echo "✔️Checkout commit"
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
# get parent dir
- name: 📁Get Parent Directory
shell: bash
run: |
echo "📁Get Parent Directory"
- name: 📁Get Parent Directory
id: parentDir
uses: ./.github/actions/get-parent-dir
# download binary artifact
- name: 🔽Download Binary Artifact
shell: bash
run: |
echo "🔽Download Binary Artifact"
- name: 🔽Download Binary Artifact
uses: actions/download-artifact@v4.1.7
with:
name: binary-${{ inputs.os-name }}-py${{ inputs.python-version }}
path: ./
# download appversion artifact
- name: 🔽Download AppVersion Artifact
uses: actions/download-artifact@v4.1.7
with:
name: appversion
path: ${{ steps.parentDir.outputs.parentDir }}/build
# Prepare Release
- name: 💬Prepare Release
shell: bash
run: |
echo "💬Prepare Release"
- name: Prepare Release
shell: bash
env:
OS_NAME: ${{ inputs.os-name }}
run: |
python ./resources/ci/common/prepare_release.py
# upload archive artifact for later step
- name: 🔼Upload Archive Artifact
shell: bash
run: |
echo "🔼Upload Archive Artifact"
- name: 🔼Upload Archive Artifact
uses: actions/upload-artifact@v4.3.3
with:
name: archive-${{ inputs.os-name }}-py${{ inputs.python-version }}
path: ${{ steps.parentDir.outputs.parentDir }}/deploy
-76
View File
@@ -1,76 +0,0 @@
name: 🏷️Tag Repository
description: Tag a repository
inputs:
repository:
description: "Repository Owner/Name; octocat/Hello-World"
required: true
ref-name:
description: "Reference name; branch, tag, etc"
required: true
github-tag:
description: "Reference to tag with"
required: true
debug:
description: "Debug Mode, won't set tag"
required: false
default: "false"
runs:
using: "composite"
steps:
- name: 🏷️Tag Repository
uses: actions/github-script@v7.0.1
with:
github-token: ${{ env.FINE_PAT }}
script: |
const debug = ${{ inputs.debug }} == "true" || ${{ inputs.debug }} == true;
const repository = '${{ inputs.repository }}';
const owner = repository.substring(0,repository.indexOf('/'));
const repo = repository.substring(repository.indexOf('/')+1);
const ref = '${{ inputs.ref-name }}';
// get git tag
const gitTag = '${{ inputs.github-tag }}';
console.log('Repo Data: ', `${owner}/${repo}@${ref}`)
console.log('Git tag: ', gitTag)
if(gitTag == '') {
let msg = 'Result: 🔴No Git Tag sent, aborting!';
console.log(msg)
core.setFailed(msg)
return
}
// get latest commit
const latestCommit = await github.rest.git.getRef({
owner: owner,
repo: repo,
ref: ref
})
// get latest refs
const latestRefs = await github.rest.git.listMatchingRefs({
owner: owner,
repo: repo
})
let latestTag = ''; // bucket for latest tag
// get last tag in data
for(let thisRef of latestRefs.data) {
if(thisRef['ref'].indexOf('tags') > -1) {
let refParts = thisRef['ref'].split('/');
latestTag = refParts[-1];
}
}
console.log('Latest tag:', latestTag)
if(latestTag != gitTag) {
if(debug) {
console.log(`DEBUG: 🔵Creating '${gitTag}' tag`)
} else {
console.log(`Result: 🟢Creating '${gitTag}' tag`)
github.rest.git.createRef({
owner: owner,
repo: repo,
ref: `refs/tags/${gitTag}`,
sha: latestCommit.data.object.sha
})
}
} else {
console.log('Result: 🟡Not creating release tag')
}
-97
View File
@@ -1,97 +0,0 @@
name: ⏱️Test
description: Test app
inputs:
os-name:
required: true
description: OS to run on
python-version:
required: true
description: Python version to install
#########
# actions
#########
# actions/checkout@v4.1.4
# actions/download-artifact@v4.1.7
# actions/upload-artifact@v4.3.3
# coactions/setup-xvfb@v1.0.1
runs:
using: "composite"
steps:
# download pipline
- name: 🔽Download PipLine
shell: bash
run: |
echo "🔽Download PipLine"
- name: 🔽Download PipLine
uses: actions/download-artifact@v4.1.7
with:
name: pipline-test-${{ inputs.os-name }}-py${{ inputs.python-version }}
path: ./resources/user/meta/manifests
# run tests
- name: 🖥️Test Base
shell: bash
run: |
echo "🖥️Test Base"
- name: 🖥️Test Base
shell: bash
run: |
mkdir -p ./failures
echo "" > ./failures/errors.txt
python -m pip install tqdm
python ./test/NewTestSuite.py
# - name: 🖥️Test Mystery
# shell: bash
# run: |
# echo "🖥️Test Mystery"
# if: contains(inputs.os-name, 'macos')
# - name: 🖥️Test Mystery
# shell: bash
# run: |
# python ./test/MysteryTestSuite.py
# if: contains(inputs.os-name, 'macos')
# upload logs
- name: 🔼Upload Logs
shell: bash
run: |
echo "🔼Upload Logs"
- name: 🔼Upload Logs
uses: actions/upload-artifact@v4.3.3
with:
name: logs-${{ inputs.os-name }}-py${{ inputs.python-version }}
path: ./logs
if-no-files-found: ignore
# print failures
- name: 💬Print Failures
if: failure()
shell: bash
run: |
echo "💬Print Failures"
- name: Print Failures
if: failure()
shell: bash
run: |
ERR_STRING="$(cat ./failures/errors.txt)"
ERR_STRING="${ERR_STRING//'%'/'%25'}"
ERR_STRING="${ERR_STRING//$'\n'/' | '}"
ERR_STRING="${ERR_STRING//$'\r'/' | '}"
ERR_STRING="${ERR_STRING//$'\n'/'%0A'}"
ERR_STRING="${ERR_STRING//$'\r'/'%0D'}"
echo "::error ::$ERR_STRING"
# upload failures
- name: 🔼Upload Failures
if: failure()
shell: bash
run: |
echo "🔼Upload Failures"
- name: 🔼Upload Failures
if: failure()
uses: actions/upload-artifact@v4.3.3
with:
name: failures-${{ inputs.os-name }}-py${{ inputs.python-version }}
path: ./failures
-17
View File
@@ -1,17 +0,0 @@
# Set to true to add reviewers to pull requests
addReviewers: true
# Set to true to add assignees to pull requests
addAssignees: true
# A list of reviewers to be added to pull requests (GitHub user name)
reviewers:
- aerinon
# A list of keywords to be skipped the process that add reviewers if pull requests include it
skipKeywords:
- wip
# A number of reviewers added to the pull request
# Set 0 to add all the reviewers (default: 0)
numberOfReviewers: 0
@@ -1,47 +0,0 @@
# workflow name
name: 🏷️Tag Repositories
# Fine-grained personal access token
# https://github.com/settings/tokens?type=beta
# token needs perms:
# actions: read/write
# commit statuses: read/write
# contents: read/write
# workflows: read/write
# copy token
# Actions secrets and variables
# github.com/<owner>/<repo>/settings/secrets/actions
# repository secret
# name a new secret "ALTTPER_TAGGER"
# value set to copied token
# fire on
on:
release:
types:
- released
jobs:
# Tag Baserom
tag-baserom:
name: 🖳Tag Baserom
runs-on: ${{ matrix.os-name }}
strategy:
matrix:
os-name: [
# ubuntu-latest
"ubuntu-22.04"
]
steps:
# call checkout
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
- name: 🏷️Tag Repository
uses: ./.github/actions/tag-repo
env:
FINE_PAT: ${{ secrets.ALTTPER_TAGGER }}
with:
repository: ${{ github.repository_owner }}/z3randomizer
ref-name: heads/OWMain
github-tag: ${{ github.event.release.tag_name }}
-415
View File
@@ -1,415 +0,0 @@
# workflow name
name: ⏱️Test/🔨Build/🚀Deploy
# fire on
on:
push:
branches:
# - DoorDevUnstable
# - DoorDev
- OverworldShuffle
pull_request:
branches:
# - DoorDevUnstable
# - DoorDev
- OverworldShuffle
# stuff to do
jobs:
# Diagnostics
diags:
# diagnostics
# call checkout
# call install python
# print python version
# call install
# call analyze github actions
# install extra python modules
# run diagnostics
name: 🧮
runs-on: ${{ matrix.os-name }}
continue-on-error: True
strategy:
matrix:
#TODO: OS List to run on
os-name: [
# ubuntu-latest, # ubuntu-22.04
ubuntu-22.04,
macos-latest, # macos-12
windows-latest # windows-2022
]
#TODO: Python Version to run on
python-version: [ "3.12" ]
steps:
# call checkout
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
# call install python
- name: 💿Install Python
uses: actions/setup-python@v5.1.0
with:
python-version: ${{ matrix.python-version }}
# print python version
- name: 🐍Python Version
shell: bash
run: |
python --version
# call install
- name: 💿Call Install
uses: ./.github/actions/install
with:
calling-job: diags
os-name: ${{ matrix.os-name }}
python-version: ${{ matrix.python-version }}
# call analyze github actions
- name: ⚙️Analyze used GitHub Actions
shell: bash
run: |
python ./resources/ci/common/list_actions.py
# install extra python modules
- name: 💿Install extra Python Modules
shell: bash
run: |
python -m pip install setuptools
# run diagnostics
- name: 🧮Print Diagnostics
shell: bash
run: |
python -m source.meta.run_diags
# Test
install-test:
# test
# call checkout
# call install
# run tests
name: 💿/⏱️
runs-on: ${{ matrix.os-name }}
continue-on-error: False
strategy:
matrix:
#TODO: OS List to run on
os-name: [
# ubuntu-latest, # ubuntu-22.04
ubuntu-22.04,
macos-latest, # macos-12
windows-latest # windows-2022
]
#TODO: Python Version to run on
python-version: [ "3.12" ]
steps:
# call checkout
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
# call install
- name: 💿Call Install
uses: ./.github/actions/install
with:
calling-job: test
os-name: ${{ matrix.os-name }}
python-version: ${{ matrix.python-version }}
# call test
- name: ⏱️Call Test
uses: ./.github/actions/test
with:
os-name: ${{ matrix.os-name }}
python-version: ${{ matrix.python-version }}
# Prepare AppVersion
appversion-prepare:
# prepare appversion
# call checkout
# call install
# call appversion-prepare
name: 💬
runs-on: ${{ matrix.os-name }}
needs: [install-test]
continue-on-error: False
strategy:
matrix:
#TODO: OS List to run on
os-name: [
# ubuntu-latest, # ubuntu-22.04
ubuntu-22.04,
]
#TODO: Python Version to run on
python-version: [ "3.12" ]
steps:
# call checkout
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
# call install
- name: 💿Call Install
uses: ./.github/actions/install
with:
calling-job: appversion-prepare
os-name: ${{ matrix.os-name }}
python-version: ${{ matrix.python-version }}
# call appversion-prepare
- name: 💬Call Prepare AppVersion
uses: ./.github/actions/appversion-prepare
# Build
install-build:
# build
# call checkout
# call install
# call build
name: 💿/🔨
runs-on: ${{ matrix.os-name }}
needs: [appversion-prepare]
continue-on-error: False
strategy:
matrix:
#TODO: OS List to run on
os-name: [
# ubuntu-latest, # ubuntu-22.04
ubuntu-22.04,
macos-latest, # macos-12
windows-latest # windows-2022
]
#TODO: Python Version to run on
python-version: [ "3.12" ]
steps:
# call checkout
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
# call install
- name: 💿Call Install
uses: ./.github/actions/install
with:
calling-job: build
os-name: ${{ matrix.os-name }}
python-version: ${{ matrix.python-version }}
# call build
- name: 🔨Call Build
uses: ./.github/actions/build
with:
calling-job: build
os-name: ${{ matrix.os-name }}
python-version: ${{ matrix.python-version }}
# Prepare Release
release-prepare:
# prepare release
# call checkout
# install extra python modules
# call prepare release
name: 💿/📀->📦
runs-on: ${{ matrix.os-name }}
needs: [install-build]
continue-on-error: False
strategy:
matrix:
#TODO: OS List to run on
os-name: [
# ubuntu-latest, # ubuntu-22.04
ubuntu-22.04,
macos-latest, # macos-12
windows-latest # windows-2022
]
python-version: [ "3.12" ]
steps:
# call checkout
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
# install extra python modules
- name: 💿Install extra Python Modules
shell: bash
run: |
python -m pip install setuptools
# call prepare release
- name: 📀->📦Prepare Release
uses: ./.github/actions/release-prepare
with:
os-name: ${{ matrix.os-name }}
python-version: ${{ matrix.python-version }}
# Deploy Release
# Needs to be top-level for SECRET to work easily
release-deploy:
name: 📀->🚀
runs-on: ${{ matrix.os-name }}
needs: [release-prepare]
strategy:
matrix:
#TODO: OS List to run on
os-name: [
# ubuntu-latest, # ubuntu-22.04
ubuntu-22.04,
]
#TODO: Python Version to run on
python-version: [ "3.12" ]
steps:
# checkout commit
- name: ✔️Checkout commit
uses: actions/checkout@v4.1.4
# install extra python modules
- name: 💿Install extra Python Modules
shell: bash
run: |
python -m pip install pytz requests
# get parent dir
- name: 📁Get Parent Directory
id: parentDir
uses: ./.github/actions/get-parent-dir
# download appversion artifact
- name: 🔽Download AppVersion Artifact
uses: actions/download-artifact@v4.1.7
with:
name: appversion
path: ${{ steps.parentDir.outputs.parentDir }}/build
# download ubuntu archive artifact
- name: 🔽Download Ubuntu Archive Artifact
uses: actions/download-artifact@v4.1.7
with:
# should run on latest explicit ubuntu version
name: archive-ubuntu-22.04-py${{ matrix.python-version }}
path: ${{ steps.parentDir.outputs.parentDir }}/deploy/linux
# download macos archive artifact
- name: 🔽Download MacOS Archive Artifact
uses: actions/download-artifact@v4.1.7
with:
name: archive-macos-latest-py${{ matrix.python-version }}
path: ${{ steps.parentDir.outputs.parentDir }}/deploy/macos
# download windows archive artifact
- name: 🔽Download Windows Archive Artifact
uses: actions/download-artifact@v4.1.7
with:
name: archive-windows-latest-py${{ matrix.python-version }}
path: ${{ steps.parentDir.outputs.parentDir }}/deploy/windows
# determine linux archive asset
- name: ❔Identify Linux Archive Asset
id: identify-linux-asset
shell: bash
run: |
ASSET_LINUX="$(ls ${{ steps.parentDir.outputs.parentDir }}/deploy/linux)"
echo "asset_linux=$ASSET_LINUX" >> $GITHUB_OUTPUT
# determine macos archive asset
- name: ❔Identify MacOS Archive Asset
id: identify-macos-asset
shell: bash
run: |
ASSET_MACOS="$(ls ${{ steps.parentDir.outputs.parentDir }}/deploy/macos)"
echo "asset_macos=$ASSET_MACOS" >> $GITHUB_OUTPUT
# determine windows archive asset
- name: ❔Identify Windows Archive Asset
id: identify-windows-asset
shell: bash
run: |
ASSET_WIN="$(ls ${{ steps.parentDir.outputs.parentDir }}/deploy/windows)"
echo "asset_windows=$ASSET_WIN" >> $GITHUB_OUTPUT
# archive listing
# - name: Archive Listing
# shell: bash
# run: |
# ls -R ${{ steps.parentDir.outputs.parentDir }}/deploy/
# debug info
#TODO: Project Name
- name: 📝Debug Info
id: debug_info
run: |
PROJECT_NAME="ALttPOverworldRandomizer"
echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT
GITHUB_TAG="$(head -n 1 ../build/app_version.txt)"
echo "github_tag=$GITHUB_TAG" >> $GITHUB_OUTPUT
RELEASE_NAME="${PROJECT_NAME} ${GITHUB_TAG}"
echo "release_name=$RELEASE_NAME" >> $GITHUB_OUTPUT
ASSET_PREFIX="${PROJECT_NAME}-${GITHUB_TAG}"
echo "asset_prefix=$ASSET_PREFIX" >> $GITHUB_OUTPUT
echo "Project Name: ${PROJECT_NAME}"
echo "Release Name: ${RELEASE_NAME}"
echo "Asset Prefix: ${ASSET_PREFIX}"
echo "Git Tag: ${GITHUB_TAG}"
echo "Linux Asset: ${{ steps.identify-linux-asset.outputs.asset_linux }}"
echo "MacOS Asset: ${{ steps.identify-macos-asset.outputs.asset_macos }}"
echo "Windows Asset: ${{ steps.identify-windows-asset.outputs.asset_windows }}"
# create a release (OverworldShuffle)
# if: contains(github.ref, 'OverworldShuffle') # branch or tag name
# if: contains(github.event.head_commit.message, 'Version bump') # commit message
- name: 📀->🚀Create a Release (OverworldShuffle)
id: create_release
uses: actions/create-release@v1.1.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.debug_info.outputs.github_tag }}
release_name: ${{ steps.debug_info.outputs.release_name }}
body_path: CHANGELOG.md
# draft: true
if: contains(github.event.head_commit.message, 'Merge branch') # branch/tag name and commit message
# upload linux archive asset (OverworldShuffle)
#TODO: Make sure we're firing on the proper branches
- name: 🔼Upload Linux Archive Asset (OverworldShuffle)
id: upload-linux-asset
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/linux/${{ steps.identify-linux-asset.outputs.asset_linux }}
asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-linux-focal.tar.gz
asset_content_type: application/gzip
if: contains(github.event.head_commit.message, 'Merge branch') # branch/tag name and commit message
# upload macos archive asset (OverworldShuffle)
#TODO: Make sure we're firing on the proper branches
- name: 🔼Upload MacOS Archive Asset (OverworldShuffle)
id: upload-macos-asset
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/macos/${{ steps.identify-macos-asset.outputs.asset_macos }}
asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-osx.tar.gz
asset_content_type: application/gzip
if: contains(github.event.head_commit.message, 'Merge branch') # branch/tag name and commit message
# upload windows archive asset (OverworldShuffle)
#TODO: Make sure we're firing on the proper branches
- name: 🔼Upload Windows Archive Asset (OverworldShuffle)
id: upload-windows-asset
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ steps.parentDir.outputs.parentDir }}/deploy/windows/${{ steps.identify-windows-asset.outputs.asset_windows }}
asset_name: ${{ steps.debug_info.outputs.asset_prefix }}-windows.zip
asset_content_type: application/zip
if: contains(github.event.head_commit.message, 'Merge branch') # branch/tag name and commit message
# - name: 🖳Tag Baserom
# uses: ./.github/actions/tag-repo
# env:
# FINE_PAT: ${{ secrets.ALTTPER_TAGGER }}
# with:
# repository: ${{ github.repository_owner }}/z3randomizer
# ref-name: heads/OWMain
# github-tag: ${{ github.event.release.tag_name }}
# if: contains(github.event.head_commit.message, 'Merge branch')
+1 -36
View File
@@ -1,20 +1,12 @@
.idea
.vscode
*_Spoiler.txt
*.bmbp
*.log
*_Spoiler.json
*_Meta.json
*_custom.yaml
*_meta.txt
*.bps
*.pyc
*.sfc
*.srm
*.bst
*.wixobj
*.bat
/build
build
bundle/components.wxs
dist
README.html
@@ -23,30 +15,3 @@ README.html
*multisave
EnemizerCLI/
.mypy_cache/
RaceRom.py
upx/
weights/
/MultiMystery/
/Players/
/QUsb2Snes/
/output/
/enemizer/
visualizations/
resources/user/*
!resources/user/.gitkeep
*.exe
get-pip.py
venv
/test
test_games/
docs/issues/*
docs/ideas/*
data/sprites/official/selan.1.zspr
*.zspr
*errors.txt
*success.txt
-1
View File
@@ -1 +0,0 @@
3.13
+6 -18
View File
@@ -1,13 +1,11 @@
#!/usr/bin/env python3
import argparse
import logging
import os
import sys
import logging
import textwrap
import sys
from AdjusterMain import adjust
from Rom import get_sprite_from_name
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
@@ -17,8 +15,7 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def main():
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--rom', default='ER_base.sfc', help='Path to an ALttPR rom to adjust.')
parser.add_argument('--baserom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
parser.add_argument('--rom', default='ER_base.sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
parser.add_argument('--fastmenu', default='normal', const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
help='''\
@@ -34,13 +31,6 @@ def main():
''')
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
help='Select the color of Link\'s heart meter. (default: %(default)s)')
parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout'])
parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout'])
parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true')
parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true')
parser.add_argument('--shuffle_sfxinstruments', help='Shuffles sound instruments', action='store_true')
parser.add_argument('--shuffle_songinstruments', help='Shuffles song instruments', action='store_true')
parser.add_argument('--msu_resume', help='Enable MSU resume', action='store_true')
parser.add_argument('--sprite', help='''\
Path to a sprite sheet to use for Link. Needs to be in
binary format and have a length of 0x7000 (28672) bytes,
@@ -48,16 +38,14 @@ def main():
Alternatively, can be a ALttP Rom patched with a Link
sprite that will be extracted.
''')
parser.add_argument('--triforce_gfx', help='Name of the triforce graphics to use.')
parser.add_argument('--names', default='', type=str)
args = parser.parse_args()
# ToDo: Validate files further than mere existance
if not os.path.isfile(args.rom):
input('Could not find valid rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
sys.exit(1)
if args.sprite is not None and not os.path.isfile(args.sprite) and not get_sprite_from_name(args.sprite):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
if args.sprite is not None and not os.path.isfile(args.sprite):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
sys.exit(1)
# set up logger
+13 -60
View File
@@ -1,83 +1,36 @@
import logging
import os
import time
import logging
try:
import bps.apply
import bps.io
except ImportError:
raise Exception('Could not load BPS module')
from Rom import LocalRom, apply_rom_settings
from source.tools.BPS import bps_read_vlv
from Utils import output_path
from Rom import LocalRom, Sprite, apply_rom_settings
def adjust(args):
start = time.process_time()
start = time.perf_counter()
logger = logging.getLogger('')
logger.info('Patching ROM.')
if args.sprite is not None:
if isinstance(args.sprite, Sprite):
sprite = args.sprite
else:
sprite = Sprite(args.sprite)
else:
sprite = None
outfilebase = os.path.basename(args.rom)[:-4] + '_adjusted'
if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc':
rom = LocalRom(args.rom, False)
if os.path.isfile(args.baserom):
baserom = LocalRom(args.baserom, True)
rom.orig_buffer = baserom.orig_buffer
else:
raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
if not hasattr(args,"sprite"):
args.sprite = None
if not hasattr(args,"triforce_gfx"):
args.triforce_gfx = None
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, sprite)
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
args.sprite, args.triforce_gfx, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx,
args.shuffle_sfxinstruments, args.shuffle_songinstruments, args.msu_resume)
output_path.cached_path = args.outputpath
rom.write_to_file(output_path('%s.sfc' % outfilebase))
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s', time.process_time() - start)
return args
def patch(args):
start = time.process_time()
logger = logging.getLogger('')
logger.info('Patching ROM.')
outfile_base = os.path.basename(args.patch)[:-4]
rom = LocalRom(args.baserom, False)
if os.path.isfile(args.baserom):
rom.verify_base_rom()
orig_buffer = rom.buffer.copy()
with open(args.patch, 'rb') as stream:
stream.seek(4) # skip BPS1
bps_read_vlv(stream) # skip source size
target_length = bps_read_vlv(stream)
rom.buffer.extend(bytearray([0x00] * (target_length - len(rom.buffer))))
stream.seek(0)
bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, rom.buffer)
if not hasattr(args, "sprite"):
args.sprite = None
if not hasattr(args, "triforce_gfx"):
args.triforce_gfx = None
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
args.sprite, args.triforce_gfx, args.ow_palettes, args.uw_palettes, args.reduce_flashing,
args.shuffle_sfx, args.shuffle_sfxinstruments, args.shuffle_songinstruments, args.msu_resume)
output_path.cached_path = args.outputpath
rom.write_to_file(output_path('%s.sfc' % outfile_base))
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s', time.process_time() - start)
logger.debug('Total Time: %s', time.perf_counter() - start)
return args
+356 -3226
View File
File diff suppressed because it is too large Load Diff
+84 -187
View File
@@ -1,16 +1,14 @@
import logging
import RaceRandom as random
from BaseClasses import Boss, FillError
from source.enemizer.Bossmizer import boss_adjust
from BaseClasses import Boss
from Fill import FillError
def BossFactory(boss, player, on_ice=False):
def BossFactory(boss, player):
if boss is None:
return None
if boss in boss_table:
enemizer_name, normal_defeat_rule, ice_defeat_rule = boss_table[boss]
defeat_rule = ice_defeat_rule if on_ice else normal_defeat_rule
enemizer_name, defeat_rule = boss_table[boss]
return Boss(boss, enemizer_name, defeat_rule, player)
logging.getLogger('').error('Unknown Boss: %s', boss)
@@ -34,31 +32,27 @@ def LanmolasDefeatRule(state, player):
state.has('Fire Rod', player) or
state.has('Ice Rod', player) or
state.has('Cane of Somaria', player) or
(state.has('Cane of Byrna', player) and state.can_use_bombs(player)) or
state.has('Cane of Byrna', player) or
state.can_shoot_arrows(player))
def MoldormDefeatRule(state, player):
return state.has_blunt_weapon(player)
def HelmasaurKingDefeatRule(state, player):
return (state.has('Hammer', player) or state.can_use_bombs(player)) and (state.has_sword(player) or state.can_shoot_arrows(player))
def IceHelmasaurKingDefeatRule(state, player):
return state.can_use_bombs(player) and (state.has_sword(player) or state.can_shoot_arrows(player))
# TODO: technically possible with the hammer
return state.has_sword(player) or state.can_shoot_arrows(player)
def ArrghusDefeatRule(state, player):
if not state.has('Hookshot', player):
return False
# TODO: ideally we would have a check for bow and silvers, which combined with the
# hookshot is enough. This is not coded yet because the silvers that only work in pyramid feature
# makes this complicated
if state.has_blunt_weapon(player):
return True
if state.can_shoot_arrows(player) and state.has('Silver Arrows', player) and state.world.difficulty_adjustments[player] not in ['hard', 'expert']:
return True
return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or # assuming mostly getting two puffs with one shot
(state.has('Ice Rod', player) and state.can_use_bombs(player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16))))
return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot
(state.has('Ice Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16))))
def MothulaDefeatRule(state, player):
@@ -68,30 +62,13 @@ def MothulaDefeatRule(state, player):
# TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply
# to non-vanilla locations, so are harder to test, so sticking with what VT has for now:
(state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or
(state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16))
(state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or
state.can_get_good_bee(player)
)
def BlindDefeatRule(state, player):
return state.has_blunt_weapon(player) or state.has('Cane of Somaria', player) or state.has('Cane of Byrna', player)
def IceBlindDefeatRule(state, player):
return (
(
# weapon
state.has_beam_sword(player) or
state.has('Cane of Somaria', player) or
(state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16))
) and
(
# protection
state.has('Red Shield', player) or
(state.has('Cane of Byrna', player) and state.world.difficulty_adjustments[player] not in ['hard', 'expert'])
)
)
def KholdstareDefeatRule(state, player):
return (
(
@@ -99,7 +76,7 @@ def KholdstareDefeatRule(state, player):
(
state.has('Bombos', player) and
# FIXME: the following only actually works for the vanilla location for swordless
(state.has_sword(player) or state.world.swords[player] == 'swordless')
(state.has_sword(player) or state.world.swords == 'swordless')
)
) and
(
@@ -109,101 +86,52 @@ def KholdstareDefeatRule(state, player):
(
state.has('Fire Rod', player) and
state.has('Bombos', player) and
state.world.swords[player] == 'swordless' and
state.world.swords == 'swordless' and
state.can_extend_magic(player, 16)
)
)
)
def IceKholdstareDefeatRule(state, player):
return (
(
state.has('Fire Rod', player) or
(
state.has('Bombos', player) and
# FIXME: the following only actually works for the vanilla location for swordless
(state.has_sword(player) or state.world.swords[player] == 'swordless')
)
) and
(
state.has_beam_sword(player) or
(state.has('Fire Rod', player) and state.can_extend_magic(player, 20)) or
# FIXME: this actually only works for the vanilla location for swordless
(
state.has('Fire Rod', player) and
state.has('Bombos', player) and
(state.has_sword(player) or state.world.swords[player] == 'swordless') and
state.can_extend_magic(player, 16)
)
)
)
def VitreousDefeatRule(state, player):
return (state.can_shoot_arrows(player) and state.can_use_bombs(player)) or state.has_blunt_weapon(player)
def IceVitreousDefeatRule(state, player):
return (state.can_shoot_arrows(player) and state.can_use_bombs(player)) or state.has_beam_sword(player)
return state.can_shoot_arrows(player) or state.has_blunt_weapon(player)
def TrinexxDefeatRule(state, player):
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)):
return False
return (state.has('Hammer', player) or
state.has('Golden Sword', player) or
state.has('Tempered Sword', player) or
(state.has('Master Sword', player) and state.can_extend_magic(player, 16)) or
(state.has_sword(player) and state.can_extend_magic(player, 32)))
def IceTrinexxDefeatRule(state, player):
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player) and state.has_Boots(player)):
return False
return (state.has('Golden Sword', player) or
(state.has('Tempered Sword', player) and state.can_extend_magic(player, 16)) or
((state.has('Hammer', player) or
state.has('Master Sword', player)) and state.can_extend_magic(player, 32))) # rod spam rule
return state.has('Hammer', player) or state.has('Tempered Sword', player) or state.has('Golden Sword', player) or \
(state.has('Master Sword', player) and state.can_extend_magic(player, 16)) or \
(state.has_sword(player) and state.can_extend_magic(player, 32))
def AgahnimDefeatRule(state, player):
return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player)
boss_table = {
'Armos Knights': ('Armos', ArmosKnightsDefeatRule, ArmosKnightsDefeatRule),
'Lanmolas': ('Lanmola', LanmolasDefeatRule, LanmolasDefeatRule),
'Moldorm': ('Moldorm', MoldormDefeatRule, MoldormDefeatRule),
'Helmasaur King': ('Helmasaur', HelmasaurKingDefeatRule, IceHelmasaurKingDefeatRule),
'Arrghus': ('Arrghus', ArrghusDefeatRule, ArrghusDefeatRule),
'Mothula': ('Mothula', MothulaDefeatRule, MothulaDefeatRule),
'Blind': ('Blind', BlindDefeatRule, IceBlindDefeatRule),
'Kholdstare': ('Kholdstare', KholdstareDefeatRule, IceKholdstareDefeatRule),
'Vitreous': ('Vitreous', VitreousDefeatRule, IceVitreousDefeatRule),
'Trinexx': ('Trinexx', TrinexxDefeatRule, IceTrinexxDefeatRule),
'Agahnim': ('Agahnim', AgahnimDefeatRule, AgahnimDefeatRule),
'Agahnim2': ('Agahnim2', AgahnimDefeatRule, AgahnimDefeatRule)
'Armos Knights': ('Armos', ArmosKnightsDefeatRule),
'Lanmolas': ('Lanmola', LanmolasDefeatRule),
'Moldorm': ('Moldorm', MoldormDefeatRule),
'Helmasaur King': ('Helmasaur', HelmasaurKingDefeatRule),
'Arrghus': ('Arrghus', ArrghusDefeatRule),
'Mothula': ('Mothula', MothulaDefeatRule),
'Blind': ('Blind', BlindDefeatRule),
'Kholdstare': ('Kholdstare', KholdstareDefeatRule),
'Vitreous': ('Vitreous', VitreousDefeatRule),
'Trinexx': ('Trinexx', TrinexxDefeatRule),
'Agahnim': ('Agahnim', AgahnimDefeatRule),
'Agahnim2': ('Agahnim2', AgahnimDefeatRule)
}
def can_place_boss(world, player, boss, dungeon_name, level=None):
if world.swords[player] in ['swordless'] and boss == 'Kholdstare' and dungeon_name != 'Ice Palace':
def can_place_boss(world, boss, dungeon_name, level=None):
if world.swords in ['swordless'] and boss == 'Kholdstare' and dungeon_name != 'Ice Palace':
return False
if dungeon_name == 'Ganons Tower' and level == 'top':
if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'top':
if boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]:
return False
if dungeon_name == 'Ganons Tower' and level == 'middle':
if dungeon_name in ['Ganons Tower', 'Inverted Ganons Tower'] and level == 'middle':
if boss in ["Blind"]:
return False
# no Trinexx on Ice in doors without doing some health modelling
if world.doorShuffle[player] != 'vanilla' and boss == 'Trinexx':
if dungeon_name == 'Ganons Tower' and level == 'bottom':
return False
if dungeon_name == 'Tower of Hera' and boss in ["Armos Knights", "Arrghus", "Blind", "Trinexx", "Lanmolas"]:
return False
@@ -215,108 +143,77 @@ def can_place_boss(world, player, boss, dungeon_name, level=None):
return True
def place_bosses(world, player):
if world.boss_shuffle[player] == 'none':
if world.boss_shuffle == 'none':
return
# Most to least restrictive order
boss_locations = [
['Ganons Tower', 'top'],
['Tower of Hera', None],
['Skull Woods', None],
['Ganons Tower', 'middle'],
['Ganons Tower', 'bottom'],
['Eastern Palace', None],
['Desert Palace', None],
['Palace of Darkness', None],
['Swamp Palace', None],
['Thieves Town', None],
['Ice Palace', None],
['Misery Mire', None],
['Turtle Rock', None],
]
if world.mode != 'inverted':
boss_locations = [
['Ganons Tower', 'top'],
['Tower of Hera', None],
['Skull Woods', None],
['Ganons Tower', 'middle'],
['Eastern Palace', None],
['Desert Palace', None],
['Palace of Darkness', None],
['Swamp Palace', None],
['Thieves Town', None],
['Ice Palace', None],
['Misery Mire', None],
['Turtle Rock', None],
['Ganons Tower', 'bottom'],
]
else:
boss_locations = [
['Inverted Ganons Tower', 'top'],
['Tower of Hera', None],
['Skull Woods', None],
['Inverted Ganons Tower', 'middle'],
['Eastern Palace', None],
['Desert Palace', None],
['Palace of Darkness', None],
['Swamp Palace', None],
['Thieves Town', None],
['Ice Palace', None],
['Misery Mire', None],
['Turtle Rock', None],
['Inverted Ganons Tower', 'bottom'],
]
all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
used_bosses = []
if world.customizer and world.customizer.get_bosses():
custom_bosses = world.customizer.get_bosses()
if player in custom_bosses:
for location, boss in custom_bosses[player].items():
level = None
if '(' in location:
i = location.find('(')
level = location[i+1:location.find(')')]
location = location[:i-1]
if can_place_boss(world, player, boss, location, level):
loc_text = location + (' ('+level+')' if level else '')
place_boss(boss, level, location, loc_text, world, player)
boss_locations.remove([location, level])
used_bosses.append((boss, level))
# temporary hack for swordless kholdstare:
if world.boss_shuffle[player] in ["simple", "full", "unique"]:
if world.swords[player] == 'swordless':
if world.boss_shuffle in ["basic", "normal"]:
# temporary hack for swordless kholdstare:
if world.swords == 'swordless':
world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player)
logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace')
boss_locations.remove(['Ice Palace', None])
placeable_bosses.remove('Kholdstare')
if world.boss_shuffle[player] in ["simple", "full"]:
if world.boss_shuffle[player] == "simple": # vanilla bosses shuffled
if world.boss_shuffle == "basic": # vanilla bosses shuffled
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
else: # all bosses present, the three duplicates chosen at random
bosses = placeable_bosses + random.sample(placeable_bosses, 3)
for u, level in used_bosses:
placeable_bosses.remove(u)
else: # all bosses present, the three duplicates chosen at random
bosses = all_bosses + [random.choice(placeable_bosses) for _ in range(3)]
logging.getLogger('').debug('Bosses chosen %s', bosses)
random.shuffle(bosses)
for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '')
try:
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
except IndexError:
boss = next((b for b in bosses if can_place_boss(world, b, loc, level)), None)
if not boss:
raise FillError('Could not place boss for location %s' % loc_text)
bosses.remove(boss)
place_boss(boss, level, loc, loc_text, world, player)
elif world.boss_shuffle[player] == "random": #all bosses chosen at random
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
elif world.boss_shuffle == "chaos": #all bosses chosen at random
for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '')
try:
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)])
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, b, loc, level)])
except IndexError:
raise FillError('Could not place boss for location %s' % loc_text)
place_boss(boss, level, loc, loc_text, world, player)
elif world.boss_shuffle[player] == 'unique':
bosses = list(placeable_bosses)
for u, level in used_bosses:
if not level:
bosses.remove(u)
gt_bosses = []
for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '')
try:
if level:
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)
and b not in gt_bosses])
gt_bosses.append(boss)
else:
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
bosses.remove(boss)
except IndexError:
raise FillError('Could not place boss for location %s' % loc_text)
place_boss(boss, level, loc, loc_text, world, player)
boss_adjust(world, player)
def place_boss(boss, level, loc, loc_text, world, player):
# GT Bosses can move dungeon - find the real dungeon to place them in
if level:
loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
loc_text = loc + ' (' + level + ')'
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player, level == 'bottom')
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
-813
View File
@@ -1,813 +0,0 @@
# Changelog
# 0.7.0.2
- Fixed money error for bombbag/take-anys
- Fixed palette issue for map check/flute menu
- Fixed issue with dungeon icons showing in opposite world in Inverted
- Fixed Links position in map checks when in Special OW areas
- Fixed buffered sword issue in OW Shuffle water transitions
- Added PlasmaKappa tribute TF room text
# 0.7.0.1
- Fixed buggy sprites in post-Aga Zora's Domain
- Fixed L/R map switch when in special OW screens
- Fixes issue not able to screen transition if bumped by enemy in water
# 0.7.0.0
- New OW Layout Shuffle Mode: Grid
- Implemented Fog of War for Tile Flip
# 0.6.1.11
- Fixed bonk drops duplicate counting and potentially overwriting arbitrary values
- Fixed boss icons on dungeon map check
- Fixed dungeon counters to autotrack correctly
- Key and chest counts in menu now display consistently (must have dungeon item or have visited dungeon to see the HUD)
- Money balancing will fail in less scenarios
- Fixed issue with Sanc pots not collecting
- Enemizer now allows more enemies on water
- Fix infinite pit fall issue with Old Man follower location
- Fix bad overworld tilemap drawing on HC and Pyramid screens in OW Layout Shuffle
## 0.6.1.10
- Emergency fix for bonk functionality
## 0.6.1.9
- Fixed follower shuffle placement errors
- Fixed pseudoboots ability to open Kings Tomb
- Implemented better accurate coordinates on map check locations
- Fixed janky icons on zoomed-in map check screen
## 0.6.1.8
- Fixed follower placement and logic
- Fixed error with HC Courtyard Tree Pull
## 0.6.1.7
- \~Merged in DR v1.5.2~
- Reverted key count update
## 0.6.1.6
- Fixed bonk drop sparkles in DW
- \~Merged in DR v1.5.1~
- Fixed key count issues
## 0.6.1.5
- Fixed error with non-prize shuffle
## 0.6.1.4
- Fixes for Glitched Rain State (Bonk Shuffle included)
- \~Merged in DR v1.5.0~
- GT Key Logic fix
- New HUD key count behavior
- Pot uncoloring on collection
## 0.6.1.3
- Added new post-gen option to change Triforce Piece GFX
- Added new GFX for 10/11 keys to replace the A/B GFX
- Fixed issue with Follower Sprite GFX after mirroring
- Fixed VRAM issue with Crystal Maiden cutscene
- Some performance updates
- \~Merged in DR v1.4.11~
- Enemizer bans update
## 0.6.1.2
- Various fixes for Custom Goal Framework
- Added custom gfx for Pedestal and Murahdahla
- Re-fixed purple chest follower dupe
- Updated tournament winners texts
## 0.6.1.1
- Fixed issue with Bosses goals in Custom Goal Framework
- Fixed error when using Custom Goals with no extra values
- Added more Custom Goal detail in Spoiler Log
## 0.6.1.0
- New Custom Goal framework (see Customizer.md)
- New 'logic' hint terminology to replace vital/useful terminology
- Removed possibility of duplicated hints
- New fix for hammering pot drops when at sprite limit
- Fixed issue with incorrect Ganon silvers hint
- Fixed issue with Locksmith despawning after purple chest if a follower is stored
- Fixed pogdor glitch (frogdor but at PoD entrance and Kiki following)
- Fixed issue with Duck gfx overwriting GT Cutscene gfx
- New CLI argument to allow external generators to supply a custom ROM Header
- Fixed error with extra argument in MultiServer call
- Added new text if Link's House is placed at any Snitch Lady house
## 0.6.0.8
- Re-fixed issue with Old Man spawning on pyramid
- Allowing Zelda to be in TT Prison for follower shuffle escape
- Fixed error with placing Old Man Cave in ER
- Fixed error when plando'ing followers at locations
## 0.6.0.7
- Emergency fix for GT cutscene GFX
## 0.6.0.6
- Added ability to change GT entry cutscene GFX thru customizer
- Chicken as a standing item is no longer a star gfx
- Fix issue in DR with music silence in pre-Aga room
- Other various music fixes
- Fixed issue with Mystery subweights not correctly evaluating boolean settings
## 0.6.0.5
- Emergency fix for map/key totals in non-DR
## 0.6.0.4
- Fixed pause menu to show compass count and prizes to also show if you've seen them
- Fixed crash when Old Man follower exits with you at his destination entrance
- Old Man fetch cave is no longer restricted from exiting at his destination cave in ER
- Kodongo AI is returned to its vanilla behavior in their vanilla rooms
## 0.6.0.3
- Arrghus' Room has water floor restored in boss shuffle
- Fixed no music in Links House when S+Q from Kakariko
- Fixed issue with Boots not showing in pause menu after collection
- Fixed issue with Kiki running away outside the PoD maze
- Fixed issue with Kiki running away when using Quake
- Fixed issue with lingering purple chest after dropping and mirroring
- Getting Locksmith to follow behaves closer to vanilla
- Fixed issue with Locksmith follower despawning after purple chest item get
- Possible fix for prizes getting placed in major locations
- \~Merged in DR v1.4.10~
- Helmacopter fix
## 0.6.0.2
- Fixed issue with item pool placing items on Zelda Drop Off
- Fix issue with infinite purple chest item get
- Fix issue with Kiki running away on i-frame ledge hops
- Fix issue with bad follower gfx on screens with followers
- Fix issue with Zelda appearing in conditional follower locations
## 0.6.0.1
- Emergency fix for generation errors
## 0.6.0.0
- New Follower Shuffle option (See Readme)
- HMG fixes for key pickup behavior
- Fixed some errors with starting equipment
- Added documentation for some unknown/obscure starting inventory options
- Possible fix for ignorable error message for EXE users
- \~Merged in DR v1.4.9~
- Fixed Moth conveyor issue
## 0.5.1.5
- Fixed rare overworld map check VRAM crash
- Fixed cavestate dark room hidden item issue
- Removed dungeon items from hint pool for nearby dungeon items
- Fixed generation error with shopsanity
- Fixed generation error in ER: empty cave lists being added as candidates
## 0.5.1.4
- Fixed incorrect ganon silvers hint
- \~Merged in DR v1.4.8.1~
## 0.5.1.3
- Fixed some minor issues with mystery multiworld
- Corrected some generation issues with Nearby dungeon item shuffles
## 0.5.1.2
- Many fixes to HMG logic, generation, key collection issues
- Fixed issue with In-Dungeon Prizes getting placed in the same dungeon
- Fixed issue with Old Man getting placed outside of DM in glitched modes
- Fixed issue with Free Terrain water transitions not checking for Pearl requirement
## 0.5.1.0 (+ Hotfix 0.5.1.1)
- New "Nearby" Dungeon Item Shuffle option
- Fixed issue with smith follower getting deleted incorrectly on s+q
- Various generation and logic fixes for HMG/NL
- \~Merged in DR v1.4.6.2~
## 0.5.0.6
- New improved Pseudoboots behavior
- Fixed issue with missing Blue Potion in Dark Lake Shop in Inverted
- Various generation and logic fixes for HMG/NL
- Fixed error with prize plando
- Fixed error with District ER
- Added TT Maiden pathing to spoiler log
- Corrected and improved some pathing logic
## 0.5.0.5
- Fixed issue with vanilla HCBK acting like a small key
## 0.5.0.4
- Fixed dark room item gfx
- Fixed dungeon item pickup behavior in cross-dungeon glitched scenarios
- \~Merged in DR v1.4.6~
- Fixed Sanc & Quit behavior
## 0.5.0.3
- Fixed crash on pedestal
## 0.5.0.2
- \~Merged in DR v1.4.5~
- ER - SW Shuffle Options
- ER - Linked Drops Options
- Many GFX fixes
- Fixes to MSU/music
- No longer delete smith with prize shuffle
- Fixed screen scroll issue in Zora's Domain
- Fixed HUD when drawing A items on DR menu
## 0.5.0.1
- Fixed HP refill interrupting boss cutscenes
- Fixed incorrect compass counts in Prize Shuffle
- Various QoL to Prize Shuffle for less confusion on map checks
- Color-blind support for red crystals (item receipt and map check)
- Added X icon on flute map to indicate you can cancel out of flute menu (Flute Shuffle only)
## 0.5.0.0
- Overworld Map Check has been overhauled
- Dungeon Prize Indicators have changed: P/C no longer display, replaced by G/B/R/1-7
- New Mode: Prize Shuffle
- Death Mountain Return Cave and Paradox Cave (Top) are no longer bunny passable
- \~Merged in DR v1.4.1.12~
## 0.4.0.2
- Fixed issue with Inverted GT entrance not opening
- Potential fox for bonk items duplicating onto other OW items
## 0.4.0.1
- \~Merged in DR v1.4.1.10~
- Fixed issue with bonk items for MW players
- Fixed fake world issues in Glitched modes
- Fixed issue with Flute getting canceled in OWR Layout modes
- Various GFX Fixes
## 0.4.0.0
- Fully merged big base ROM changes from upstream, including FastROM
- \~Merged up to date with DR v1.4.1.8~
- Various GFX re-implementation including new GFX for bee traps, bomb bags, etc.
## 0.3.4.2
- Added Shuffle SFX Instruments as post-gen option
- Fixed some issues with Swapped ER failing to place Old Man Cave
- Changed Inverted 2.0 spawn prompt to display Bomb Shop
- Fixed some minor issues with ER and Vanilla GT
## 0.3.4.1
- Implemented new District ER mode option
- Added alternate boss logic when in GT Ice Basement
- Updated Inverted 2.0 to start in Bomb Shop regardless of ER mode
- Fixed broken customizer features with OWR Tile Flip
- Allowing Insanity ER + Standard to decouple standard entrances
## 0.3.4.0
- \~Merged in DR v1.2.0.23~
- Improved bunny-walking algorithm
- Improved multiworld balancing
- \~Merged in some things from DR v1.4.0.0-v~
- Implemented Hybrid Major Glitches logic (thanks Muffins/Espeon)
- Added sparkles to Bonk Drop locations for better visibility
- Some tweaks/improvements to Shuffle Song Instruments
- Replaced Save Settings on Exit with Settings on Load
- Added new button in GUI to export a Yaml file based on current settings
- Allow starting Aga-Defeated and Old-Man-Rescued in inventory
## 0.3.3.2
- \~Merged in DR v1.2.0.22~
- Added Shuffle Song Instruments as post-gen option
- Allow user to change and save output directory within the GUI
- Fixed issue with Smith not deleting on S+Q when no path is possible
- Fixed various MSU inaccuracies
## 0.3.3.1
- \~Merged in DR v1.2.0.21~
- Fixed issue with Old Man death spawning on Pyramid/Castle
- Fixed issue with Mixed OWR + Flute Shuffle placing spots on large screens
- Fixed issue with mirror portals disappearing in Crossed OWR
- Added more OWR preset yamls + some fixes to the existing ones
## 0.3.3.0
- Added Customizer support for all remaining OWR options
- Added several OWR preset yamls (many ideas are thanks to Catobat)
- Removed Limited Crossed OWR and renamed Chaos Crossed to Unrestricted Crossed
## 0.3.2.2
- Added Customizer support for Flute Shuffle (thanks Catobat)
- Fixed bad Old Man rescue possibility in Swapped ER
- Fixed vanilla placement issue in Swapped ER
- Removed entrance hints in Swapped ER
- Fixed various generation/validation errors
## 0.3.2.1
- \~Merged in DR v1.2.0.20~
- Some minor Swapped ER improvements
- Fixed generation error with Flute Activation
## 0.3.2.0
- New Swapped ER mode option
- Fixed issue with flipper rules not properly getting pearl requirement added
- Fixed minor issues in generating non-crossed ER on subsequent attempts
## 0.3.1.2
- Retro now gives a universal key from bottle vendor fish prize
- Fixed issue with Aga Door preventing Murahduhla Cutscene
- Fixed issue with Ice Cave/Shopping Mall water transitions not flipping in Mixed OWR
- Minor improvements to item GFX draw routine
## 0.3.1.1
- \~Merged in DR v1.2.0.17~
- Various renames/reorganizations of region/rule definition to match upcoming DR world remodel
- Minor improvements to item GFX draw routine
- Minor change to terrain/logic of Spiral/Mimic Ledge in Inverted 2.0
## 0.3.1.0
- Added new Triforce Cutscene when getting Triforce item (turning in TF Pieces)
- Fixed issue with enemy key drops displaying the wrong GFX
- Fixed issue with boss music playing in Hera after boss is defeated
- Fixed issue that limited the Bonk count to cap at 99
- Made Murahdahla interactable on Pyramid in Rainstate
## 0.3.0.8
- All Bonk prize GFX are no longer using the Power Star placeholder GFX
- Fixed issue with dislaying Key GFX during tablet animations
- Fixed issue with DPad input being disabled in Pause Menu if an item GFX is on screen
## 0.3.0.7
- \~Merged in DR v1.2.0.16~
- Major overhaul of how item GFX are drawn on screen
- Bonk prize GFX can now all be displayed simultaneously
- Rupee items that are in-plain-sight are now animated
- Narrow items are now centered within their tile space
- Fixed issue with pottery items showing bad GFX after map check or medallion use
- Fixed issue with bonk drop items causing duplicate sprite spawns
## 0.3.0.6
- \~Merged in DR v1.2.0.15~
- Fixed Tower of Hera music silence issue
- Improved symmetrical GT crystal cutscene
- Changed bonk prizes to not mark as collected unless it is visible on screen
## 0.3.0.5
- Major reorganization to GUI Options
- Corrected various fake world behavior in glitched modes
- Fixed various issues with glitched mode logic
- Various minor logic fixes
## 0.3.0.4
- \~Merged in DR v1.2.0.14~
- Fixed issue with enemy drops on OW enemies
- Fixed issue with magically opened doors
- Inverted + Vanilla ER can now generate
- Fixed issue with Multiworld bonk items not sending to correct player
- Minor improvements to OW Palettes on screen transition
## 0.3.0.3
- Fixed issue with new Cold Fairy Statue location not dropping correct item
- Fixed issue with Multiworld due to new Cold Fairy Statue location
- Improved water collision to only target Heart Piece sprites (rando items)
## 0.3.0.2
- \~Merged in DR v1.2.0.12~
- Fixed some door landing issues
- Added Cold Fairy Statue as a new Bonk Location in Bonk Drop Shuffle
- Added Customizer support for enabling OWR Options
- Removed `Arrows (5)` item from Item Table, replaces with `Arrows (10)`
## 0.3.0.1
- \~Merged in DR v1.2.0.10~
- Fixed some door landing issues
- Added Customizer support for OWR Tile Flips
- Added 'Standard+Inverted' template customizer yaml
- Fixed some ER issues with placing Sanc Drop in non-crossed ER
## 0.3.0.0
- \~Merged in all DR Customizer features since its initial release up to v1.2.0.9~
- Major revamp of Aerinon's ER 2.0 to better support OWR modes
- Fixed various incorrect logic issues (Inverted flute spots, Bomb Shop start, etc)
- Flute is disabled in rain state except glitched modes
## 0.2.11.4
- Fixed broken OWG logic issues
- Gave chickens a base price for shopsanity
- Modeled TR entrance opening to use a logical pseudo-item
## 0.2.11.3
- Fixed error during multiworld generation
- Added proper Old Man pathing to the logic and spoiler playthru
- Removed the delay in spawning sprites when bonking items out of bonk locations
- Removed useless hints in Lite/Lean ER that are already categorically assumed
- Added GT and Links House entrance hints when either are shuffled in the pool
## 0.2.11.2
- Implemented proper Districting with Mixed OWR, affecting Hints and District modes
- Suppressed item locations in spoiler log that are junk or logical
## 0.2.11.1
- Renamed mode: Tile Swap (Mixed) is now called Tile Flip (Mixed)
- Fixed generation errors due to issue with new Farmable item locations
## 0.2.11.0
- New OWR mode option: Free Terrain
- When used with OW Layout Shuffle, land and water transitions are combined into one pool and shuffled, this means land transitions can lead to water and vice versa. There is already tracker support for this change on DunkaTracker. Thanks @Catobat for the work on all of this.
- Glitched modes now have correct fake world behavior in all modes, including Inverted and even Mixed OWR
- Glitched + Mixed OWR now has correct logic (previously it was completely unimplemented)
- Lite ER is back and working!
- There was an issue with ER resulting in regions being inaccessible, this has been fixed.
- Changed Crossworld ER modes so that DW inaccessible areas are resolved before considering LW inaccessible areas, to give the algo a chance to make some of the LW areas accessible thru the DW
- Added new Bomb/Rupee farm logic, which uses pseudo-items, simplifies the graph searching and they even show up in the Playthru Calc (shows a logical path to Farmable Bombs if you ever question how you are able to get early bombs when the opening area is limited)
- Fixed issue with grabbing an item near Murahduhla and freezing the game
- Various logic corrections, including the DR Bumper Cave fix for pottery logic
## 0.2.10.1
- \~Merged v1.0.1.3~
- Fixed Zelda despawn in TT Prison
- Fixed issue with key door usage in rainstate
- Added missing modes to example mystery yaml
## 0.2.10.0
- \~Merged v1.0.1.1-1.0.1.2~
- Removed text color from hint tiles
- Removed Good Bee requirement from Mothula
- Some keylogic/generation fixes
- Fixed a Pottery logic issue in the playthru
- Fixed a generation error in Mixed OWR, resulting in more possible Mixed scenarios (thanks Catobat)
- Added more scenarios where OW Map Checks in Mixed OWR show dungeon prizes in their respective worlds
- Fixed rupee logic to consider Pottery option and lack of early rupees
- Changed Lean ER + Inverted Dark Chapel start is guaranteed to be in DW
- Fixed graphical issue with Hammerpeg Cave
- Fixed logic rule with HC Main Gate to not require mirror if screen is swapped
- Removed Crossed OWR option: "None (Allowed)"
### 0.2.9.1
- Lite/Lean ER now includes Cave Pot locations with various Pottery options
- Changed Unique Boss Shuffle so that GT Bosses are unique amongst themselves
- Changed MSU-1 in Inverted to trigger DW2 track with Aga1 kill and LW2 with 7 crystals
- Fixed disappearing mirror portal issue in Inverted (Hopefully for good)
- Fixed issue with TR Peg Puzzle not spawning portal in some Mixed OWR scenarios
- Removed ability to roll Myserty with phantom Crossed OWR options
### 0.2.9.0
- Added Bonk Drop Shuffle
- Fixed disappearing mirror portal issue in Inverted+Crossed OWR
- Fixed 4-digit collection rate in credits
- Fixed Ganon vulnerability to reference Aga2 boss flag rather than pyramid hole
- Fixed issue with pre-opened pyramid when not expected
### 0.2.8.0
- \~Merged DR v1.0.1.0 - Pottery options, BPS support, MSU Resume, Collection Rate Counter~
- Various improvements to increase generation success rate and reduce generation time
- Fixed issue with playthru recognizing Aga accessibility
- Fixed issue with applying rules correctly to Murahdahla, fixing Murahdahla+Beatable issues
- Fixed issue with Flute+Rainstate, flute use is no longer in logic until Zelda is delivered
### 0.2.7.3
- Restructured OWR algorithm to include some additional scenarios not previously allowed
- Added new Inverted D-pad controls for Social Distortion (ie. Mirror Mode) support
- Crossed OWR/Special OW Areas are now included in the spoiler log
- Fixed default TF pieces with Trinity in Mystery
- Added bush crabs to rupee farm logic (only in non-enemizer)
- Updated tree pull logic to also require ability to kill most things
### 0.2.7.2
- Special OW Areas are now shuffled in Layout Shuffle (Zora/Hobo/Pedestal)
- Fixed some broken water region graph modelling, fixed some reachability logic
- Some minor code simplifications
### 0.2.7.1
- Map checks in Mixed OWR now will show the proper tile images when screens are swapped (ie. Pyramid shows in the LW if that screen is swapped)
- Added mystery seed number to spoiler log, so it is easier to match a spoiler log to a rom filename
- Added proper branch-specific versioning (ie. Dev branch has '-u' suffixing the version number while Release/Main branch does not)
### 0.2.7.0
- \~Merged DR v1.0.0.3 - MANY changes, major things listed below~
- New Item Fills (Districts/Vanilla/Major Location/Dungeon)
- New OW Map Prize Indicators (In ER, map checks can spoil dungeon locations with a user setting)
- Forbidden Boss Items (Exclude certain dungeon items from dropping on bosses)
- Map checks in Mixed OWR now will show dungeon prizes for dungeons actually in the world you map check on
- In Mixed OWR, Sanc screen must stay in LW if the starting location is guaranteed to exit at the Sanc entrance
- Fixed various issues with Flute logic
- Fixed issue that resulted in infinite loops in Flute Shuffle
- Changed map in attract mode to always show a vanilla LW map
- Various improvements to increase generation success rate
### 0.2.6.1
- Fixed issue with mirror bonking deleting portal in Crossed OW
- Fixed issue with mirror portal not spawning when entering the OW from the DW, in Crossed OW
- Updated some text with proper capitalization/spacing
- Updated tournament winners text
### 0.2.6.0
- New text engine font!
- Fixed invisible Witch item bug
- Added 'O' to ROM Header for autotrackers
- Fixed generation error with Shopsanity + OWR Layout
- Fixed OWR validation error with Insanity ER + OWR Layout
- Fixed issue with TR Peg Puzzle not spawning a valid portal
### 0.2.5.3
- Changed AT/GT Swap to favor vanilla, only swapping if GT entrance is the only choice in starting world
- Fixed issue with Links House not swapping in OW Mixed
- Added Flute Spots to spoiler log
- Fixed issue with Light Hype Fairy excluded from bombable door list
### 0.2.5.1
- Fixed missing rule for Inverted VoO Portal access
### 0.2.5.0
- Many updates to Inverted OW Terrain:
- Links House start now spawns in Big Bomb Shop
- Old Man Cave/Bumper Cave returned to vanilla
- Mountain Cave S+Q now spawns in his usual Cave
- Flute Spot 1 is moved to Top of Dark Death Mountain
- Ladder is removed from West Dark Death Mountain
- When finding Flute, it comes pre-activated (will hear SFX on collecting)
- Spiral and Mimic Cave are now bridged together
- TR Peg Puzzle is restored but instead reveals a ladder
- Houlihan now exits same place as Big Bomb Shop does (OWR branch always had this)
- Ice Palace has been re-sealed to vanilla, portal moved to outer edge of moat
- Glitched modes will now use vanilla terrain except where necessary
- Fixed errors with OW Layout Shuffle
- Fixed issue with incorrect Mirror bonking
- Fixed issue with old man follower death to Pyramid
- Fixed Hera boss music not playing when boss not defeated
- \~Merged DR v0.5.1.7 - TT boss trap door fix/Applied Glitched flag~
### 0.2.4.0
- Added Guaranteed OWR Reachability
- Fixed incorrect parity calc for Whirlpool Shuffle
- Fixed error with generating seeds with GUI
- CLI fixes for triforce piece arguments
### 0.2.3.6
- Added Trinity goal (8/10 default TF pieces)
- Many improvements to TFH pool allocation
- Fixed some issues with Multiworld generation with Custom Item Pools
### 0.2.3.5
- Fixed issue with multiworld generation
- Added infinite loop detection
- Move mirror portal off screen during mirror bonk in Crossed OW
### 0.2.3.4
- Fixed major issue with subsequent seeds using same seed/settings resulting different
- Flute Shuffle now awards separated regions a prorated number of flute spots based on size
- Fixed spoiler log, was missing OW Tile Swap map
- Fixed spoiler log JSON output
- Fake flipper damage fix improved to skip the long delay after the scroll
- Fixed missing Blue Potion in Lake Shop in Inverted
- Added legacy OW Crossed option 'None (Allowed)' to support old behavior when invalid option was used in Mystery
- \~Merged DR v0.5.1.6 - Money balancing fix/Boss logic fixes with Bombbag~
### 0.2.3.3
- Added OW Layout validation that reduces the cases where some screens are unreachable
- Fixed issue with mirror portals showing up in DW in Crossed OW
- Corrected Lost/Skull Woods Pass regions to be more accurate
### 0.2.3.0/1/2
- Fixed issue in Crossed OW where mirror portal sprites would disappear when changing worlds
- Added Big Red Bomb logic to support using residual mirror portals for later re-entry
- Suppressed irrelevant paths in spoiler playthru
- Suppressed identical paths in spoiler playthru if multiple locations within the same region contain progression
### 0.2.2.3
- Fixed GT entrance not being opened when Inverted and WDM is Tile Swapped
- The Goal sign is moved to the area where the hole will be (always in opposite world as starting)
### 0.2.2.2
- Fixed Whirlpool Shuffle with Grouped Crossed OW
- Made filename not spoil OWR in Mystery
- Fixed Triforce Hunt goal
### 0.2.2.1
- Allow normal Link speed with Old Man following if not in his cave or WDM
- Fixed issue with Flute exits not getting placed on the correct tiles
- Hints in Lite/Lean ER no longer refer to entrances that are guaranteed vanilla
- Added Links House entrance to hint candidate list in ER when it is shuffled
- Added Tile Swaps ASCII map to Spoiler Log when Tile Swap is enabled
- Fixed issue with Whirlpool Shuffle not abiding by Polar rules
### 0.2.2.0
- Delivering Big Red Bomb is now in logic
- Smith/Purple Chest have proper dynamic pathing to fix logical issues
- Fixed issue with bomb walls in OW not requiring moon pearl in DW
### 0.2.1.3
- New fake flipper handling to allow S+Q rather than insta-kill
- Fixed whirlpools in Crossed OW
- Spoiler fixes, incl. missing Starting Inventory in Spoiler
- Fixed music track change to Sanc music when Standard mode is delivering Zelda
- Fixed SP flooding issue
- Fixed issue with Shuffle Ganon in CLI/GUI
- \~Merged DR v0.5.1.5 - Mystery subweights~
### 0.2.1.2
- Fixed issue with whirlpools not changing world when in Crossed OW
### 0.2.1.1
- Many fixes to ER: infinite loops, preventing cross-world scenarios in non-cross-world modes
- Spoiler log improvements, outputs in stages so a Spoiler is available if an error occurs
- Added no_race option for Mystery
- Fixed output_path in Mystery to use the saved setting if none is specified on CLI
### 0.2.1.0
- Implemented Whirlpool Shuffle
### 0.2.0.0
- Massive overhaul of ER algorithm
- Added 2 new ER modes (Lite and Lean)
- Added new mystery options (Logic/Shuffle Ganon)
- Smith deletion on S+Q only occurs if Blacksmith not reachable from starting locations
- Spoiler log improvements to prevent spoiling in the beginning 'meta' section
- Various minor fixes and improvements
- \~Merged DR v0.5.1.4 - ROM bug fixes/keylogic improvements~
### 0.1.9.4
- Hotfix for bad 0.1.9.3 version
### 0.1.9.3
- Moved flute spot from Northwest Lake Hylia to Southeast Lake Hylia
- Fixed Links House start in Inverted ER
- Minor accuracy improvements to ER, mostly preparations for future work
### 0.1.9.2
- Fixed spoiler log and mystery for new Crossed/Mixed structure
- Minor preparations and tweaks to ER framework (added global Entrance/Exit pool)
- \~Merged DR v0.5.1.2 - Blind Prison shuffled outside TT/Keylogic Improvements~
### 0.1.9.1
- Fixed logic issue with leaving IP entrance not requiring flippers
- \~Merged DR v0.5.1.1 - Map Indicator Fix/Boss Shuffle Bias/Shop Hints~
### 0.1.9.0
- Expanded Crossed OW to four separate options, see Readme for details
- Crossed OW will now play a short SFX when changing worlds
- Improved Link/Bunny state in Crossed OW
- Fixed issue with TR Pegs when fluting directly from an area with hammerpegs
- Updated OW GUI layout
### 0.1.8.2
- Fixed issue with game crashing on using Flute
- Fixed issues with Link/Bunny state in Crossed OW
- Fixed issue with Standard+Parallel not using vanilla connections for Escape
- Fixed issue with Mystery for OW boolean options
- \~Merged DR v0.5.1.0 - Major Keylogic Update~
### 0.1.8.1
- Fixed issue with activating flute in DW (OW Mixed)
- Fixed issue with Parallel+Crossed not generating
- Fixed issue with Standard not generating
- Fixed issue with Swordless not generating
- Fixed logic for Graveyard Ledge and Kings Tomb
### 0.1.8.0
- Moved Crossed to its own checkbox option
- Removed Legacy ER shuffles
- Added OW Shuffle support for Plando module (needs user testing)
- Fixed issue with Sanc start at TR as bunny when it is LW
- Fixed issue with Pyramid Hole not getting shuffled
- \~Merged DR v0.5.0.3 - Minor DR fixes~
### 0.1.7.4
- Fixed issue with Mixed OW failing to generate when HC/Pyramid is swapped
- Various fixes to improve generation rates for Mixed OW Shuffle
- \~Merged DR v0.5.0.2 - Shuffle SFX~
### 0.1.7.3
- Fixed minor issue with ambient SFX stopping and starting on OW screen load
- MSU-1 changed to play LW2 (track 60) when Aga1 is killed instead of ped pull
- Added dynamic flute exits for all LW OW regions
- Improved spoiler log playthru pathing accuracy by including flute routing
- Fixed issue with generating a filename for vanilla OW settings
### 0.1.7.2
- Fixed music algorithm to play correct track in OW Shuffle
- Removed convenient portal on WDM in OW Layout Shuffle
- Fixed Mystery to not spoil OW Shuffle in filename
### 0.1.7.1
- Improved bomb logic to consider tree pulls, bush crabs, and stun prize
- Fixed Mystery to use new updated OW mode terminology
### 0.1.7.0
- Expanded new DR bomb logic to all modes (bomb usage in logic only if there is an unlimited supply of bombs available)
- \~Merged DR v0.5.0.1 - Bombbag mode / Enemizer fixes~
### 0.1.6.9
- \~Merged DR v0.4.0.12 - Secure random update / Credits fix~
### 0.1.6.8
- Implemented a smarter Balanced Flute Shuffle algorithm
- Fixed Collection Rate in credits
- Removed sortedcontainers dependency
### 0.1.6.7
- Mountain Pass and West Death Mountain are now Swapped independently (Old Man rescue is always in your starting world)
- Fixed issue with AT/GT access logic
- Improved spoiler log playthru accuracy
- Fixed Boss Music when boss room is entered thru straight stairs
- Suppressed in-dungeon music changes when DR is enabled
- Fixed issue with Pyramid Exit exiting to wrong location in ER
- \~Merged DR v0.4.0.11 - Various DR changes~
### 0.1.6.6
- \~Merged DR v0.4.0.9 - P/C Indicator / Credits fix / CLI Hints Fix~
### 0.1.6.5
- Reduced chance of diagonal flute spot in Balanced
- \~Merged DR v0.4.0.8 - Boss Indicator / Psuedo Boots / Quickswap Update / Credits Updates~
### 0.1.6.4
- Fixed Frogsmith and Stumpy and restored progression in these locations
- Added Blacksmith/Hammer Pegs to OW Tile Swap pool
### 0.1.6.3
- Fixed borked credits (and missing Sprite Author) when collection rate isn't 216 or if GTBK count isn't /22
- Added OW Rando in credits
- Actually merged in DR v0.4.0.7 (with no thanks to GitHub)
### 0.1.6.2
- Added Balanced option for Flute Shuffle
- Fixed issue with Flute Spot to Mountain Pass softlocking
- Fixed logic bug with Inverted Kakariko Portal
### 0.1.6.1
- Fixed issue with Flute Spot to VoO softlocking
- Fixed Houlihan to exit where Link's House does
- Fixed issue with jsonout not correctly showing rom mods for some OW Shuffle data
### 0.1.6.0
- Added Flute Shuffle setting
- Minor GUI changes
### 0.1.5.3
- Fixed issue with Aga portal mirror bonking in Mixed Shuffle
- Fixed issue with Frog/Dig Game edges duplicating in edge pool, causing Insanity-like behavior
### 0.1.5.2
- Partial revert of horizontal VRAM fix from v0.1.5.0
### 0.1.5.1
- Fixed issue with Flute Spot 7 logically connecting to the wrong area in Mixed/Crossed OW Shuffle
- Fixed issue with TR portal not requiring mitts when tile is swapped
### 0.1.5.0
- Added OW Tile Swap setting
- Fixed horizontal VRAM visual loading glitch on megatiles
- ~\~Merged DR v0.4.0.7 - Fast Credits / Reduced Flashing / Sprite Author in Credits~~ Didn't fully merge
### 0.1.4.3
- \~Merged DR v0.4.0.6 - TT Maiden Attic Hint / DR Entrance Floor Mat Mods / Hard/Expert Item Pool Fix~
### 0.1.4.2
- Modified various OW map terrain specific to OW Shuffle
- Changed World check to table-based vs OW ID-based (should have no effect with current modes)
- \~Merged DR v0.4.0.5 - Mystery Boss Shuffle Fix / Swordless+Hard Item Pool Fix / Insanity+Inverted ER Fixes~
### 0.1.4.1
- Moved Inverted Pyramid Entrance to top of HC Ledge
- Fixed various issues with Inverted and Insanity
### 0.1.4.0
- Initial Inverted Implementation
- Fix for Kakariko Pond no longer requiring fake flipper
### 0.1.3.1
- Various logic fixes and region prep for Inverted
- Fixed muted MSU-1 music in door rando when descending GT Climb stairs
- Fixed Standard + Vanilla (thanks compiling)
- \~Merged DR v0.4.0.4 - Shuffle Link's House / Experimental Bunny Start / 10 Bomb Fix~
### 0.1.3.0
- Added OWG Logic for OW Shuffle
- \~Merged DR v0.4.0.2 - OWG Framework / YAML~
### 0.1.2.2
- Re-purposed OW Shuffle setting to Layout Shuffle
- Merged Parallel Worlds setting into Layout Shuffle
- Added guaranteed Flute hint for OW Shuffle modes
### 0.1.2.1
- Made possible fix for Standard
- \~Merged DR v0.3.1.10 - Fixed Standard generation~
### 0.1.2.0
- Added 'Parallel Worlds' toggle option
- Updated shuffle algorithm
- Renamed some OW areas
### 0.1.1.2
- If Link's current position fits within the incoming gap, Link will not get re-centered to the incoming gap
- Added Rule for Pearl required to drop down back of SW
- \~Merged DR v0.3.1.8 - Improved Shopsanity pricing - Fixed Retro generation~
### 0.1.1.1
- Fixed camera unlocking issue
- Changed default setting for DR to Vanilla
### 0.1.1.0
- Added 'Keep Similar Edges Together' toggle option
- Removed Duplicate Pyramid Ledge location, causing a Warning of unfilled location on generation
### 0.1.0.3
- Modified various logic rule for some OW locations
### 0.1.0.2
- Fixed error generating a bad Spoiler log
### 0.1.0.1
- Separated DR versioning from OR versioning
- Removed LW/DW flag toggle on transitions
### 0.1.0.0
- Initial release
-487
View File
@@ -1,487 +0,0 @@
import argparse
import copy
import json
import os
import shlex
import sys
import textwrap
from source.classes.BabelFish import BabelFish
from source.classes.CustomSettings import CustomSettings
from Utils import update_deprecated_args
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def _get_help_string(self, action):
return textwrap.dedent(action.help)
def parse_cli(argv, no_defaults=False):
def defval(value):
return value if not no_defaults else None
# get settings
settings = parse_settings()
lang = "en"
fish = BabelFish(lang=lang)
# we need to know how many players we have first
# also if we're loading our own settings file, we should do that now
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--settingsfile', help="input json file of settings", type=str)
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
parser.add_argument('--customizer', help='input yaml file for customizations', type=str)
parser.add_argument('--print_template_yaml', help='print example yaml for current settings',
default=False, action="store_true")
parser.add_argument('--print_custom_yaml', help='print example plando yaml for current settings and placements',
default=False, action="store_true")
parser.add_argument('--mystery', dest="mystery", default=False, action="store_true")
multiargs, _ = parser.parse_known_args(argv)
if multiargs.settingsfile:
settings = apply_settings_file(settings, multiargs.settingsfile)
player_num = multiargs.multi
if multiargs.customizer:
custom = CustomSettings()
custom.load_yaml(multiargs.customizer)
cp = custom.determine_players()
if cp:
player_num = cp
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
# get args
args = []
with open(os.path.join("resources", "app", "cli", "args.json")) as argsFile:
args = json.load(argsFile)
for arg in args:
argdata = args[arg]
argname = "--" + arg
argatts = {}
argatts["help"] = "(default: %(default)s)"
if "action" in argdata:
argatts["action"] = argdata["action"]
if "choices" in argdata:
argatts["choices"] = argdata["choices"]
argatts["const"] = argdata["choices"][0]
argatts["default"] = argdata["choices"][0]
argatts["nargs"] = "?"
if arg in settings:
default = settings[arg]
if "type" in argdata and argdata["type"] == "bool":
default = settings[arg] != 0
argatts["default"] = defval(default)
arghelp = fish.translate("cli", "help", arg)
if "help" in argdata and argdata["help"] == "suppress":
argatts["help"] = argparse.SUPPRESS
elif not isinstance(arghelp, str):
argatts["help"] = '\n'.join(arghelp).replace("\\'", "'")
else:
argatts["help"] = arghelp + " " + argatts["help"]
parser.add_argument(argname, **argatts)
parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli", "help", "seed")), type=int)
parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli", "help", "count")), type=int)
parser.add_argument('--tries', default=defval(int(settings["tries"]) if settings["tries"] != "" and settings["tries"] is not None else 1), help="\n".join(fish.translate("cli", "help", "tries")), type=int)
parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS)
# included for backwards compatibility
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
parser.add_argument('--securerandom', default=defval(settings["securerandom"]), action='store_true')
parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1))
parser.add_argument('--settingsfile', dest="filename", help="input json file of settings", type=str)
parser.add_argument('--customizer', dest="customizer", help='input yaml file for customizations', type=str)
parser.add_argument('--print_template_yaml', dest="print_template_yaml", default=False, action="store_true")
parser.add_argument('--print_custom_yaml', dest="print_custom_yaml", default=False, action="store_true")
if player_num:
for player in range(1, player_num + 1):
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
ret = parser.parse_args(argv)
if ret.keysanity:
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = ['wild'] * 4
if ret.keydropshuffle:
ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' else ret.dropshuffle
ret.pottery = 'keys' if ret.pottery == 'none' else ret.pottery
if ret.retro or ret.mode == 'retro':
if ret.bow_mode == 'progressive':
ret.bow_mode = 'retro'
elif ret.bow_mode == 'silvers':
ret.bow_mode = 'retro_silvers'
ret.take_any = 'random' if ret.take_any == 'none' else ret.take_any
ret.keyshuffle = 'universal'
if ret.ow_unparallel:
ret.ow_parallel = False
if ret.ow_shuffle == 'parallel':
ret.ow_layout = 'wild'
ret.ow_parallel = True
elif ret.ow_shuffle == 'full':
ret.ow_layout = 'wild'
ret.ow_parallel = False
if ret.ow_no_fog:
ret.ow_fog = False
if player_num:
defaults = copy.deepcopy(ret)
for player in range(1, player_num + 1):
playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True)
if playerargs.filename:
playersettings = apply_settings_file({}, playerargs.filename)
for k, v in playersettings.items():
setattr(playerargs, k, v)
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty',
'item_functionality', 'ow_shuffle', 'ow_layout',
'ow_parallel', 'ow_terrain', 'ow_crossed',
'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool',
'ow_fluteshuffle', 'ow_fog', 'flute_mode', 'bow_mode',
'take_any', 'boots_hint', 'shuffle_followers',
'shuffle', 'door_shuffle', 'intensity',
'crystals_ganon', 'crystals_gt', 'bosses_ganon',
'bosshunt_include_agas', 'ganon_item', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle',
'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap',
'startinventory', 'usestartinventory', 'bombbag',
'shuffleganon', 'overworld_map', 'restrict_boss_items',
'triforce_max_difference', 'triforce_pool_min',
'triforce_pool_max', 'triforce_goal_min',
'triforce_goal_max', 'triforce_min_difference',
'triforce_goal', 'triforce_pool', 'shufflelinks',
'shuffletavern', 'skullwoods', 'linked_drops',
'pseudoboots', 'mirrorscroll', 'dark_rooms',
'damage_challenge', 'shuffle_damage_table',
'crystal_book', 'retro', 'accessibility', 'hints',
'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health',
'enemy_damage', 'shufflepots', 'ow_palettes',
'uw_palettes', 'sprite', 'triforce_gfx', 'disablemusic',
'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
'remote_items', 'shopsanity', 'dropshuffle', 'pottery',
'keydropshuffle', 'mixed_travel',
'standardize_palettes', 'code', 'reduce_flashing',
'shuffle_sfx', 'shuffle_sfxinstruments',
'shuffle_songinstruments', 'msu_resume',
'collection_rate', 'colorizepots', 'decoupledoors',
'door_type_mode', 'bonk_drops', 'trap_door_mode',
'key_logic_algorithm', 'door_self_loops',
'any_enemy_logic', 'aga_randomness', 'money_balance']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})
else:
getattr(ret, name)[player] = value
return ret
def apply_settings_file(settings, settings_path):
if os.path.exists(settings_path):
with open(settings_path) as json_file:
data = json.load(json_file)
for k, v in data.items():
settings[k] = v
return settings
def parse_settings():
# set default settings
settings = {
"lang": "en",
"retro": False,
"bombbag": False,
"mode": "open",
"boots_hint": False,
"logic": "noglitches",
"goal": "ganon",
"crystals_gt": "7",
"crystals_ganon": "7",
"bosses_ganon": "8",
"bosshunt_include_agas": False,
"ganon_item": "silver",
"swords": "random",
"flute_mode": "normal",
"bow_mode": "progressive",
"difficulty": "normal",
"item_functionality": "normal",
"timer": "none",
"progressive": "on",
"accessibility": "items",
"algorithm": "balanced",
"mystery": False,
"suppress_meta": False,
"restrict_boss_items": "none",
# Shuffle Ganon defaults to TRUE
"openpyramid": "auto",
"shuffleganon": True,
"ow_shuffle": "vanilla", # for backwards compatibility
"ow_layout": "vanilla",
"ow_parallel": True,
"ow_unparallel": False,
"ow_terrain": False,
"ow_crossed": "none",
"ow_keepsimilar": False,
"ow_mixed": False,
"ow_whirlpool": False,
"ow_fluteshuffle": "vanilla",
"ow_fog": True,
"ow_no_fog": False,
"shuffle_followers": False,
"bonk_drops": False,
"shuffle": "vanilla",
"shufflelinks": False,
"shuffletavern": True,
'skullwoods': 'original',
'linked_drops': 'unset',
"overworld_map": "default",
"take_any": "none",
"pseudoboots": False,
"mirrorscroll": False,
"dark_rooms": "require_lamp",
"damage_challenge": "normal",
"shuffle_damage_table": "vanilla",
"crystal_book": False,
"shuffleenemies": "none",
"shufflebosses": "none",
"enemy_damage": "default",
"enemy_health": "default",
'any_enemy_logic': 'allow_all',
"shopsanity": False,
"keydropshuffle": False,
"dropshuffle": "none",
"pottery": "none",
"colorizepots": True,
"shufflepots": False,
"mapshuffle": "none",
"compassshuffle": "none",
"keyshuffle": "none",
"bigkeyshuffle": "none",
"prizeshuffle": "none",
"showloot": "never",
"showmap": "map",
"keysanity": False,
"door_shuffle": "vanilla",
"intensity": 3,
"door_type_mode": "original",
"trap_door_mode": "optional",
"key_logic_algorithm": "partial",
"decoupledoors": False,
"door_self_loops": False,
"experimental": False,
"dungeon_counters": "default",
"mixed_travel": "prevent",
"standardize_palettes": "standardize",
'aga_randomness': True,
'money_balance': 100,
"triforce_pool": 0,
"triforce_goal": 0,
"triforce_pool_min": 0,
"triforce_pool_max": 0,
"triforce_goal_min": 0,
"triforce_goal_max": 0,
"triforce_min_difference": 0,
"triforce_max_difference": 10000,
"code": "",
"multi": 1,
"names": "",
"securerandom": False,
"hints": False,
"disablemusic": False,
"quickswap": False,
"heartcolor": "red",
"heartbeep": "normal",
"sprite": None,
"triforce_gfx": None,
"fastmenu": "normal",
"ow_palettes": "default",
"uw_palettes": "default",
"reduce_flashing": False,
"shuffle_sfx": False,
"shuffle_sfxinstruments": False,
"shuffle_songinstruments": False,
"msu_resume": False,
"collection_rate": False,
'spoiler': 'full',
# Playthrough defaults to TRUE
# ROM defaults to TRUE
"calc_playthrough": True,
"create_rom": True,
"bps": False,
"usestartinventory": False,
"custom": False,
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
"patch": os.path.join(".", "Patch File.bps"),
"seed": "",
"count": 1,
"tries": 1,
"startinventory": "",
'beemizer': '0',
"remote_items": False,
"race": False,
"customitemarray": {
"bow": 0,
"progressivebow": 2,
"boomerang": 1,
"redmerang": 1,
"hookshot": 1,
"mushroom": 1,
"powder": 1,
"firerod": 1,
"icerod": 1,
"bombos": 1,
"ether": 1,
"quake": 1,
"lamp": 1,
"hammer": 1,
"shovel": 1,
"flute": 1,
"bugnet": 1,
"book": 1,
"bottle": 4,
"somaria": 1,
"byrna": 1,
"cape": 1,
"mirror": 1,
"boots": 1,
"powerglove": 0,
"titansmitt": 0,
"progressiveglove": 2,
"flippers": 1,
"pearl": 1,
"heartpiece": 24,
"heartcontainer": 10,
"sancheart": 1,
"sword1": 0,
"sword2": 0,
"sword3": 0,
"sword4": 0,
"progressivesword": 4,
"shield1": 0,
"shield2": 0,
"shield3": 0,
"progressiveshield": 3,
"mail2": 0,
"mail3": 0,
"progressivemail": 2,
"halfmagic": 1,
"quartermagic": 0,
"bombsplus5": 0,
"bombsplus10": 0,
"arrowsplus5": 0,
"arrowsplus10": 0,
"arrow1": 1,
"arrow10": 12,
"bomb1": 0,
"bomb3": 16,
"bomb10": 1,
"rupee1": 2,
"rupee5": 4,
"rupee20": 28,
"rupee50": 7,
"rupee100": 1,
"rupee300": 5,
"blueclock": 0,
"greenclock": 0,
"redclock": 0,
"silversupgrade": 0,
"generickeys": 0,
"triforcepieces": 0,
"triforcepiecesgoal": 0,
"triforce": 0,
"rupoor": 0,
"rupoorcost": 10
},
"randomSprite": False,
"outputpath": os.path.join("."),
"settingsonload": "saved",
"outputname": "",
"startinventoryarray": {},
"notes": ""
}
# read saved settings file if it exists and set these
settings_path = os.path.join(".", "resources", "user", "settings.json")
settings = apply_settings_file(settings, settings_path)
if settings["settingsonload"] == "saved":
settings_path = os.path.join(".", "resources", "user", "saved.json")
settings = apply_settings_file(settings, settings_path)
elif settings["settingsonload"] == "lastused":
settings_path = os.path.join(".", "resources", "user", "last.json")
settings = apply_settings_file(settings, settings_path)
return settings
# Priority fallback is:
# 1: CLI
# 2: Settings file(s)
# 3: Canned defaults
def get_args_priority(settings_args, gui_args, cli_args):
args = {}
args["settings"] = parse_settings() if settings_args is None else settings_args
args["gui"] = gui_args
args["cli"] = cli_args
args["load"] = args["settings"]
if args["gui"] is not None:
for k in args["gui"]:
if k not in args["load"] or args["load"][k] != args["gui"]:
args["load"][k] = args["gui"][k]
if args["cli"] is None:
args["cli"] = {}
cli = vars(parse_cli(None))
for k, v in cli.items():
if isinstance(v, dict) and 1 in v:
args["cli"][k] = v[1]
else:
args["cli"][k] = v
args["cli"] = argparse.Namespace(**args["cli"])
cli = vars(args["cli"])
for k in vars(args["cli"]):
load_doesnt_have_key = k not in args["load"]
cli_val = cli[k]
if isinstance(cli_val, dict) and 1 in cli_val:
cli_val = cli_val[1]
different_val = (k in args["load"] and k in cli) and (str(args["load"][k]) != str(cli_val))
cli_has_empty_dict = k in cli and isinstance(cli_val, dict) and len(cli_val) == 0
if load_doesnt_have_key or different_val:
if not cli_has_empty_dict:
args["load"][k] = cli_val
newArgs = {}
for key in ["settings", "gui", "cli", "load"]:
if args[key]:
if isinstance(args[key], dict):
newArgs[key] = argparse.Namespace(**args[key])
else:
newArgs[key] = args[key]
else:
newArgs[key] = args[key]
newArgs[key] = update_deprecated_args(newArgs[key])
args = newArgs
return args
-50
View File
@@ -1,50 +0,0 @@
from dataclasses import dataclass, field
from typing import List
import RaceRandom as random
def _load_entries():
entries = []
with open("data/damage_table.bin", 'rb') as stream:
for sprite in range(0x100):
entries.append([])
for pair in range(8):
byte = stream.read(1)[0]
entries[sprite].append(byte >> 4)
entries[sprite].append(byte & 0x0F)
return entries
@dataclass
class DamageTable:
_entries: List[int] = field(default_factory=_load_entries)
def randomize_sword_subclasses(self):
for sprite in range(0xD8):
for dclass in range(1, 6):
if self._entries[sprite][dclass] > 0:
self._entries[sprite][dclass] = random.randint(1, 7)
def get_bytes(self):
values = []
for sprite in range(0x100):
for dclass in range(0, 16, 2):
cur = self._entries[sprite][dclass] & 0xF
nex = self._entries[sprite][dclass + 1] & 0xF
values.append((cur << 4) | nex)
return values
if __name__ == '__main__':
def print_table(table):
bytelist = table.get_bytes()
for sprite in range(0x100):
for byte in range(8):
print(format(bytelist.pop(0), "02X"), end=" ")
print()
print()
dmg = DamageTable()
print_table(dmg)
dmg.randomize_sword_subclasses()
print_table(dmg)
-4777
View File
File diff suppressed because it is too large Load Diff
-1678
View File
File diff suppressed because it is too large Load Diff
-4257
View File
File diff suppressed because it is too large Load Diff
-102
View File
@@ -1,102 +0,0 @@
#!/usr/bin/env python3
if __name__ == '__main__':
from source.meta.check_requirements import check_requirements
check_requirements(console=True)
import logging
import os
import sys
import RaceRandom as random
import source.classes.diags as diagnostics
from CLI import get_args_priority, parse_cli
from Fill import FillError
from Main import EnemizerError, main
from Rom import get_sprite_from_name
from source.classes.BabelFish import BabelFish
from Utils import close_console, is_bundled
def start():
args = parse_cli(None)
# print diagnostics
# usage: py DungeonRandomizer.py --diags
if args.diags:
diags = diagnostics.output()
print("\n".join(diags))
sys.exit(0)
if is_bundled() and len(sys.argv) == 1:
# for the bundled builds, if we have no arguments, the user
# probably wants the gui. Users of the bundled build who want the command line
# interface shouuld specify at least one option, possibly setting a value to a
# default if they like all the defaults
from Gui import guiMain
close_console()
guiMain()
sys.exit(0)
# ToDo: Validate files further than mere existance
if not args.jsonout and not os.path.isfile(args.rom):
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
sys.exit(1)
if any([sprite is not None and not os.path.isfile(sprite) and not get_sprite_from_name(sprite) for sprite in args.sprite.values()]):
if not args.jsonout:
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
sys.exit(1)
else:
raise IOError('Cannot find sprite file at %s' % args.sprite)
# set up logger
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel)
priority = get_args_priority(None, None, args)
lang = "en"
if "load" in priority and "lang" in priority["load"]:
lang = priority["load"].lang
fish = BabelFish(lang=lang)
if args.gui:
from Gui import guiMain
guiMain(args)
else:
count = args.count or 1
tries = args.tries or 1
random.seed(None)
seed = args.seed or random.randint(0, 999999999)
failures = []
attempts = 0
logger = logging.getLogger('')
for seednum in range(count):
for trynum in range(tries):
try:
attempts += 1
main(seed=seed, args=args, fish=fish)
logger.info('%s %s', fish.translate("cli","cli","finished.run"), seednum + 1)
logger.info('')
seed = random.randint(0, 999999999)
break
except (FillError, EnemizerError, Exception, RuntimeError) as err:
failures.append((err, seed))
logger.exception('Attempt %d - %s: %s', trynum, fish.translate("cli","cli","generation.failed"), err)
logger.info('')
seed = random.randint(0, 999999999)
if count > 1 or tries > 1:
for fail in failures:
logger.info('seed %9s failed with: %s', fail[1], fail[0])
if len(failures) > 0:
logger.info('')
fail_rate = 100 * len(failures) / attempts
success_rate = 100 * (attempts - len(failures)) / attempts
logger.info('Generation failure rate: %6.2f%%', fail_rate)
logger.info('Generation success rate: %6.2f%%', success_rate)
if len(failures) == attempts:
sys.exit(1)
if __name__ == '__main__':
start()
+124 -324
View File
@@ -1,43 +1,38 @@
import RaceRandom as random
from BaseClasses import Dungeon
from Bosses import BossFactory
from Fill import fill_restrictive
from Items import ItemFactory
def create_dungeons(world, player):
def make_dungeon(name, id, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.keyshuffle[player] == 'universal' else small_keys,
dungeon_items, player, id)
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro else small_keys, dungeon_items, player)
dungeon.boss = BossFactory(default_boss, player)
for region in dungeon.regions:
world.get_region(region, player).dungeon = dungeon
dungeon.world = world
return dungeon
hc_dungeon_items = ['Map (Escape)']
at_dungeon_items = []
ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'], None, [ItemFactory('Small Key (Escape)', player)], [ItemFactory('Map (Escape)', player)])
EP = make_dungeon('Eastern Palace', 'Armos Knights', ['Eastern Palace'], ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player))
DP = make_dungeon('Desert Palace', 'Lanmolas', ['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)', 'Desert Palace East'], ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player))
ToH = make_dungeon('Tower of Hera', 'Moldorm', ['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'], ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player))
PoD = make_dungeon('Palace of Darkness', 'Helmasaur King', ['Palace of Darkness (Entrance)', 'Palace of Darkness (Center)', 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness (Bonk Section)', 'Palace of Darkness (North)', 'Palace of Darkness (Maze)', 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness (Final Section)'], ItemFactory('Big Key (Palace of Darkness)', player), ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player))
TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'], ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section', 'Skull Woods Second Section', 'Skull Woods Second Section (Drop)', 'Skull Woods Final Section (Mothula)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'], ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 2, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
SP = make_dungeon('Swamp Palace', 'Arrghus', ['Swamp Palace (Entrance)', 'Swamp Palace (First Room)', 'Swamp Palace (Starting Area)', 'Swamp Palace (Center)', 'Swamp Palace (North)'], ItemFactory('Big Key (Swamp Palace)', player), [ItemFactory('Small Key (Swamp Palace)', player)], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
IP = make_dungeon('Ice Palace', 'Kholdstare', ['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (Back)', 'Ice Palace (East)', 'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
MM = make_dungeon('Misery Mire', 'Vitreous', ['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)', 'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
TR = make_dungeon('Turtle Rock', 'Trinexx', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
if world.showloot[player] == 'compass':
if world.dropshuffle[player] == 'underworld' or world.pottery[player] in ['dungeon', 'reduced', 'clustered', 'nonempty', 'lottery']:
hc_dungeon_items.append('Compass (Escape)')
at_dungeon_items.append('Compass (Agahnims Tower)')
elif world.compassshuffle[player] == 'wild':
hc_dungeon_items.append('Compass (Escape)')
if world.keyshuffle[player] == 'wild':
at_dungeon_items.append('Compass (Agahnims Tower)')
ES = make_dungeon('Hyrule Castle', 1, None, hyrule_castle_regions, None, [ItemFactory('Small Key (Escape)', player)], ItemFactory(hc_dungeon_items, player))
EP = make_dungeon('Eastern Palace', 2, 'Armos Knights', eastern_regions, ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player))
DP = make_dungeon('Desert Palace', 3, 'Lanmolas', desert_regions, ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player))
ToH = make_dungeon('Tower of Hera', 10, 'Moldorm', hera_regions, ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player))
PoD = make_dungeon('Palace of Darkness', 6, 'Helmasaur King', pod_regions, ItemFactory('Big Key (Palace of Darkness)', player), ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player))
TT = make_dungeon('Thieves Town', 11, 'Blind', thieves_regions, ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
SW = make_dungeon('Skull Woods', 8, 'Mothula', skull_regions, ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 3, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
SP = make_dungeon('Swamp Palace', 5, 'Arrghus', swamp_regions, ItemFactory('Big Key (Swamp Palace)', player), [ItemFactory('Small Key (Swamp Palace)', player)], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
IP = make_dungeon('Ice Palace', 9, 'Kholdstare', ice_regions, ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
MM = make_dungeon('Misery Mire', 7, 'Vitreous', mire_regions, ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
TR = make_dungeon('Turtle Rock', 12, 'Trinexx', tr_regions, ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
AT = make_dungeon('Agahnims Tower', 4, 'Agahnim', tower_regions, None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), ItemFactory(at_dungeon_items, player))
GT = make_dungeon('Ganons Tower', 13, 'Agahnim2', gt_regions, ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
if world.mode != 'inverted':
AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
GT = make_dungeon('Ganons Tower', 'Agahnim2', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
else:
AT = make_dungeon('Inverted Agahnims Tower', 'Agahnim', ['Inverted Agahnims Tower', 'Agahnim 1'], None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
GT = make_dungeon('Inverted Ganons Tower', 'Agahnim2', ['Inverted Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
GT.bosses['bottom'] = BossFactory('Armos Knights', player)
GT.bosses['middle'] = BossFactory('Lanmolas', player)
@@ -45,322 +40,127 @@ def create_dungeons(world, player):
world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
def fill_dungeons(world):
freebes = ['Ganons Tower - Map Chest', 'Palace of Darkness - Harmless Hellway', 'Palace of Darkness - Big Key Chest', 'Turtle Rock - Big Key Chest']
dungeon_music_addresses = {'Eastern Palace': [0x1559A],
'Desert Palace': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
'Tower of Hera': [0x155C5, 0x1107A, 0x10B8C],
'Palace of Darkness': [0x155B8],
'Swamp Palace': [0x155B7],
'Thieves Town': [0x155C6],
'Skull Woods': [0x155BA, 0x155BB, 0x155BC, 0x155BD, 0x15608, 0x15609, 0x1560A, 0x1560B],
'Ice Palace': [0x155BF],
'Misery Mire': [0x155B9],
'Turtle Rock': [0x155C7, 0x155A7, 0x155AA, 0x155AB]}
all_state_base = world.get_all_state()
hyrule_castle_regions = [
'Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall',
'Hyrule Castle West Hall', 'Hyrule Castle Back Hall', 'Hyrule Castle Throne Room', 'Hyrule Castle Behind Tapestry',
'Hyrule Dungeon Map Room', 'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk',
'Hyrule Dungeon South Abyss', 'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom',
'Hyrule Dungeon Armory Main', 'Hyrule Dungeon Armory Boomerang', 'Hyrule Dungeon Armory North Branch',
'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Hyrule Dungeon Cell', 'Sewers Behind Tapestry',
'Sewers Rope Room', 'Sewers Dark Cross', 'Sewers Water', 'Sewers Dark Aquabats', 'Sewers Key Rat',
'Sewers Rat Path', 'Sewers Secret Room Blocked Path', 'Sewers Secret Room', 'Sewers Yet More Rats',
'Sewers Pull Switch', 'Sanctuary', 'Sanctuary Portal', 'Hyrule Castle West Portal', 'Hyrule Castle South Portal',
'Hyrule Castle East Portal'
]
for player in range(1, world.players + 1):
pinball_room = world.get_location('Skull Woods - Pinball Room', player)
if world.retro:
world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
else:
world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
pinball_room.event = True
pinball_room.locked = True
eastern_regions = [
'Eastern Lobby', 'Eastern Lobby Bridge', 'Eastern Lobby Left Ledge', 'Eastern Lobby Right Ledge',
'Eastern Cannonball', 'Eastern Cannonball Ledge', 'Eastern Courtyard Ledge', 'Eastern East Wing',
'Eastern Pot Switch', 'Eastern Map Balcony', 'Eastern Map Room', 'Eastern West Wing', 'Eastern Stalfos Spawn',
'Eastern Compass Room', 'Eastern Hint Tile', 'Eastern Hint Tile Blocked Path', 'Eastern Courtyard',
'Eastern Fairies', 'Eastern Map Valley', 'Eastern Dark Square', 'Eastern Dark Pots', 'Eastern Big Key',
'Eastern Darkness', 'Eastern Rupees', 'Eastern Attic Start', 'Eastern False Switches', 'Eastern Cannonball Hell',
'Eastern Single Eyegore', 'Eastern Duo Eyegores', 'Eastern Boss', 'Eastern Boss Spoils', 'Eastern Portal'
]
dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons]
desert_regions = [
'Desert Main Lobby', 'Desert Left Alcove', 'Desert Right Alcove', 'Desert Dead End', 'Desert East Lobby',
'Desert East Wing', 'Desert Compass Room', 'Desert Cannonball', 'Desert Arrow Pot Corner', 'Desert Trap Room',
'Desert North Hall', 'Desert Map Room', 'Desert Sandworm Corner', 'Desert Bonk Torch', 'Desert Circle of Pots',
'Desert Big Chest Room', 'Desert West Wing', 'Desert West Lobby', 'Desert Fairy Fountain', 'Desert Back Lobby',
'Desert Tiles 1', 'Desert Bridge', 'Desert Four Statues', 'Desert Beamos Hall', 'Desert Tiles 2',
'Desert Wall Slide', 'Desert Boss', 'Desert Boss Spoils', 'Desert West Portal', 'Desert South Portal', 'Desert East Portal',
'Desert Back Portal'
]
loopcnt = 0
while dungeons:
loopcnt += 1
dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0)
# this is what we need to fill
dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions]
random.shuffle(dungeon_locations)
hera_regions = [
'Hera Lobby', 'Hera Lobby - Crystal', 'Hera Front', 'Hera Back', 'Hera Front - Crystal', 'Hera Down Stairs Landing',
'Hera Down Stairs Landing - Ranged Crystal', 'Hera Up Stairs Landing', 'Hera Up Stairs Landing - Ranged Crystal',
'Hera Back - Ranged Crystal', 'Hera Basement Cage', 'Hera Basement Cage - Crystal', 'Hera Tile Room',
'Hera Tridorm', 'Hera Tridorm - Crystal', 'Hera Torches', 'Hera Beetles', 'Hera Startile Corner',
'Hera Startile Wide', 'Hera Startile Wide - Crystal', 'Hera 4F', 'Hera Big Chest Landing', 'Hera 5F',
'Hera 5F Pot Block', 'Hera Fairies', 'Hera Boss', 'Hera Boss Spoils', 'Hera Portal'
]
all_state = all_state_base.copy()
tower_regions = [
'Tower Lobby', 'Tower Gold Knights', 'Tower Room 03', 'Tower Lone Statue', 'Tower Dark Maze', 'Tower Dark Chargers',
'Tower Dual Statues', 'Tower Dark Pits', 'Tower Dark Archers', 'Tower Red Spears', 'Tower Red Guards',
'Tower Circle of Pots', 'Tower Pacifist Run', 'Tower Push Statue', 'Tower Catwalk', 'Tower Antechamber',
'Tower Altar', 'Tower Agahnim 1', 'Agahnims Tower Portal'
]
# first place big key
if big_key is not None:
bk_location = None
for location in dungeon_locations:
if location.item_rule(big_key):
bk_location = location
break
pod_regions = [
'PoD Lobby', 'PoD Left Cage', 'PoD Middle Cage', 'PoD Shooter Room', 'PoD Pit Room', 'PoD Pit Room Blocked',
'PoD Arena Main', 'PoD Arena Main - Ranged Crystal', 'PoD Arena North', 'PoD Arena Bridge', 'PoD Arena Bridge - Ranged Crystal',
'PoD Arena Landing', 'PoD Arena Right', 'PoD Arena Right - Ranged Crystal', 'PoD Arena Ledge', 'PoD Arena Ledge - Ranged Crystal', 'PoD Sexy Statue',
'PoD Map Balcony', 'PoD Map Balcony - Ranged Crystal', 'PoD Fairy Pool', 'PoD Conveyor', 'PoD Mimics 1',
'PoD Jelly Hall', 'PoD Warp Hint', 'PoD Warp Room', 'PoD Stalfos Basement', 'PoD Basement Ledge',
'PoD Big Key Landing', 'PoD Falling Bridge', 'PoD Falling Bridge Mid', 'PoD Falling Bridge Ledge', 'PoD Dark Maze',
'PoD Big Chest Balcony', 'PoD Compass Room', 'PoD Dark Basement', 'PoD Harmless Hellway', 'PoD Mimics 2',
'PoD Bow Statue Left', 'PoD Bow Statue Left - Crystal', 'PoD Bow Statue Right', 'PoD Bow Statue Right - Ranged Crystal',
'PoD Dark Pegs Landing', 'PoD Dark Pegs Right', 'PoD Dark Pegs Middle', 'PoD Dark Pegs Left', 'PoD Dark Pegs Landing - Ranged Crystal',
'PoD Dark Pegs Middle - Ranged Crystal', 'PoD Dark Pegs Left - Ranged Crystal', 'PoD Lonely Turtle', 'PoD Turtle Party',
'PoD Dark Alley', 'PoD Callback', 'PoD Boss', 'PoD Boss Spoils', 'Palace of Darkness Portal'
]
if bk_location is None:
raise RuntimeError('No suitable location for %s' % big_key)
swamp_regions = [
'Swamp Lobby', 'Swamp Entrance', 'Swamp Pot Row', 'Swamp Map Ledge', 'Swamp Trench 1 Approach',
'Swamp Trench 1 Nexus', 'Swamp Trench 1 Alcove', 'Swamp Trench 1 Key Ledge', 'Swamp Trench 1 Departure',
'Swamp Hammer Switch', 'Swamp Hub', 'Swamp Hub Side Ledges', 'Swamp Hub Dead Ledge', 'Swamp Hub North Ledge',
'Swamp Donut Top', 'Swamp Donut Bottom', 'Swamp Compass Donut', 'Swamp Crystal Switch Outer',
'Swamp Crystal Switch Outer - Ranged Crystal', 'Swamp Crystal Switch Inner', 'Swamp Crystal Switch Inner - Crystal',
'Swamp Shortcut', 'Swamp Trench 2 Pots', 'Swamp Trench 2 Blocks', 'Swamp Trench 2 Alcove',
'Swamp Trench 2 Departure', 'Swamp Big Key Ledge', 'Swamp West Shallows', 'Swamp West Block Path',
'Swamp West Ledge', 'Swamp Barrier Ledge', 'Swamp Barrier', 'Swamp Attic', 'Swamp Push Statue', 'Swamp Shooters',
'Swamp Left Elbow', 'Swamp Right Elbow', 'Swamp Drain Left', 'Swamp Drain Right', 'Swamp Flooded Room',
'Swamp Flooded Spot', 'Swamp Basement Shallows', 'Swamp Waterfall Room', 'Swamp Refill', 'Swamp Behind Waterfall',
'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss', 'Swamp Boss Spoils', 'Swamp Portal'
]
world.push_item(bk_location, big_key, False)
bk_location.event = True
bk_location.locked = True
dungeon_locations.remove(bk_location)
big_key = None
skull_regions = [
'Skull 1 Lobby', 'Skull Map Room', 'Skull Pot Circle', 'Skull Pull Switch', 'Skull Big Chest', 'Skull Pinball',
'Skull Pot Prison', 'Skull Compass Room', 'Skull Left Drop', 'Skull 2 East Lobby', 'Skull Big Key',
'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull 2 West Lobby Ledge',
'Skull X Room', 'Skull 3 Lobby', 'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits',
'Skull Torch Room', 'Skull Vines', 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss', 'Skull Boss Spoils',
'Skull 1 Portal', 'Skull 2 East Portal', 'Skull 2 West Portal', 'Skull 3 Portal'
]
# next place small keys
while small_keys:
small_key = small_keys.pop()
all_state.sweep_for_events()
sk_location = None
for location in dungeon_locations:
if location.name in freebes or (location.can_reach(all_state) and location.item_rule(small_key)):
sk_location = location
break
thieves_regions = [
'Thieves Lobby', 'Thieves Ambush', 'Thieves BK Corner', 'Thieves Rail Ledge', 'Thieves Compass Room',
'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom',
'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway',
'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch',
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Attic Switch', 'Thieves Cricket Hall Left',
'Thieves Cricket Hall Right', 'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry',
'Thieves Lonely Zazak', "Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge',
'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap', 'Revealing Light',
'Thieves Boss Spoils', 'Thieves Town Portal'
]
if sk_location is None:
# need to retry this later
small_keys.append(small_key)
dungeons.append((dungeon_regions, big_key, small_keys, dungeon_items))
# infinite regression protection
if loopcnt < (30 * world.players):
break
else:
raise RuntimeError('No suitable location for %s' % small_key)
ice_regions = [
'Ice Lobby', 'Ice Jelly Key', 'Ice Floor Switch', 'Ice Cross Left', 'Ice Cross Bottom', 'Ice Cross Right',
'Ice Cross Top', 'Ice Compass Room', 'Ice Pengator Switch', 'Ice Dead End', 'Ice Big Key', 'Ice Bomb Drop',
'Ice Stalfos Hint', 'Ice Conveyor', 'Ice Conveyor - Crystal', 'Ice Bomb Jump Ledge', 'Ice Bomb Jump Catwalk',
'Ice Narrow Corridor', 'Ice Right H', 'Ice Bomb Drop - Top',
'Ice Pengator Trap', 'Ice Spike Cross', 'Ice Firebar', 'Ice Falling Square', 'Ice Spike Room', 'Ice Hammer Block',
'Ice Tongue Pull', 'Ice Freezors', 'Ice Freezors Ledge', 'Ice Tall Hint', 'Ice Hookshot Ledge',
'Ice Hookshot Balcony', 'Ice Spikeball', 'Ice Lonely Freezor', 'Iced T', 'Ice Catwalk', 'Ice Many Pots',
'Ice Crystal Right', 'Ice Crystal Left', 'Ice Crystal Block', 'Ice Big Chest View', 'Ice Big Chest Landing',
'Ice Backwards Room', 'Ice Anti-Fairy', 'Ice Switch Room', 'Ice Refill', 'Ice Refill - Crystal',
'Ice Fairy', 'Ice Antechamber', 'Ice Boss', 'Ice Boss Spoils', 'Ice Portal'
]
world.push_item(sk_location, small_key, False)
sk_location.event = True
sk_location.locked = True
dungeon_locations.remove(sk_location)
mire_regions = [
'Mire Lobby', 'Mire Post-Gap', 'Mire 2', 'Mire Hub', 'Mire Hub Right', 'Mire Hub Top', 'Mire Hub Switch',
'Mire Lone Shooter', 'Mire Failure Bridge', 'Mire Falling Bridge - Primary', 'Mire Falling Bridge - Failure',
'Mire Falling Bridge - Chest', 'Mire Map Spike Side', 'Mire Map Spot', 'Mire Crystal Dead End',
'Mire Hidden Shooters', 'Mire Hidden Shooters Blocked', 'Mire Cross', 'Mire Minibridge', 'Mire BK Door Room',
'Mire Spikes', 'Mire Ledgehop', 'Mire Bent Bridge', 'Mire Over Bridge', 'Mire Right Bridge', 'Mire Left Bridge',
'Mire Fishbone', 'Mire South Fish', 'Mire Spike Barrier', 'Mire Square Rail', 'Mire Lone Warp',
'Mire Wizzrobe Bypass', 'Mire Conveyor Crystal', 'Mire Conveyor - Crystal', 'Mire Tile Room', 'Mire Compass Room',
'Mire Compass Chest', 'Mire Neglected Room', 'Mire Chest View', 'Mire Conveyor Barrier', 'Mire BK Chest Ledge',
'Mire Warping Pool', 'Mire Torches Top', 'Mire Torches Bottom', 'Mire Attic Hint', 'Mire Dark Shooters',
'Mire Key Rupees', 'Mire Block X', 'Mire Tall Dark and Roomy', 'Mire Tall Dark and Roomy - Ranged Crystal',
'Mire Crystal Right', 'Mire Crystal Mid', 'Mire Crystal Left',
'Mire Crystal Top', 'Mire Shooter Rupees', 'Mire Falling Foes', 'Mire Firesnake Skip', 'Mire Antechamber',
'Mire Boss', 'Mire Boss Spoils', 'Mire Portal'
]
if small_keys:
# key placement not finished, loop again
continue
tr_regions = [
'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Hub Ledges', 'TR Torches Ledge', 'TR Torches',
'TR Roller Room', 'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal',
'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge',
'TR Lava Dual Pipes', 'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal',
'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal', 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers',
'TR Big View', 'TR Big Chest', 'TR Big Chest Entrance', 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull',
'TR Rupees', 'TR Crystaroller Bottom', 'TR Crystaroller Middle', 'TR Crystaroller Top',
'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest', 'TR Crystaroller Middle - Ranged Crystal',
'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride North Platform', 'TR Dark Ride South Platform',
'TR Dark Ride Ledges', 'TR Dash Bridge', 'TR Eye Bridge',
'TR Crystal Maze Start', 'TR Crystal Maze Start - Crystal', 'TR Crystal Maze Interior', 'TR Crystal Maze End',
'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss Balcony', 'TR Final Abyss Ledge', 'TR Boss', 'TR Boss Spoils',
'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal',
'Turtle Rock Eye Bridge Portal'
]
gt_regions = [
'GT Lobby', 'GT Bob\'s Torch', 'GT Hope Room', 'GT Big Chest', 'GT Blocked Stairs', 'GT Bob\'s Room',
'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor',
'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal',
'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk',
'GT Conveyor Cross', 'GT Conveyor Cross Across Pits', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform',
'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry',
'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', 'GT Double Switch Entry',
'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', 'GT Double Switch Left',
'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches',
'GT Double Switch Exit', 'GT Spike Crystal Left',
'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section',
'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot',
'GT Warp Maze Exit Section', 'GT Firesnake Room', 'GT Firesnake Room Ledge', 'GT Warp Maze - Rail Choice',
'GT Warp Maze - Rando Rail', 'GT Warp Maze - Main Rails', 'GT Warp Maze - Pot Rail', 'GT Petting Zoo',
'GT Conveyor Star Pits', 'GT Hidden Star', 'GT DMs Room', 'GT Falling Bridge', 'GT Randomizer Room', 'GT Ice Armos',
'GT Big Key Room', 'GT Four Torches', 'GT Fairy Abyss', 'GT Crystal Paths', 'GT Mimics 1', 'GT Mimics 2',
'GT Dash Hall', 'GT Hidden Spikes', 'GT Cannonball Bridge', 'GT Refill', 'GT Gauntlet 1', 'GT Gauntlet 2',
'GT Gauntlet 3', 'GT Gauntlet 4', 'GT Gauntlet 5', 'GT Beam Dash', 'GT Lanmolas 2', 'GT Quad Pot', 'GT Wizzrobes 1',
'GT Dashing Bridge', 'GT Wizzrobes 2', 'GT Conveyor Bridge', 'GT Torch Cross', 'GT Staredown', 'GT Falling Torches',
'GT Mini Helmasaur Room', 'GT Bomb Conveyor', 'GT Crystal Circles', 'GT Crystal Inner Circle',
'GT Crystal Circles - Ranged Crystal', 'GT Left Moldorm Ledge', 'GT Right Moldorm Ledge', 'GT Moldorm',
'GT Moldorm Pit', 'GT Validation', 'GT Validation Door',
'GT Frozen Over', 'GT Brightly Lit Hall', 'GT Agahnim 2', 'Ganons Tower Portal'
]
# next place dungeon items
if world.place_dungeon_items:
for dungeon_item in dungeon_items:
di_location = dungeon_locations.pop()
world.push_item(di_location, dungeon_item, False)
dungeon_regions = {
'Hyrule Castle': hyrule_castle_regions,
'Eastern Palace': eastern_regions,
'Desert Palace': desert_regions,
'Tower of Hera': hera_regions,
'Agahnims Tower': tower_regions,
'Palace of Darkness': pod_regions,
'Swamp Palace': swamp_regions,
'Skull Woods': skull_regions,
'Thieves Town': thieves_regions,
'Ice Palace': ice_regions,
'Misery Mire': mire_regions,
'Turtle Rock': tr_regions,
'Ganons Tower': gt_regions
}
def get_dungeon_item_pool(world):
return [item for dungeon in world.dungeons for item in dungeon.all_items if item.key or world.place_dungeon_items]
region_starts = {
'Hyrule Castle': ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Sewers Rat Path', 'Sanctuary'],
'Eastern Palace': ['Eastern Lobby'],
'Desert Palace': ['Desert Back Lobby', 'Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby'],
'Tower of Hera': ['Hera Lobby'],
'Agahnims Tower': ['Tower Lobby'],
'Palace of Darkness': ['PoD Lobby'],
'Swamp Palace': ['Swamp Lobby'],
'Skull Woods': ['Skull 1 Lobby', 'Skull 2 East Lobby', 'Skull 2 West Lobby', 'Skull 3 Lobby', 'Skull Pot Circle',
'Skull Pinball', 'Skull Left Drop', 'Skull Back Drop'],
'Thieves Town': ['Thieves Lobby'],
'Ice Palace': ['Ice Lobby'],
'Misery Mire': ['Mire Lobby'],
'Turtle Rock': ['TR Main Lobby', 'TR Lazy Eyes', 'TR Big Chest Entrance', 'TR Eye Bridge'],
'Ganons Tower': ['GT Lobby']
}
def fill_dungeons_restrictive(world, shuffled_locations):
all_state_base = world.get_all_state()
standard_starts = {
'Hyrule Castle': ['Hyrule Castle South']
}
for player in range(1, world.players + 1):
pinball_room = world.get_location('Skull Woods - Pinball Room', player)
if world.retro:
world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
else:
world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
pinball_room.event = True
pinball_room.locked = True
shuffled_locations.remove(pinball_room)
split_region_starts = {
'Desert Palace': {
'Back': ['Desert Back Lobby'],
'Main': ['Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby']
},
'Skull Woods': {
'1': ['Skull 1 Lobby', 'Skull Pot Circle'],
'2': ['Skull 2 West Lobby', 'Skull 2 East Lobby', 'Skull Back Drop'],
'3': ['Skull 3 Lobby']
}
}
if world.keysanity:
#in keysanity dungeon items are distributed as part of the normal item pool
for item in world.get_items():
if item.key:
item.advancement = True
elif item.map or item.compass:
item.priority = True
return
flexible_starts = {
'Skull Woods': ['Skull Left Drop', 'Skull Pinball']
}
dungeon_items = get_dungeon_item_pool(world)
# sort in the order Big Key, Small Key, Other before placing dungeon items
sort_order = {"BigKey": 3, "SmallKey": 2}
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items)
class DungeonInfo:
def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, dungeon_idx, extra_map_idx):
# todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name):
self.free_items = free
self.key_num = keys
self.bk_present = bk
self.map_present = map
self.compass_present = compass
self.bk_drops = bk_drop
self.key_drops = drops
self.prize = prize
self.dungeon_index = dungeon_idx
self.extra_map_index = extra_map_idx
dungeon_table = {
'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0x00, 0x00),
'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, [0x1209D, 0x53F3A, 0x53F3B, 0x180052, 0x180070, 0x186FE2], 0x04, None),
'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, [0x1209E, 0x53F3C, 0x53F3D, 0x180053, 0x180072, 0x186FE3], 0x06, 0x12),
'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, [0x120A5, 0x53F4A, 0x53F4B, 0x18005A, 0x180071, 0x186FEA], 0x14, None),
'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0x08, None),
'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, [0x120A1, 0x53F42, 0x53F43, 0x180056, 0x180073, 0x186FE6], 0x0C, None),
'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, [0x120A0, 0x53F40, 0x53F41, 0x180055, 0x180079, 0x186FE5], 0x0A, None),
'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, [0x120A3, 0x53F46, 0x53F47, 0x180058, 0x180074, 0x186FE8], 0x10, 0x20),
'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, [0x120A6, 0x53F4C, 0x53F4D, 0x18005B, 0x180076, 0x186FEB], 0x16, None),
'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, [0x120A4, 0x53F48, 0x53F49, 0x180059, 0x180078, 0x186FE9], 0x12, None),
'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, [0x120A2, 0x53F44, 0x53F45, 0x180057, 0x180077, 0x186FE7], 0x0E, None),
'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, [0x120A7, 0x53F4E, 0x53F4F, 0x18005C, 0x180075, 0x186FEC], 0x18, 0x3E),
'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0x1A, None),
}
dungeon_keys = {
'Hyrule Castle': 'Small Key (Escape)',
'Eastern Palace': 'Small Key (Eastern Palace)',
'Desert Palace': 'Small Key (Desert Palace)',
'Tower of Hera': 'Small Key (Tower of Hera)',
'Agahnims Tower': 'Small Key (Agahnims Tower)',
'Palace of Darkness': 'Small Key (Palace of Darkness)',
'Swamp Palace': 'Small Key (Swamp Palace)',
'Skull Woods': 'Small Key (Skull Woods)',
'Thieves Town': 'Small Key (Thieves Town)',
'Ice Palace': 'Small Key (Ice Palace)',
'Misery Mire': 'Small Key (Misery Mire)',
'Turtle Rock': 'Small Key (Turtle Rock)',
'Ganons Tower': 'Small Key (Ganons Tower)'
}
dungeon_bigs = {
'Hyrule Castle': 'Big Key (Escape)',
'Eastern Palace': 'Big Key (Eastern Palace)',
'Desert Palace': 'Big Key (Desert Palace)',
'Tower of Hera': 'Big Key (Tower of Hera)',
'Agahnims Tower': 'Big Key (Agahnims Tower)',
'Palace of Darkness': 'Big Key (Palace of Darkness)',
'Swamp Palace': 'Big Key (Swamp Palace)',
'Skull Woods': 'Big Key (Skull Woods)',
'Thieves Town': 'Big Key (Thieves Town)',
'Ice Palace': 'Big Key (Ice Palace)',
'Misery Mire': 'Big Key (Misery Mire)',
'Turtle Rock': 'Big Key (Turtle Rock)',
'Ganons Tower': 'Big Key (Ganons Tower)'
}
dungeon_hints = {
'Hyrule Castle': 'in Hyrule Castle',
'Eastern Palace': 'in Eastern Palace',
'Desert Palace': 'in Desert Palace',
'Tower of Hera': 'in Tower of Hera',
'Agahnims Tower': 'in Castle Tower',
'Palace of Darkness': 'in Palace of Darkness',
'Swamp Palace': 'in Swamp Palace',
'Skull Woods': 'in Skull Woods',
'Thieves Town': 'in Thieves\' Town',
'Ice Palace': 'in Ice Palace',
'Misery Mire': 'in Misery Mire',
'Turtle Rock': 'in Turtle Rock',
'Ganons Tower': 'in Ganon\'s Tower'
}
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
'Tower of Hera - Prize': [0x155C5, 0x1107A, 0x10B8C],
'Palace of Darkness - Prize': [0x155B8],
'Swamp Palace - Prize': [0x155B7],
'Thieves\' Town - Prize': [0x155C6],
'Skull Woods - Prize': [0x155BA, 0x155BB, 0x155BC, 0x155BD, 0x15608, 0x15609, 0x1560A, 0x1560B],
'Ice Palace - Prize': [0x155BF],
'Misery Mire - Prize': [0x155B9],
'Turtle Rock - Prize': [0x155C7, 0x155A7, 0x155AA, 0x155AB]}
+338 -360
View File
@@ -1,361 +1,339 @@
Hint description:
Hints will appear in the following ratios across the 15 telepathic tiles that have hints and the five storyteller locations:
4 hints for inconvenient entrances.
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
3 hints for inconvenient item locations.
5 hints for valuable items.
4 junk hints.
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following ratios will be used instead:
5 hints for inconvenient item locations.
8 hints for valuable items.
7 junk hints.
In the simple, restricted shuffles, these are the ratios:
2 hints for inconvenient entrances.
1 hint for an inconvenient dungeon entrance.
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
3 hints for inconvenient item locations.
5 hints for valuable items.
5 junk hints.
These hints will use the following format:
Entrance hints go "[Entrance on overworld] leads to [interior]".
Inconvenient item locations are a little more custom but amount to "[Location] has [item name]". The item name is literal and will specify which dungeon the dungeon specific items hail from (small key/big key/map/compass).
The valuable items are of the format "[item name] can be found [location]". The item name is again literal, and the location text is taken from Ganon's silver arrow hints. Note that the way it works is that every unique valuable item that exists is considered independently, and you won't get multiple hints for the EXACT same item (so you can only get one hint for Progressive Sword no matter how many swords exist in the seed, but if swords are not progressive, you could get hints for both Master Sword and Tempered Sword). More copies of an item existing does not increase the probability of getting a hint for that particular item (you are equally likely to get a hint for a Progressive Sword as for the Hammer). Unlike the IR, item names are never obfuscated by "something unique", and there is no special bias for hints for GT Big Key or Pegasus Boots.
Hint Locations:
Eastern Palace room before Big Chest
Desert Palace bonk torch room
Tower of Hera entrance room
Tower of Hera Big Chest room
Castle Tower after dark rooms
Palace of Darkness before Bow section
Swamp Palace entryway
Thieves' Town upstairs
Ice Palace entrance
Ice Palace after first drop
Ice Palace tall ice floor room
Misery Mire cutscene room
Turtle Rock entrance
Spectacle Rock cave
Spiky Hint cave
PoD Bdlg NPC
Near PoD Storyteller (bug near bomb wall)
Dark Sanctuary Storyteller (long room with tables)
Near Mire Storyteller (feather duster in winding cave)
SE DW Storyteller (owl in winding cave)
Inconvenient entrance list:
Skull Woods Final
Ice Palace
Misery Mire
Turtle Rock
Ganon's Tower
Mimic Ledge
SW DM Foothills Cave (mirror from upper Bumper ledge)
Hammer Pegs (near purple chest)
Super Bomb cracked wall
Inconvenient location list:
Swamp left (two chests)
Mire left (two chests)
Hera basement
Eastern Palace Big Key chest (protected by anti-fairies)
Thieves' Town Big Chest
Ice Palace Big Chest
Ganon's Tower Big Chest
Purple Chest
Spike Cave
Magic Bat
Sahasrahla (Green Pendant)
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following two locations are added to the inconvenient locations list:
Graveyard Cave
Mimic Cave
Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If key shuffle is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses.
While the exact verbage of location names and item names can be found in the source code, here's a copy for reference:
Overworld Entrance naming:
Links House: The hero's old residence
Turtle Rock: Turtle Rock Main
Misery Mire: Misery Mire
Ice Palace: Ice Palace
Skull Woods Final Section: The back of Skull Woods
Death Mountain Return Cave (West): The SW DM Foothills Cave
Mimic Cave: Mimic Ledge
Hammer Peg Cave: The rows of pegs
Pyramid Fairy: The crack on the pyramid
Eastern Palace: Eastern Palace
Elder House (East): Elder House
Elder House (West): Elder House
Two Brothers House (East): Eastern Quarreling Brothers' house
Old Man Cave (West): The lower DM entrance
Hyrule Castle Entrance (South): The ground level castle door
Thieves Town: Thieves' Town
Bumper Cave (Bottom): The lower Bumper Cave
Swamp Palace: Swamp Palace
Dark Death Mountain Ledge (West): The East dark DM connector ledge
Dark Death Mountain Ledge (East): The East dark DM connector ledge
Superbunny Cave (Top): The summit of dark DM cave
Superbunny Cave (Bottom): The base of east dark DM
Hookshot Cave: The rock on dark DM
Desert Palace Entrance (South): The book sealed passage
Tower of Hera: The Tower of Hera
Two Brothers House (West): The door near the race game
Old Man Cave (East): The SW-most cave on west DM
Old Man House (Bottom): A cave with a door on west DM
Old Man House (Top): The eastmost cave on west DM
Death Mountain Return Cave (East): The westmost cave on west DM
Spectacle Rock Cave Peak: The highest cave on west DM
Spectacle Rock Cave: The right ledge on west DM
Spectacle Rock Cave (Bottom): The left ledge on west DM
Paradox Cave (Bottom): The right paired cave on east DM
Paradox Cave (Middle): The southmost cave on east DM
Paradox Cave (Top): The east DM summit cave
Fairy Ascension Cave (Bottom): The east DM cave behind rocks
Fairy Ascension Cave (Top): The central ledge on east DM
Spiral Cave: The left ledge on east DM
Spiral Cave (Bottom): The SWmost cave on east DM
Palace of Darkness: Palace of Darkness
Hyrule Castle Entrance (West): The left castle door
Hyrule Castle Entrance (East): The right castle door
Agahnims Tower: The sealed castle door
Desert Palace Entrance (West): The westmost building in the desert
Desert Palace Entrance (North): The northmost cave in the desert
Blinds Hideout: Blind's old house
Lake Hylia Fairy: A cave NE of Lake Hylia
Light Hype Fairy: The cave south of your house
Desert Fairy: The cave near the desert
Chicken House: The chicken lady's house
Tavern North: A backdoor
Aginahs Cave: The open desert cave
Sahasrahlas Hut: The house near armos
Lake Hylia Shop: The cave NW Lake Hylia
Blacksmiths Hut: The old smithery
Sick Kids House: The central house in Kakariko
Lost Woods Gamble: A tree trunk door
Fortune Teller (Light): A building NE of Kakariko
Snitch Lady (East): A house guarded by a snitch
Snitch Lady (West): A house guarded by a snitch
Bush Covered House: A house with an uncut lawn
Tavern (Front): A building with a backdoor
Light World Bomb Hut: A Kakariko building with no door
Kakariko Shop: The old Kakariko shop
Mini Moldorm Cave: The cave south of Lake Hylia
Long Fairy Cave: The eastmost portal cave
Good Bee Cave: The open cave SE Lake Hylia
20 Rupee Cave: The rock SE Lake Hylia
50 Rupee Cave: The rock near the desert
Ice Rod Cave: The sealed cave SE Lake Hylia
Library: The old library
Potion Shop: The witch's building
Dam: The old dam
Lumberjack House: The lumberjack house
Lake Hylia Fortune Teller: The building NW Lake Hylia
Kakariko Gamble Game: The old Kakariko gambling den
Waterfall of Wishing: Going behind the waterfall
Capacity Upgrade: The cave on the island
Bonk Rock Cave: The rock pile near Sanctuary
Graveyard Cave: The graveyard ledge
Checkerboard Cave: The NE desert ledge
Cave 45: The ledge south of haunted grove
Kings Grave: The northeastmost grave
Bonk Fairy (Light): The rock pile near your home
Hookshot Fairy: A cave on east DM
Bonk Fairy (Dark): The rock pile near the old bomb shop
Dark Sanctuary Hint: The dark sanctuary cave
Dark Lake Hylia Fairy: The cave NE dark Lake Hylia
C-Shaped House: The NE house in Village of Outcasts
Big Bomb Shop: The old bomb shop
Dark Death Mountain Fairy: The SW cave on dark DM
Dark Lake Hylia Shop: The building NW dark Lake Hylia
Dark World Shop: The hammer sealed building
Red Shield Shop: The fenced in building
Mire Shed: The western hut in the mire
East Dark World Hint: The dark cave near the eastmost portal
Mire Hint: The cave east of the mire
Spike Cave: The ledge cave on west dark DM
Palace of Darkness Hint: The building south of Kiki
Dark Lake Hylia Ledge Spike Cave: The rock SE dark Lake Hylia
Dark Death Mountain Shop: The base of east dark DM
Dark Potion Shop: The building near the catfish
Archery Game: The old archery game
Dark Lumberjack Shop: The northmost Dark World building
Hype Cave: The cave south of the old bomb shop
Brewery: The Village of Outcasts building with no door
Dark Lake Hylia Ledge Hint: The open cave SE dark Lake Hylia
Chest Game: The westmost building in the Village of Outcasts
Mire Fairy: The eastern hut in the mire
Dark Lake Hylia Ledge Fairy: The sealed cave SE dark Lake Hylia
Fortune Teller (Dark): The building NE the Village of Outcasts
Sanctuary: Sanctuary
Lumberjack Tree Cave: The cave Behind Lumberjacks
Lost Woods Hideout Stump: The stump in Lost Woods
North Fairy Cave: The cave East of Graveyard
Bat Cave Cave: The cave in eastern Kakariko
Kakariko Well Cave: The cave in northern Kakariko
Hyrule Castle Secret Entrance Stairs: The tunnel near the castle
Skull Woods First Section Door: The southeastmost skull
Skull Woods Second Section Door (East): The central open skull
Skull Woods Second Section Door (West): The westmost open skull
Desert Palace Entrance (East): The eastern building in the desert
Turtle Rock Isolated Ledge Entrance: The isolated ledge on east dark DM
Bumper Cave (Top): The upper Bumper Cave
Hookshot Cave Back Entrance: The stairs on the floating island
Destination Entrance Naming:
Hyrule Castle: Hyrule Castle (all three entrances)
Eastern Palace: Eastern Palace
Desert Palace: Desert Palace (all four entrances, including final)
Tower of Hera: Tower of Hera
Palace of Darkness: Palace of Darkness
Swamp Palace: Swamp Palace
Skull Woods: Skull Woods (any entrance including final)
Thieves' Town: Thieves' Town
Ice Palace: Ice Palace
Misery Mire: Misery Mire
Turtle Rock: Turtle Rock (all four entrances)
Ganon's Tower: Ganon's Tower
Castle Tower: Agahnim's Tower
A connector: Paradox Cave, Spectacle Rock Cave, Hookshot Cave, Superbunny Cave, Spiral Cave, Old Man Fetch Cave, Old Man House, Elder House, Quarreling Brothers' House, Bumper Cave, DM Fairy Ascent Cave, DM Exit Cave
A bounty of five items: Mini-moldorm cave, Hype Cave, Blind's Hideout
Sahasrahla: Sahasrahla
A cave with two items: Mire hut, Waterfall Fairy, Pyramid Fairy
A fairy fountain: Any healer fairy cave, either bonk cave with four fairies, the "long fairy" cave
A common shop: Any shop that sells bombs by default
The rare shop: The shop that sells the Red Shield by default
The potion shop: Potion Shop
The bomb shop: Bomb Shop
A fortune teller: Any of the three fortune tellers
A house with a chest: Chicken Lady's house, C-House, Brewery
A cave with an item: Checkerboard cave, Hammer Pegs cave, Cave 45, Graveyard Ledge cave
A cave with a chest: Sanc Bonk Rock Cave, Cape Grave Cave, Ice Rod Cave, Aginah's Cave
The dam: Watergate
The sick kid: Sick Kid
The library: Library
Mimic Cave: Mimic Cave
Spike Cave: Spike Cave
A game of 16 chests: VoO chest game (for the item)
A storyteller: The four DW NPCs who charge 20 rupees for a hint as well as the PoD Bdlg guy who gives a free hint
A cave with some cash: 20 rupee cave, 50 rupee cave (both have thieves and some pots)
A game of chance: Gambling game (just for cash, no items)
A game of skill: Archery minigame
The queen of fairies: Capacity Upgrade Fairy
A drop's exit: Sanctuary, LW Thieves' Hideout, Kakariko Well, Magic Bat, Useless Fairy, Uncle Tunnel, Ganon drop exit
A restock room: The Kakariko bomb/arrow restock room
The tavern: The Kakariko tavern
The grass man: The Kakariko man with many beds
A cold bee: The "wrong side" of Ice Rod cave where you can get a Good Bee
Fairies deep in a cave: Hookshot Fairy
Location naming reference:
Mushroom: in the woods
Master Sword Pedestal: at the pedestal
Bottle Merchant: with a merchant
Stumpy: with tree boy
Flute Spot: underground
Digging Game: underground
Lake Hylia Island: on an island
Floating Island: on an island
Bumper Cave Ledge: on a ledge
Spectacle Rock: atop a rock
Maze Race: at the race
Desert Ledge: in the desert
Pyramid: on the pyramid
Catfish: with a catfish
Ether Tablet: at a monument
Bombos Tablet: at a monument
Hobo: with the hobo
Zora's Ledge: near Zora
King Zora: at a high price
Sunken Treasure: underwater
Floodgate Chest: in the dam
Blacksmith: with the smith
Purple Chest: from a box
Old Man: with the old man
Link's Uncle: with your uncle
Secret Passage: near your uncle
Kakariko Well (5 items): in a well
Lost Woods Hideout: near a thief
Lumberjack Tree: in a hole
Magic Bat: with the bat
Paradox Cave (7 items): in a cave with seven chests
Blind's Hideout (5 items): in a basement
Mini Moldorm Cave (5 items): near Moldorms
Hype Cave (4 back chests): near a bat-like man
Hype Cave - Generous Guy: with a bat-like man
Hookshot Cave (4 items): across pits
Sahasrahla's Hut (chests in back): near the elder
Sahasrahla: with the elder
Waterfall Fairy (2 items): near a fairy
Pyramid Fairy (2 items): near a fairy
Mire Shed (2 items): near sparks
Superbunny Cave (2 items): in a connection
Spiral Cave: in spiral cave
Kakariko Tavern: in the bar
Link's House: in your home
Sick Kid: with the sick
Library: near books
Potion Shop: near potions
Spike Cave: beyond spikes
Mimic Cave: in a cave of mimicry
Chest Game: as a game reward
Chicken House: near poultry
Aginah's Cave: with Aginah
Ice Rod Cave: in a frozen cave
Brewery: alone in a home
C-Shaped House: alone in a home
Spectacle Rock Cave: alone in a cave
King's Tomb: alone in a cave
Cave 45: alone in a cave
Graveyard Cave: alone in a cave
Checkerboard Cave: alone in a cave
Bonk Rock Cave: alone in a cave
Peg Cave: alone in a cave
Sanctuary: in Sanctuary
Hyrule Castle - Boomerang Chest: in Hyrule Castle
Hyrule Castle - Map Chest: in Hyrule Castle
Hyrule Castle - Zelda's Chest: in Hyrule Castle
Sewers - Dark Cross: in the sewers
Sewers - Secret Room (3 items): in the sewers
Eastern Palace - Boss: with the Armos
Eastern Palace (otherwise, 5 items): in Eastern Palace
Desert Palace - Boss: with Lanmolas
Desert Palace (otherwise, 5 items): in Desert Palace
Tower of Hera - Boss: with Moldorm
Tower of Hera (otherwise, 5 items): in Tower of Hera
Castle Tower (2 items): in Castle Tower
Palace of Darkness - Boss: with Helmasaur King
Palace of Darkness (otherwise, 13 items): in Palace of Darkness
Swamp Palace - Boss: with Arrghus
Swamp Palace (otherwise, 9 items): in Swamp Palace
Skull Woods - Bridge Room: near Mothula
Skull Woods - Boss: with Mothula
Skull Woods (otherwise, 6 items): in Skull Woods
Thieves' Town - Boss: with Blind
Thieves' Town (otherwise, 7 items): in Thieves' Town
Ice Palace - Boss: with Kholdstare
Ice Palace (otherwise, 7 items): in Ice Palace
Misery Mire - Boss: with Vitreous
Misery Mire (otherwise, 7 items): in Misery Mire
Turtle Rock - Boss: with Trinexx
Turtle Rock (otherwise, 11 items): in Turtle Rock
Ganons Tower (after climb, 4 items): atop Ganon's Tower
Hint description:
Hints will appear in the following ratios across the 15 telepathic tiles that have hints and the five storyteller locations:
4 hints for inconvenient entrances.
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
3 hints for inconvenient item locations.
5 hints for valuable items.
4 junk hints.
These hints will use the following format:
Entrance hints go "[Entrance on overworld] leads to [interior]".
Inconvenient item locations are a little more custom but amount to "[Location] has [item name]". The item name is literal and will specify which dungeon the dungeon specific items hail from (small key/big key/map/compass).
The valuable items are of the format "[item name] can be found [location]". The item name is again literal, and the location text is taken from Ganon's silver arrow hints. Note that the way it works is that every unique valuable item that exists is considered independently, and you won't get multiple hints for the EXACT same item (so you can only get one hint for Progressive Sword no matter how many swords exist in the seed, but if swords are not progressive, you could get hints for both Master Sword and Tempered Sword). More copies of an item existing does not increase the probability of getting a hint for that particular item (you are equally likely to get a hint for a Progressive Sword as for the Hammer). Unlike the IR, item names are never obfuscated by "something unique", and there is no special bias for hints for GT Big Key or Pegasus Boots.
Hint Locations:
Eastern Palace room before Big Chest
Desert Palace bonk torch room
Tower of Hera entrance room
Tower of Hera Big Chest room
Castle Tower after dark rooms
Palace of Darkness before Bow section
Swamp Palace entryway
Thieves' Town upstairs
Ice Palace entrance
Ice Palace after first drop
Ice Palace tall ice floor room
Misery Mire cutscene room
Turtle Rock entrance
Spectacle Rock cave
Spiky Hint cave
PoD Bdlg NPC
Near PoD Storyteller (bug near bomb wall)
Dark Sanctuary Storyteller (long room with tables)
Near Mire Storyteller (feather duster in winding cave)
SE DW Storyteller (owl in winding cave)
Inconvenient entrance list:
Skull Woods Final
Ice Palace
Misery Mire
Turtle Rock
Ganon's Tower
Mimic Ledge
SW DM Foothills Cave (mirror from upper Bumper ledge)
Hammer Pegs (near purple chest)
Super Bomb cracked wall
Inconvenient location list:
Swamp left (two chests)
Mire left (two chests)
Hera basement
Eastern Palace Big Key chest (protected by anti-fairies)
Thieves' Town Big Chest
Ice Palace Big Chest
Ganon's Tower Big Chest
Purple Chest
Spike Cave
Magic Bat
Sahasrahla (Green Pendant)
Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If keysanity is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses.
While the exact verbage of location names and item names can be found in the source code, here's a copy for reference:
Overworld Entrance naming:
Turtle Rock: Turtle Rock Main
Misery Mire: Misery Mire
Ice Palace: Ice Palace
Skull Woods Final Section: The back of Skull Woods
Death Mountain Return Cave (West): The SW DM Foothills Cave
Mimic Cave: Mimic Ledge
Dark World Hammer Peg Cave: The rows of pegs
Pyramid Fairy: The crack on the pyramid
Eastern Palace: Eastern Palace
Elder House (East): Elder House
Elder House (West): Elder House
Two Brothers House (East): Eastern Quarreling Brothers' house
Old Man Cave (West): The lower DM entrance
Hyrule Castle Entrance (South): The ground level castle door
Thieves Town: Thieves' Town
Bumper Cave (Bottom): The lower Bumper Cave
Swamp Palace: Swamp Palace
Dark Death Mountain Ledge (West): The East dark DM connector ledge
Dark Death Mountain Ledge (East): The East dark DM connector ledge
Superbunny Cave (Top): The summit of dark DM cave
Superbunny Cave (Bottom): The base of east dark DM
Hookshot Cave: The rock on dark DM
Desert Palace Entrance (South): The book sealed passage
Tower of Hera: The Tower of Hera
Two Brothers House (West): The door near the race game
Old Man Cave (East): The SW-most cave on west DM
Old Man House (Bottom): A cave with a door on west DM
Old Man House (Top): The eastmost cave on west DM
Death Mountain Return Cave (East): The westmost cave on west DM
Spectacle Rock Cave Peak: The highest cave on west DM
Spectacle Rock Cave: The right ledge on west DM
Spectacle Rock Cave (Bottom): The left ledge on west DM
Paradox Cave (Bottom): The southmost cave on east DM
Paradox Cave (Middle): The right paired cave on east DM
Paradox Cave (Top): The east DM summit cave
Fairy Ascension Cave (Bottom): The east DM cave behind rocks
Fairy Ascension Cave (Top): The central ledge on east DM
Spiral Cave: The left ledge on east DM
Spiral Cave (Bottom): The SWmost cave on east DM
Palace of Darkness: Palace of Darkness
Hyrule Castle Entrance (West): The left castle door
Hyrule Castle Entrance (East): The right castle door
Agahnims Tower: The sealed castle door
Desert Palace Entrance (West): The westmost building in the desert
Desert Palace Entrance (North): The northmost cave in the desert
Blinds Hideout: Blind's old house
Lake Hylia Fairy: A cave NE of Lake Hylia
Light Hype Fairy: The cave south of your house
Desert Fairy: The cave near the desert
Chicken House: The chicken lady's house
Aginahs Cave: The open desert cave
Sahasrahlas Hut: The house near armos
Cave Shop (Lake Hylia): The cave NW Lake Hylia
Blacksmiths Hut: The old smithery
Sick Kids House: The central house in Kakariko
Lost Woods Gamble: A tree trunk door
Fortune Teller (Light): A building NE of Kakariko
Snitch Lady (East): A house guarded by a snitch
Snitch Lady (West): A house guarded by a snitch
Bush Covered House: A house with an uncut lawn
Tavern (Front): A building with a backdoor
Light World Bomb Hut: A Kakariko building with no door
Kakariko Shop: The old Kakariko shop
Mini Moldorm Cave: The cave south of Lake Hylia
Long Fairy Cave: The eastmost portal cave
Good Bee Cave: The open cave SE Lake Hylia
20 Rupee Cave: The rock SE Lake Hylia
50 Rupee Cave: The rock near the desert
Ice Rod Cave: The sealed cave SE Lake Hylia
Library: The old library
Potion Shop: The witch's building
Dam: The old dam
Lumberjack House: The lumberjack house
Lake Hylia Fortune Teller: The building NW Lake Hylia
Kakariko Gamble Game: The old Kakariko gambling den
Waterfall of Wishing: Going behind the waterfall
Capacity Upgrade: The cave on the island
Bonk Rock Cave: The rock pile near Sanctuary
Graveyard Cave: The graveyard ledge
Checkerboard Cave: The NE desert ledge
Cave 45: The ledge south of haunted grove
Kings Grave: The northeastmost grave
Bonk Fairy (Light): The rock pile near your home
Hookshot Fairy: A cave on east DM
Bonk Fairy (Dark): The rock pile near the old bomb shop
Dark Sanctuary Hint: The dark sanctuary cave
Dark Lake Hylia Fairy: The cave NE dark Lake Hylia
C-Shaped House: The NE house in Village of Outcasts
Big Bomb Shop: The old bomb shop
Dark Death Mountain Fairy: The SW cave on dark DM
Dark Lake Hylia Shop: The building NW dark Lake Hylia
Dark World Shop: The hammer sealed building
Red Shield Shop: The fenced in building
Mire Shed: The western hut in the mire
East Dark World Hint: The dark cave near the eastmost portal
Dark Desert Hint: The cave east of the mire
Spike Cave: The ledge cave on west dark DM
Palace of Darkness Hint: The building south of Kiki
Dark Lake Hylia Ledge Spike Cave: The rock SE dark Lake Hylia
Cave Shop (Dark Death Mountain): The base of east dark DM
Dark World Potion Shop: The building near the catfish
Archery Game: The old archery game
Dark World Lumberjack Shop: The northmost Dark World building
Hype Cave: The cave south of the old bomb shop
Brewery: The Village of Outcasts building with no door
Dark Lake Hylia Ledge Hint: The open cave SE dark Lake Hylia
Chest Game: The westmost building in the Village of Outcasts
Dark Desert Fairy: The eastern hut in the mire
Dark Lake Hylia Ledge Fairy: The sealed cave SE dark Lake Hylia
Fortune Teller (Dark): The building NE the Village of Outcasts
Sanctuary: Sanctuary
Lumberjack Tree Cave: The cave Behind Lumberjacks
Lost Woods Hideout Stump: The stump in Lost Woods
North Fairy Cave: The cave East of Graveyard
Bat Cave Cave: The cave in eastern Kakariko
Kakariko Well Cave: The cave in northern Kakariko
Hyrule Castle Secret Entrance Stairs: The tunnel near the castle
Skull Woods First Section Door: The southeastmost skull
Skull Woods Second Section Door (East): The central open skull
Skull Woods Second Section Door (West): The westmost open skull
Desert Palace Entrance (East): The eastern building in the desert
Turtle Rock Isolated Ledge Entrance: The isolated ledge on east dark DM
Bumper Cave (Top): The upper Bumper Cave
Hookshot Cave Back Entrance: The stairs on the floating island
Destination Entrance Naming:
Hyrule Castle: Hyrule Castle (all three entrances)
Eastern Palace: Eastern Palace
Desert Palace: Desert Palace (all four entrances, including final)
Tower of Hera: Tower of Hera
Palace of Darkness: Palace of Darkness
Swamp Palace: Swamp Palace
Skull Woods: Skull Woods (any entrance including final)
Thieves' Town: Thieves' Town
Ice Palace: Ice Palace
Misery Mire: Misery Mire
Turtle Rock: Turtle Rock (all four entrances)
Ganon's Tower: Ganon's Tower
Castle Tower: Agahnim's Tower
A connector: Paradox Cave, Spectacle Rock Cave, Hookshot Cave, Superbunny Cave, Spiral Cave, Old Man Fetch Cave, Old Man House, Elder House, Quarreling Brothers' House, Bumper Cave, DM Fairy Ascent Cave, DM Exit Cave
A bounty of five items: Mini-moldorm cave, Hype Cave, Blind's Hideout
Sahasrahla: Sahasrahla
A cave with two items: Mire hut, Waterfall Fairy, Pyramid Fairy
A fairy fountain: Any healer fairy cave, either bonk cave with four fairies, the "long fairy" cave
A common shop: Any shop that sells bombs by default
The rare shop: The shop that sells the Red Shield by default
The potion shop: Potion Shop
The bomb shop: Bomb Shop
A fortune teller: Any of the three fortune tellers
A house with a chest: Chicken Lady's house, C-House, Brewery
A cave with an item: Checkerboard cave, Hammer Pegs cave, Cave 45, Graveyard Ledge cave
A cave with a chest: Sanc Bonk Rock Cave, Cape Grave Cave, Ice Rod Cave, Aginah's Cave
The dam: Watergate
The sick kid: Sick Kid
The library: Library
Mimic Cave: Mimic Cave
Spike Cave: Spike Cave
A game of 16 chests: VoO chest game (for the item)
A storyteller: The four DW NPCs who charge 20 rupees for a hint as well as the PoD Bdlg guy who gives a free hint
A cave with some cash: 20 rupee cave, 50 rupee cave (both have thieves and some pots)
A game of chance: Gambling game (just for cash, no items)
A game of skill: Archery minigame
The queen of fairies: Capacity Upgrade Fairy
A drop's exit: Sanctuary, LW Thieves' Hideout, Kakariko Well, Magic Bat, Useless Fairy, Uncle Tunnel, Ganon drop exit
A restock room: The Kakariko bomb/arrow restock room
The tavern: The Kakariko tavern
The grass man: The Kakariko man with many beds
A cold bee: The "wrong side" of Ice Rod cave where you can get a Good Bee
Fairies deep in a cave: Hookshot Fairy
Location naming reference:
Mushroom: in the woods
Master Sword Pedestal: at the pedestal
Bottle Merchant: with a merchant
Stumpy: with tree boy
Flute Spot: underground
Digging Game: underground
Lake Hylia Island: on an island
Floating Island: on an island
Bumper Cave Ledge: on a ledge
Spectacle Rock: atop a rock
Maze Race: at the race
Desert Ledge: in the desert
Pyramid: on the pyramid
Catfish: with a catfish
Ether Tablet: at a monument
Bombos Tablet: at a monument
Hobo: with the hobo
Zora's Ledge: near Zora
King Zora: at a high price
Sunken Treasure: underwater
Floodgate Chest: in the dam
Blacksmith: with the smith
Purple Chest: from a box
Old Man: with the old man
Link's Uncle: with your uncle
Secret Passage: near your uncle
Kakariko Well (5 items): in a well
Lost Woods Hideout: near a thief
Lumberjack Tree: in a hole
Magic Bat: with the bat
Paradox Cave (7 items): in a cave with seven chests
Blind's Hideout (5 items): in a basement
Mini Moldorm Cave (5 items): near Moldorms
Hype Cave (4 back chests): near a bat-like man
Hype Cave - Generous Guy: with a bat-like man
Hookshot Cave (4 items): across pits
Sahasrahla's Hut (chests in back): near the elder
Sahasrahla: with the elder
Waterfall Fairy (2 items): near a fairy
Pyramid Fairy (2 items): near a fairy
Mire Shed (2 items): near sparks
Superbunny Cave (2 items): in a connection
Spiral Cave: in spiral cave
Kakariko Tavern: in the bar
Link's House: in your home
Sick Kid: with the sick
Library: near books
Potion Shop: near potions
Spike Cave: beyond spikes
Mimic Cave: in a cave of mimicry
Chest Game: as a prize
Chicken House: near poultry
Aginah's Cave: with Aginah
Ice Rod Cave: in a frozen cave
Brewery: alone in a home
C-Shaped House: alone in a home
Spectacle Rock Cave: alone in a cave
King's Tomb: alone in a cave
Cave 45: alone in a cave
Graveyard Cave: alone in a cave
Checkerboard Cave: alone in a cave
Bonk Rock Cave: alone in a cave
Peg Cave: alone in a cave
Sanctuary: in Sanctuary
Hyrule Castle - Boomerang Chest: in Hyrule Castle
Hyrule Castle - Map Chest: in Hyrule Castle
Hyrule Castle - Zelda's Chest: in Hyrule Castle
Sewers - Dark Cross: in the sewers
Sewers - Secret Room (3 items): in the sewers
Eastern Palace - Boss: with the Armos
Eastern Palace (otherwise, 5 items): in Eastern Palace
Desert Palace - Boss: with Lanmolas
Desert Palace (otherwise, 5 items): in Desert Palace
Tower of Hera - Boss: with Moldorm
Tower of Hera (otherwise, 5 items): in Tower of Hera
Castle Tower (2 items): in Castle Tower
Palace of Darkness - Boss: with Helmasaur King
Palace of Darkness (otherwise, 13 items): in Palace of Darkness
Swamp Palace - Boss: with Arrghus
Swamp Palace (otherwise, 9 items): in Swamp Palace
Skull Woods - Bridge Room: near Mothula
Skull Woods - Boss: with Mothula
Skull Woods (otherwise, 6 items): in Skull Woods
Thieves' Town - Boss: with Blind
Thieves' Town (otherwise, 7 items): in Thieves' Town
Ice Palace - Boss: with Kholdstare
Ice Palace (otherwise, 7 items): in Ice Palace
Misery Mire - Boss: with Vitreous
Misery Mire (otherwise, 7 items): in Misery Mire
Turtle Rock - Boss: with Trinexx
Turtle Rock (otherwise, 11 items): in Turtle Rock
Ganons Tower (after climb, 4 items): atop Ganon's Tower
Ganon's Tower (otherwise, 23 items): in Ganon's Tower
+264
View File
@@ -0,0 +1,264 @@
#!/usr/bin/env python3
import argparse
import os
import logging
import RaceRandom as random
import textwrap
import sys
from Main import main
from Utils import is_bundled, close_console, output_path
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def _get_help_string(self, action):
return textwrap.dedent(action.help)
def start():
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
parser.add_argument('--json_spoiler', help='Output a JSON Spoiler File', action='store_true')
parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'],
help='''\
Select Enforcement of Item Requirements. (default: %(default)s)
No Glitches:
Minor Glitches: May require Fake Flippers, Bunny Revival
and Dark Room Navigation.
No Logic: Distribute items without regard for
item requirements.
''')
parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'inverted'],
help='''\
Select game mode. (default: %(default)s)
Open: World starts with Zelda rescued.
Standard: Fixes Hyrule Castle Secret Entrance and Front Door
but may lead to weird rain state issues if you exit
through the Hyrule Castle side exits before rescuing
Zelda in a full shuffle.
Inverted: Starting locations are Dark Sanctuary in West Dark
World or at Link's House, which is shuffled freely.
Requires the moon pearl to be Link in the Light World
instead of a bunny.
''')
parser.add_argument('--swords', default='random', const='random', nargs='?', choices= ['random', 'assured', 'swordless', 'vanilla'],
help='''\
Select sword placement. (default: %(default)s)
Random: All swords placed randomly.
Assured: Start game with a sword already.
Swordless: No swords. Curtains in Skull Woods and Agahnim\'s
Tower are removed, Agahnim\'s Tower barrier can be
destroyed with hammer. Misery Mire and Turtle Rock
can be opened without a sword. Hammer damages Ganon.
Ether and Bombos Tablet can be activated with Hammer
(and Book). Bombos pads have been added in Ice
Palace, to allow for an alternative to firerod.
Vanilla: Swords are in vanilla locations.
''')
parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'ganonhunt', 'crystals', 'all_items', 'completionist'],
help='''\
Select completion goal. (default: %(default)s)
Ganon: Collect all crystals, beat Agahnim 2 then
defeat Ganon.
Crystals: Collect all crystals then defeat Ganon.
Pedestal: Places the Triforce at the Master Sword Pedestal.
All Dungeons: Collect all crystals, pendants, beat both
Agahnim fights and then defeat Ganon.
All Items: Requires collecting 216 items to defeat Ganon.
Completionist: Same as above, plus All Dungeons
''')
parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
help='''\
Select game difficulty. Affects available itempool. (default: %(default)s)
Normal: Normal difficulty.
Hard: A harder setting with less equipment and reduced health.
Expert: A harder yet setting with minimum equipment and health.
''')
parser.add_argument('--item_functionality', default='normal', const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
help='''\
Select limits on item functionality to increase difficulty. (default: %(default)s)
Normal: Normal functionality.
Hard: Reduced functionality.
Expert: Greatly reduced functionality.
''')
parser.add_argument('--timer', default='none', const='none', nargs='?', choices=['none', 'ohko'],
help='''\
Select One-Hit KO (OHKO) game setting. (default: %(default)s)
None: No timer.
OHKO: Link will always die in one hit.
''')
parser.add_argument('--progressive', default='on', const='on', nargs='?', choices=['on', 'off', 'random'],
help='''\
Select progressive bow setting. Affects available itempool. (default: %(default)s)
On: The first bow you find will give you access
to the bow item. The second will give you
Silver Arrows.
Off: Bow and Silver Arrows are separate items.
Random: Bows will randomly be progressive or not.
''')
parser.add_argument('--shuffle', default='vanilla', const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'dungeonsfull', 'dungeonssimple'],
help='''\
Select Entrance Shuffling Algorithm. (default: %(default)s)
Full: Mix cave and dungeon entrances freely while limiting
multi-entrance caves to one world.
Simple: Shuffle Dungeon Entrances/Exits between each other
and keep all 4-entrance dungeons confined to one
location. All caves outside of death mountain are
shuffled in pairs and matched by original type.
Restricted: Use Dungeons shuffling from Simple but freely
connect remaining entrances.
Crossed: Mix cave and dungeon entrances freely while allowing
caves to cross between worlds.
Vanilla: All entrances are in the same locations they were
in the base game.
The dungeon variants only mix up dungeons and keep the rest of
the overworld vanilla.
''')
parser.add_argument('--crystals_ganon', default='7', const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
help='''\
How many crystals are needed to defeat ganon. Any other
requirements for ganon for the selected goal still apply.
This setting does not apply when the all dungeons goal is
selected. (default: %(default)s)
Random: Picks a random value between 0 and 7 (inclusive).
0-7: Number of crystals needed
''')
parser.add_argument('--crystals_gt', default='7', const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
help='''\
How many crystals are needed to open GT. For inverted mode
this applies to the castle tower door instead. (default: %(default)s)
Random: Picks a random value between 0 and 7 (inclusive).
0-7: Number of crystals needed
''')
parser.add_argument('--rom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
parser.add_argument('--count', help='''\
Use to batch generate multiple seeds with same settings.
If --seed is provided, it will be used for the first seed, then
used to derive the next seed (i.e. generating 10 seeds with
--seed given will produce the same 10 (different) roms each
time).
''', type=int)
parser.add_argument('--fastmenu', default='normal', const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
help='''\
Select the rate at which the menu opens and closes.
(default: %(default)s)
''')
parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true')
parser.add_argument('--pseudoboots', help='Enable pseduoboots (can dash but no logical checks).', action='store_true')
parser.add_argument('--disablemusic', help='Disables game music.', action='store_true')
parser.add_argument('--keysanity', help='''\
Keys (and other dungeon items) are no longer restricted to
their dungeons, but can be anywhere
''', action='store_true')
parser.add_argument('--retro', help='''\
Keys are universal, shooting arrows costs rupees,
and a few other little things make this more like Zelda-1.
''', action='store_true')
parser.add_argument('--nodungeonitems', help='''\
Remove Maps and Compasses from Itempool, replacing them by
empty slots.
''', action='store_true')
parser.add_argument('--accessibility', default='items', const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
Select Item/Location Accessibility. (default: %(default)s)
Items: You can reach all unique inventory items. No guarantees about
reaching all locations or all keys.
Locations: You will be able to reach every location in the game.
None: You will be able to reach enough locations to beat the game.
''')
parser.add_argument('--hints', help='''\
Make telepathic tiles and storytellers give helpful hints.
''', action='store_true')
# included for backwards compatibility
parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=True)
parser.add_argument('--no-shuffleganon', help='''\
If set, the Pyramid Hole and Ganon's Tower are not
included entrance shuffle pool.
''', action='store_false', dest='shuffleganon')
parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'],
help='''\
Select the rate at which the heart beep sound is played at
low health. (default: %(default)s)
''')
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
help='Select the color of Link\'s heart meter. (default: %(default)s)')
parser.add_argument('--sprite', help='''\
Path to a sprite sheet to use for Link. Needs to be in
binary format and have a length of 0x7000 (28672) bytes,
or 0x7078 (28792) bytes including palette data.
Alternatively, can be a ALttP Rom patched with a Link
sprite that will be extracted.
''')
parser.add_argument('--huditemcounter', default=False, help='''\
Displays a (number of items collected) / (total items available) counter
on the HUD.
''', action='store_true')
parser.add_argument('--nofastrom', default=False, help='''\
Displays a (number of items collected) / (total items available) counter
on the HUD.
''', action='store_true')
parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true')
parser.add_argument('--gui', help='Launch the GUI', action='store_true')
parser.add_argument('--jsonout', action='store_true', help='''\
Output .json patch to stdout instead of a patched rom. Used
for VT site integration, do not use otherwise.
''')
parser.add_argument('--skip_playthrough', action='store_true', default=False)
parser.add_argument('--enemizercli', default='')
parser.add_argument('--shufflebosses', default='none', choices=['none', 'basic', 'normal', 'chaos'])
parser.add_argument('--shuffleenemies', default=False, action='store_true')
parser.add_argument('--enemy_health', default='default', choices=['default', 'easy', 'normal', 'hard', 'expert'])
parser.add_argument('--enemy_damage', default='default', choices=['default', 'shuffled', 'chaos'])
parser.add_argument('--shufflepalette', default=False, action='store_true')
parser.add_argument('--shufflepots', default=False, action='store_true')
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
parser.add_argument('--securerandom', default=False, action='store_true')
parser.add_argument('--outputpath')
parser.add_argument('--outputname')
args = parser.parse_args()
if args.outputpath and os.path.isdir(args.outputpath):
output_path.cached_path = args.outputpath
if is_bundled() and len(sys.argv) == 1:
# for the bundled builds, if we have no arguments, the user
# probably wants the gui. Users of the bundled build who want the command line
# interface shouuld specify at least one option, possibly setting a value to a
# default if they like all the defaults
from Gui import guiMain
close_console()
guiMain()
sys.exit(0)
# ToDo: Validate files further than mere existance
if not args.jsonout and not os.path.isfile(args.rom):
input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom)
sys.exit(1)
if args.sprite is not None and not os.path.isfile(args.sprite):
if not args.jsonout:
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
sys.exit(1)
else:
raise IOError('Cannot find sprite file at %s' % args.sprite)
# set up logger
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel)
if args.gui:
from Gui import guiMain
guiMain(args)
elif args.count is not None:
seed = args.seed
for _ in range(args.count):
main(seed=seed, args=args)
seed = random.randint(0, 999999999)
else:
main(seed=args.seed, args=args)
if __name__ == '__main__':
start()
+2440
View File
File diff suppressed because it is too large Load Diff
+288 -1184
View File
File diff suppressed because it is too large Load Diff
+1411 -182
View File
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -1,15 +1,13 @@
import os
import queue
import threading
import tkinter as tk
from Utils import local_path
def set_icon(window):
er16 = tk.PhotoImage(file=local_path(os.path.join("data","ER16.gif")))
er32 = tk.PhotoImage(file=local_path(os.path.join("data","ER32.gif")))
er48 = tk.PhotoImage(file=local_path(os.path.join("data","ER48.gif")))
er16 = tk.PhotoImage(file=local_path('data/ER16.gif'))
er32 = tk.PhotoImage(file=local_path('data/ER32.gif'))
er48 = tk.PhotoImage(file=local_path('data/ER32.gif'))
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access
# Although tkinter is intended to be thread safe, there are many reports of issues
+17 -72
View File
@@ -1,5 +1,4 @@
from dataclasses import dataclass, field
from typing import List
from BaseClasses import CollectionState
from Utils import count_set_bits
@@ -19,7 +18,7 @@ def _new_default_sram():
@dataclass
class InitialSram:
_initial_sram_bytes: List[int] = field(default_factory=_new_default_sram)
_initial_sram_bytes: list[int] = field(default_factory=_new_default_sram)
def _set_value(self, idx: int, val:int):
if idx > SRAM_SIZE:
@@ -53,9 +52,6 @@ class InitialSram:
def pre_open_pyramid_hole(self):
self._or_value(OVERWORLD_DATA+0x5B, 0x20)
def pre_set_overworld_flag(self, owid, bitmask):
self._or_value(OVERWORLD_DATA+owid, bitmask)
def pre_open_tr_bomb_doors(self):
self._or_value(ROOM_DATA+0x47, 0x80)
self._or_value(ROOM_DATA+0x01AB, 0x80)
@@ -65,23 +61,16 @@ class InitialSram:
equip[0x36C] = 0x18
equip[0x36D] = 0x18
equip[0x379] = 0x68
starting_bomb_cap_upgrades = 10 if not world.bombbag[player] else 0
starting_arrow_cap_upgrades = 30
starting_bombs = 0
starting_arrows = 0
starting_magic = 0
if world.flute_mode[player] == 'pseudo':
self._initial_sram_bytes[0x3C2] = 0xFF
equip[0x34C] = 0x03
equip[0x38C] = 0x01
starting_bomb_cap_upgrades = 0
starting_arrow_cap_upgrades = 0
startingstate = CollectionState(world)
if startingstate.has('Bow', player):
equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
if not world.bow_mode[player].startswith('retro'):
if not world.retro[player]:
equip[0x38E] |= 0x80
if startingstate.has('Silver Arrows', player):
equip[0x38E] |= 0x40
@@ -114,43 +103,10 @@ class InitialSram:
if startingstate.has('Magic Upgrade (1/4)', player):
equip[0x37B] = 2
starting_magic = 0x80
equip[0x36E] = 0x80
elif startingstate.has('Magic Upgrade (1/2)', player):
equip[0x37B] = 1
starting_magic = 0x80
if world.mode[player] == 'standard' and world.logic[player] not in ['noglitches', 'minorglitches']:
if startingstate.has('Ocarina (Activated)', player):
self.pre_set_overworld_flag(0x18, 0x20)
if startingstate.has('Return Old Man', player):
self._initial_sram_bytes[0x410] |= 0x01
if startingstate.has('Beat Agahnim 1', player):
self.pre_open_lumberjack()
if world.mode[player] == 'standard':
self.set_progress_indicator(0x80)
else:
self.set_progress_indicator(0x03)
if startingstate.has('Zelda Herself', player):
self._initial_sram_bytes[0x3CC] = 0x01
elif startingstate.has('Escort Old Man', player):
self._initial_sram_bytes[0x3CC] = 0x04
elif startingstate.has('Maiden Rescued', player):
self._initial_sram_bytes[0x3CC] = 0x06
elif startingstate.has('Get Frog', player):
self._initial_sram_bytes[0x3CC] = 0x07
elif startingstate.has('Sign Vandalized', player):
self._initial_sram_bytes[0x3CC] = 0x09
elif startingstate.has('Pick Up Kiki', player):
self._initial_sram_bytes[0x3CC] = 0x0A
elif startingstate.has('Pick Up Purple Chest', player):
self._initial_sram_bytes[0x3CC] = 0x0C
elif startingstate.has('Pick Up Big Bomb', player):
self._initial_sram_bytes[0x3CC] = 0x0D
if self._initial_sram_bytes[0x3CC] > 0x01 and world.mode[player] == 'standard':
self._initial_sram_bytes[0x3D3] = 0x80
equip[0x36E] = 0x80
for item in world.precollected_items:
if item.player != player:
@@ -161,10 +117,7 @@ class InitialSram:
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
'Red Mail', 'Blue Mail', 'Progressive Armor',
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)',
'Return Old Man', 'Beat Agahnim 1', 'Zelda Herself', 'Escort Old Man',
'Maiden Rescued', 'Get Frog', 'Sign Vandalized', 'Pick Up Kiki',
'Pick Up Purple Chest', 'Pick Up Big Bomb']:
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']:
continue
set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2),
@@ -186,9 +139,8 @@ class InitialSram:
'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01),
'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08),
'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)}
set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02), 'Pegasus Boots': (0x355, 1, 0x379, 0x04),
'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 2, 0x38C, 0x02),
'Ocarina (Activated)': (0x34C, 3, 0x38C, 0x01),
set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04),
'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01),
'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10),
'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)}
keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F],
@@ -207,7 +159,6 @@ class InitialSram:
arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10}
bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10}
arrows = {'Single Arrow': 1, 'Arrows (10)': 10}
magic = {'Big Magic': 0x80, 'Small Magic': 0x10}
if item.name in set_table:
equip[set_table[item.name][0]] = set_table[item.name][1]
@@ -231,33 +182,27 @@ class InitialSram:
elif item.name in arrow_caps:
starting_arrow_cap_upgrades += arrow_caps[item.name]
elif item.name in bombs:
starting_bombs += bombs[item.name]
equip[0x343] += bombs[item.name]
elif item.name in arrows:
if world.bow_mode[player].startswith('retro'):
if world.retro[player]:
equip[0x38E] |= 0x80
starting_arrows = 1
equip[0x377] = 1
else:
starting_arrows += arrows[item.name]
equip[0x377] += arrows[item.name]
elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']:
if item.name == 'Piece of Heart':
equip[0x36B] = (equip[0x36B] + 1) % 4
if item.name != 'Piece of Heart' or equip[0x36B] == 0:
equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
elif item.name in magic:
starting_magic += magic[item.name]
else:
raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
equip[0x36E] = min(starting_magic, 0x80)
equip[0x370] = min(starting_bomb_cap_upgrades, 50)
equip[0x371] = min(starting_arrow_cap_upgrades, 70)
equip[0x343] = min(starting_bombs, equip[0x370])
equip[0x377] = min(starting_arrows, equip[0x371])
if not startingstate.has('Magic Mirror', player) and (world.doorShuffle[player] != 'vanilla' or world.mirrorscroll[player]):
equip[0x353] = 1
equip[0x343] = min(equip[0x343], 10)
equip[0x377] = min(equip[0x377], 30)
# Assertion and copy equip to initial_sram_bytes
assert equip[:0x340] == [0] * 0x340
self._initial_sram_bytes[0x340:0x38F] = equip[0x340:0x38F]
@@ -269,7 +214,7 @@ class InitialSram:
self._initial_sram_bytes[0x422] = self._initial_sram_bytes[0x35A]
self._initial_sram_bytes[0x46E] = self._initial_sram_bytes[0x35B]
if world.swords[player] == "swordless":
if world.swords == "swordless":
self._initial_sram_bytes[0x359] = 0xFF
self._initial_sram_bytes[0x417] = 0x00
+644
View File
@@ -0,0 +1,644 @@
import collections
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
def create_inverted_regions(world, player):
world.regions += [
create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Bombos Tablet'],
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump',
'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier', 'Lake Hylia Island Pier', 'Lake Hylia Warp',
'Bonk Rock Cave', 'Library', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow',
'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Light World River Drop',
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)',
'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game',
'East Dark World Mirror Spot', 'West Dark World Mirror Spot', 'South Dark World Mirror Spot', 'Cave 45', 'Checkerboard Cave', 'Mire Mirror Spot', 'Hammer Peg Area Mirror Spot',
'Shopping Mall Mirror Spot', 'Skull Woods Mirror Spot', 'Inverted Pyramid Entrance','Hyrule Castle Entrance (South)', 'Secret Passage Outer Bushes', 'Bush Covered Lawn Outer Bushes',
'Potion Shop Outer Bushes', 'Graveyard Cave Outer Bushes', 'Bomb Hut Outer Bushes']),
create_lw_region(player, 'Bush Covered Lawn', None, ['Bush Covered House', 'Bush Covered Lawn Inner Bushes', 'Bush Covered Lawn Mirror Spot']),
create_lw_region(player, 'Bomb Hut Area', None, ['Light World Bomb Hut', 'Bomb Hut Inner Bushes', 'Bomb Hut Mirror Spot']),
create_lw_region(player, 'Hyrule Castle Secret Entrance Area', None, ['Hyrule Castle Secret Entrance Stairs', 'Secret Passage Inner Bushes']),
create_lw_region(player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop', 'Bumper Cave Entrance Mirror Spot']),
create_lw_region(player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Mirror Spot']),
create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top",
"Blind\'s Hideout - Left",
"Blind\'s Hideout - Right",
"Blind\'s Hideout - Far Left",
"Blind\'s Hideout - Far Right"]),
create_lw_region(player, 'Northeast Light World', None, ['Zoras River', 'Waterfall Stoop', 'Potion Shop Outer Rock', 'Northeast Dark World Mirror Spot', 'Northeast Light World Warp']),
create_lw_region(player, 'Waterfall Stoop', None, ['Northeast Light World', 'Waterfall of Wishing']),
create_lw_region(player, 'Potion Shop Area', None, ['Potion Shop', 'Potion Shop Inner Bushes', 'Potion Shop Inner Rock', 'Potion Shop Mirror Spot', 'Potion Shop River Drop']),
create_lw_region(player, 'Graveyard Cave Area', None, ['Graveyard Cave', 'Graveyard Cave Inner Bushes', 'Graveyard Cave Mirror Spot']),
create_lw_region(player, 'River', None, ['Light World Pier', 'Potion Shop Pier']),
create_cave_region(player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
create_lw_region(player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']),
create_cave_region(player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
create_lw_region(player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
create_cave_region(player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
create_cave_region(player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
create_cave_region(player, 'Dam', 'the dam', ['Floodgate', 'Floodgate Chest']),
create_cave_region(player, 'Inverted Links House', 'your house', ['Link\'s House'], ['Inverted Links House Exit']),
create_cave_region(player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']),
create_cave_region(player, 'Tavern', 'the tavern', ['Kakariko Tavern']),
create_cave_region(player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']),
create_cave_region(player, 'Snitch Lady (East)', 'a boring house'),
create_cave_region(player, 'Snitch Lady (West)', 'a boring house'),
create_cave_region(player, 'Bush Covered House', 'the grass man'),
create_cave_region(player, 'Tavern (Front)', 'the tavern'),
create_cave_region(player, 'Light World Bomb Hut', 'a restock room'),
create_cave_region(player, 'Kakariko Shop', 'a common shop'),
create_cave_region(player, 'Fortune Teller (Light)', 'a fortune teller'),
create_cave_region(player, 'Lake Hylia Fortune Teller', 'a fortune teller'),
create_cave_region(player, 'Lumberjack House', 'a boring house'),
create_cave_region(player, 'Bonk Fairy (Light)', 'a fairy fountain'),
create_cave_region(player, 'Bonk Fairy (Dark)', 'a fairy fountain'),
create_cave_region(player, 'Lake Hylia Healer Fairy', 'a fairy fountain'),
create_cave_region(player, 'Swamp Healer Fairy', 'a fairy fountain'),
create_cave_region(player, 'Desert Healer Fairy', 'a fairy fountain'),
create_cave_region(player, 'Dark Lake Hylia Healer Fairy', 'a fairy fountain'),
create_cave_region(player, 'Dark Lake Hylia Ledge Healer Fairy', 'a fairy fountain'),
create_cave_region(player, 'Dark Desert Healer Fairy', 'a fairy fountain'),
create_cave_region(player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
create_cave_region(player, 'Chicken House', 'a house with a chest', ['Chicken House']),
create_cave_region(player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
create_cave_region(player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
create_cave_region(player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
create_lw_region(player, 'Hobo Bridge', ['Hobo']),
create_cave_region(player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']),
create_cave_region(player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None, ['Lost Woods Hideout Exit']),
create_cave_region(player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'], ['Lumberjack Tree (top to bottom)']),
create_cave_region(player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']),
create_cave_region(player, 'Cave 45', 'a cave with an item', ['Cave 45']),
create_cave_region(player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']),
create_cave_region(player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']),
create_cave_region(player, 'Long Fairy Cave', 'a fairy fountain'),
create_cave_region(player, 'Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']),
create_cave_region(player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']),
create_cave_region(player, 'Good Bee Cave', 'a cold bee'),
create_cave_region(player, '20 Rupee Cave', 'a cave with some cash'),
create_cave_region(player, 'Cave Shop (Lake Hylia)', 'a common shop'),
create_cave_region(player, 'Cave Shop (Dark Death Mountain)', 'a common shop'),
create_cave_region(player, 'Bonk Rock Cave', 'a cave with a chest', ['Bonk Rock Cave']),
create_cave_region(player, 'Library', 'the library', ['Library']),
create_cave_region(player, 'Kakariko Gamble Game', 'a game of chance'),
create_cave_region(player, 'Potion Shop', 'the potion shop', ['Potion Shop']),
create_lw_region(player, 'Lake Hylia Island', ['Lake Hylia Island']),
create_cave_region(player, 'Capacity Upgrade', 'the queen of fairies'),
create_cave_region(player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
create_lw_region(player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)', 'Maze Race Mirror Spot']),
create_cave_region(player, '50 Rupee Cave', 'a cave with some cash'),
create_lw_region(player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)', 'Desert Ledge Drop']),
create_lw_region(player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)', 'Desert Palace Stairs Mirror Spot']),
create_lw_region(player, 'Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
create_lw_region(player, 'Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks', 'Desert Palace North Mirror Spot']),
create_dungeon_region(player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
create_dungeon_region(player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
create_dungeon_region(player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
create_dungeon_region(player, 'Desert Palace North', 'Desert Palace', ['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
create_dungeon_region(player, 'Eastern Palace', 'Eastern Palace', ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Cannonball Chest',
'Eastern Palace - Big Key Chest', 'Eastern Palace - Map Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Palace Exit']),
create_lw_region(player, 'Master Sword Meadow', ['Master Sword Pedestal']),
create_cave_region(player, 'Lost Woods Gamble', 'a game of chance'),
create_lw_region(player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Hole']),
create_dungeon_region(player, 'Hyrule Castle', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest'],
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']),
create_dungeon_region(player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
create_dungeon_region(player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross'], ['Sewers Door']),
create_dungeon_region(player, 'Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']),
create_dungeon_region(player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']),
create_dungeon_region(player, 'Inverted Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze'], ['Agahnim 1', 'Inverted Agahnims Tower Exit']),
create_dungeon_region(player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None),
create_cave_region(player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
create_cave_region(player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
create_lw_region(player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave',
'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Mirror Spot']),
create_cave_region(player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']),
create_lw_region(player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)', 'Bumper Cave Ledge Mirror Spot']),
create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']),
create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
create_lw_region(player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Mirror Spot (Bottom)', 'Hookshot Fairy',
'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'),
create_cave_region(player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
create_cave_region(player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left',
'Paradox Cave Lower - Left',
'Paradox Cave Lower - Right',
'Paradox Cave Lower - Far Right',
'Paradox Cave Lower - Middle',
'Paradox Cave Upper - Left',
'Paradox Cave Upper - Right'],
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
create_cave_region(player, 'Paradox Cave', 'a connector', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
create_cave_region(player, 'Light World Death Mountain Shop', 'a common shop'),
create_lw_region(player, 'East Death Mountain (Top)', ['Floating Island'], ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'East Death Mountain Mirror Spot (Top)', 'Fairy Ascension Ledge Access', 'Mimic Cave Ledge Access',
'Floating Island Mirror Spot']),
create_lw_region(player, 'Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop', 'Dark Death Mountain Ledge Mirror Spot (West)']),
create_lw_region(player, 'Mimic Cave Ledge', None, ['Mimic Cave', 'Mimic Cave Ledge Drop', 'Dark Death Mountain Ledge Mirror Spot (East)']),
create_cave_region(player, 'Spiral Cave (Top)', 'a connector', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
create_cave_region(player, 'Spiral Cave (Bottom)', 'a connector', None, ['Spiral Cave Exit']),
create_lw_region(player, 'Fairy Ascension Plateau', None, ['Fairy Ascension Drop', 'Fairy Ascension Cave (Bottom)']),
create_cave_region(player, 'Fairy Ascension Cave (Bottom)', 'a connector', None, ['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']),
create_cave_region(player, 'Fairy Ascension Cave (Drop)', 'a connector', None, ['Fairy Ascension Cave Pots']),
create_cave_region(player, 'Fairy Ascension Cave (Top)', 'a connector', None, ['Fairy Ascension Cave Exit (Top)', 'Fairy Ascension Cave Drop']),
create_lw_region(player, 'Fairy Ascension Ledge', None, ['Fairy Ascension Ledge Drop', 'Fairy Ascension Cave (Top)', 'Laser Bridge Mirror Spot']),
create_lw_region(player, 'Death Mountain (Top)', ['Ether Tablet', 'Spectacle Rock'], ['East Death Mountain (Top)', 'Tower of Hera', 'Death Mountain Drop', 'Death Mountain (Top) Mirror Spot']),
create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Ledge Drop', 'Bumper Cave (Top)']),
create_dungeon_region(player, 'Tower of Hera (Bottom)', 'Tower of Hera', ['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']),
create_dungeon_region(player, 'Tower of Hera (Basement)', 'Tower of Hera', ['Tower of Hera - Big Key Chest']),
create_dungeon_region(player, 'Tower of Hera (Top)', 'Tower of Hera', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss', 'Tower of Hera - Prize']),
create_dw_region(player, 'East Dark World', ['Pyramid'], ['Pyramid Fairy', 'South Dark World Bridge', 'Palace of Darkness', 'Dark Lake Hylia Drop (East)',
'Dark Lake Hylia Fairy', 'Palace of Darkness Hint', 'East Dark World Hint', 'Northeast Dark World Broken Bridge Pass', 'East Dark World Teleporter', 'EDW Flute']),
create_dw_region(player, 'Northeast Dark World', ['Catfish'], ['West Dark World Gap', 'Dark World Potion Shop', 'East Dark World Broken Bridge Pass', 'NEDW Flute', 'Dark Lake Hylia Teleporter']),
create_cave_region(player, 'Palace of Darkness Hint', 'a storyteller'),
create_cave_region(player, 'East Dark World Hint', 'a storyteller'),
create_dw_region(player, 'South Dark World', ['Stumpy', 'Digging Game'], ['Dark Lake Hylia Drop (South)', 'Hype Cave', 'Swamp Palace', 'Village of Outcasts Heavy Rock', 'East Dark World Bridge', 'Inverted Links House', 'Archery Game', 'Bonk Fairy (Dark)',
'Dark Lake Hylia Shop', 'South Dark World Teleporter', 'Post Aga Teleporter', 'SDW Flute']),
create_cave_region(player, 'Inverted Big Bomb Shop', 'the bomb shop'),
create_cave_region(player, 'Archery Game', 'a game of skill'),
create_dw_region(player, 'Dark Lake Hylia', None, ['East Dark World Pier', 'Dark Lake Hylia Ledge Pier', 'Ice Palace', 'Dark Lake Hylia Central Island Teleporter']),
create_dw_region(player, 'Dark Lake Hylia Ledge', None, ['Dark Lake Hylia Ledge Drop', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'DLHL Flute']),
create_cave_region(player, 'Dark Lake Hylia Ledge Hint', 'a storyteller'),
create_cave_region(player, 'Dark Lake Hylia Ledge Spike Cave', 'a spiky hint'),
create_cave_region(player, 'Hype Cave', 'a bounty of five items', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
create_dw_region(player, 'West Dark World', ['Frog'], ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Bumper Cave Entrance Rock',
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Inverted Dark Sanctuary', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop',
'West Dark World Teleporter', 'WDW Flute']),
create_dw_region(player, 'Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop', 'Dark Grassy Lawn Flute']),
create_dw_region(player, 'Hammer Peg Area', ['Dark Blacksmith Ruins'], ['Dark World Hammer Peg Cave', 'Peg Area Rocks', 'Hammer Peg Area Flute']),
create_dw_region(player, 'Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Drop']),
create_cave_region(player, 'Fortune Teller (Dark)', 'a fortune teller'),
create_cave_region(player, 'Village of Outcasts Shop', 'a common shop'),
create_cave_region(player, 'Dark Lake Hylia Shop', 'a common shop'),
create_cave_region(player, 'Dark World Lumberjack Shop', 'a common shop'),
create_cave_region(player, 'Dark World Potion Shop', 'a common shop'),
create_cave_region(player, 'Dark World Hammer Peg Cave', 'a cave with an item', ['Peg Cave']),
create_cave_region(player, 'Pyramid Fairy', 'a cave with two chests', ['Pyramid Fairy - Left', 'Pyramid Fairy - Right']),
create_cave_region(player, 'Brewery', 'a house with a chest', ['Brewery']),
create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
create_cave_region(player, 'Red Shield Shop', 'the rare shop'),
create_cave_region(player, 'Inverted Dark Sanctuary', 'a storyteller'),
create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']),
create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)',
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']),
create_dw_region(player, 'Skull Woods Forest (West)', None, ['Skull Woods Second Section Hole', 'Skull Woods Second Section Door (West)', 'Skull Woods Final Section']),
create_dw_region(player, 'Dark Desert', None, ['Misery Mire', 'Mire Shed', 'Dark Desert Hint', 'Dark Desert Fairy', 'DD Flute']),
create_dw_region(player, 'Dark Desert Ledge', None, ['Dark Desert Drop', 'Dark Desert Teleporter']),
create_cave_region(player, 'Mire Shed', 'a cave with two chests', ['Mire Shed - Left', 'Mire Shed - Right']),
create_cave_region(player, 'Dark Desert Hint', 'a storyteller'),
create_dw_region(player, 'Dark Death Mountain', None, ['Dark Death Mountain Drop (East)', 'Inverted Agahnims Tower', 'Superbunny Cave (Top)', 'Hookshot Cave', 'Turtle Rock',
'Spike Cave', 'Dark Death Mountain Fairy', 'Dark Death Mountain Teleporter (West)', 'Turtle Rock Tail Drop', 'DDM Flute']),
create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)']),
create_dw_region(player, 'Turtle Rock (Top)', None, ['Dark Death Mountain Teleporter (East)', 'Turtle Rock Drop']),
create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Turtle Rock Isolated Ledge Entrance']),
create_dw_region(player, 'Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Dark Death Mountain Teleporter (East Bottom)', 'EDDM Flute']),
create_cave_region(player, 'Superbunny Cave', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'],
['Superbunny Cave Exit (Top)', 'Superbunny Cave Exit (Bottom)']),
create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
create_cave_region(player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'],
['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']),
create_dw_region(player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance']),
create_cave_region(player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
create_dungeon_region(player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']),
create_dungeon_region(player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']),
create_dungeon_region(player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest'], ['Swamp Palace (Center)']),
create_dungeon_region(player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest',
'Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest'], ['Swamp Palace (North)']),
create_dungeon_region(player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
'Swamp Palace - Waterfall Room', 'Swamp Palace - Boss', 'Swamp Palace - Prize']),
create_dungeon_region(player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest',
'Thieves\' Town - Map Chest',
'Thieves\' Town - Compass Chest',
'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']),
create_dungeon_region(player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic',
'Thieves\' Town - Big Chest',
'Thieves\' Town - Blind\'s Cell'], ['Blind Fight']),
create_dungeon_region(player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
create_dungeon_region(player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
create_dungeon_region(player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
create_dungeon_region(player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']),
create_dungeon_region(player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
create_dungeon_region(player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']),
create_dungeon_region(player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
create_dungeon_region(player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
create_dungeon_region(player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']),
create_dungeon_region(player, 'Ice Palace (Entrance)', 'Ice Palace', None, ['Ice Palace Entrance Room', 'Ice Palace Exit']),
create_dungeon_region(player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest',
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
create_dungeon_region(player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
create_dungeon_region(player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']),
create_dungeon_region(player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
create_dungeon_region(player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
create_dungeon_region(player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'], ['Misery Mire (West)', 'Misery Mire Big Key Door']),
create_dungeon_region(player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
create_dungeon_region(player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']),
create_dungeon_region(player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']),
create_dungeon_region(player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
create_dungeon_region(player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
'Turtle Rock - Roller Room - Right'], ['Turtle Rock Pokey Room', 'Turtle Rock Entrance Gap Reverse']),
create_dungeon_region(player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']),
create_dungeon_region(player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest'], ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door']),
create_dungeon_region(player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
create_dungeon_region(player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
create_dungeon_region(player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
create_dungeon_region(player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']),
create_dungeon_region(player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']),
create_dungeon_region(player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']),
create_dungeon_region(player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)', 'Palace of Darkness Big Key Door']),
create_dungeon_region(player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']),
create_dungeon_region(player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']),
create_dungeon_region(player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'],
['Palace of Darkness Spike Statue Room Door', 'Palace of Darkness Maze Door']),
create_dungeon_region(player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']),
create_dungeon_region(player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']),
create_dungeon_region(player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
create_dungeon_region(player, 'Inverted Ganons Tower (Entrance)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left', 'Ganons Tower - Hope Room - Right'],
['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door', 'Inverted Ganons Tower Exit']),
create_dungeon_region(player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']),
create_dungeon_region(player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right',
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'],
['Ganons Tower (Bottom) (East)']),
create_dungeon_region(player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower', ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'],
['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']),
create_dungeon_region(player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']),
create_dungeon_region(player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower', ['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']),
create_dungeon_region(player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower', ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'],
['Ganons Tower (Bottom) (West)']),
create_dungeon_region(player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left',
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']),
create_dungeon_region(player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']),
create_dungeon_region(player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right',
'Ganons Tower - Pre-Moldorm Chest'], ['Ganons Tower Moldorm Door']),
create_dungeon_region(player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']),
create_dungeon_region(player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
create_cave_region(player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']),
create_cave_region(player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']),
create_dw_region(player, 'Pyramid Ledge', None, ['Pyramid Drop']), # houlihan room exits here in inverted
# to simplify flute connections
create_cave_region(player, 'The Sky', 'A Dark Sky', None, ['DDM Landing','NEDW Landing', 'WDW Landing', 'SDW Landing', 'EDW Landing', 'DD Landing', 'DLHL Landing'])
]
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
region = world.get_region(region_name, player)
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
region.shop = shop
world.shops.append(shop)
for index, (item, price) in enumerate(default_shop_contents[region_name]):
shop.add_inventory(index, item, price)
region = world.get_region('Capacity Upgrade', player)
shop = Shop(region, 0x0115, ShopType.UpgradeShop, 0x04, True)
region.shop = shop
world.shops.append(shop)
shop.add_inventory(0, 'Bomb Upgrade (+5)', 100, 7)
if not world.retro:
shop.add_inventory(1, 'Arrow Upgrade (+5)', 100, 7)
world.intialize_regions()
def create_lw_region(player, name, locations=None, exits=None):
return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits)
def create_dw_region(player, name, locations=None, exits=None):
return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits)
def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None):
return _create_region(player, name, RegionType.Cave, hint, locations, exits)
def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None):
return _create_region(player, name, RegionType.Dungeon, hint, locations, exits)
def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None):
ret = Region(name, type, hint, player)
if locations is None:
locations = []
if exits is None:
exits = []
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
for location in locations:
address, crystal, hint_text = location_table[location]
ret.locations.append(Location(player, location, address, crystal, hint_text, ret))
return ret
def mark_dark_world_regions(world):
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
# That is ok. the bunny logic will check for this case and incorporate special rules.
queue = collections.deque(region for region in world.regions if region.type == RegionType.DarkWorld)
seen = set(queue)
while queue:
current = queue.popleft()
current.is_dark_world = True
for exit in current.exits:
if exit.connected_region.type == RegionType.LightWorld:
# Don't venture into the dark world
continue
if exit.connected_region not in seen:
seen.add(exit.connected_region)
queue.append(exit.connected_region)
queue = collections.deque(region for region in world.regions if region.type == RegionType.LightWorld)
seen = set(queue)
while queue:
current = queue.popleft()
current.is_light_world = True
for exit in current.exits:
if exit.connected_region.type == RegionType.DarkWorld:
# Don't venture into the light world
continue
if exit.connected_region not in seen:
seen.add(exit.connected_region)
queue.append(exit.connected_region)
# (room_id, shopkeeper, replaceable)
shop_table = {
'Cave Shop (Dark Death Mountain)': (0x0112, 0xC1, True),
'Red Shield Shop': (0x0110, 0xC1, True),
'Dark Lake Hylia Shop': (0x010F, 0xC1, True),
'Dark World Lumberjack Shop': (0x010F, 0xC1, True),
'Village of Outcasts Shop': (0x010F, 0xC1, True),
'Dark World Potion Shop': (0x010F, 0xC1, True),
'Light World Death Mountain Shop': (0x00FF, 0xA0, True),
'Kakariko Shop': (0x011F, 0xA0, True),
'Cave Shop (Lake Hylia)': (0x0112, 0xA0, True),
'Potion Shop': (0x0109, 0xFF, False),
# Bomb Shop not currently modeled as a shop, due to special nature of items
}
# region, [item]
# slot, item, price, max=0, replacement=None, replacement_price=0
# item = (item, price)
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
default_shop_contents = {
'Cave Shop (Dark Death Mountain)': _basic_shop_defaults,
'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)],
'Dark Lake Hylia Shop': _dark_world_shop_defaults,
'Dark World Lumberjack Shop': _dark_world_shop_defaults,
'Village of Outcasts Shop': _dark_world_shop_defaults,
'Dark World Potion Shop': _dark_world_shop_defaults,
'Light World Death Mountain Shop': _basic_shop_defaults,
'Kakariko Shop': _basic_shop_defaults,
'Cave Shop (Lake Hylia)': _basic_shop_defaults,
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
}
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
'Flute Spot': (0x18014A, False, 'underground'),
'Sunken Treasure': (0x180145, False, 'underwater'),
'Purple Chest': (0x33D68, False, 'from a box'),
'Blind\'s Hideout - Top': (0xEB0F, False, 'in a basement'),
'Blind\'s Hideout - Left': (0xEB12, False, 'in a basement'),
'Blind\'s Hideout - Right': (0xEB15, False, 'in a basement'),
'Blind\'s Hideout - Far Left': (0xEB18, False, 'in a basement'),
'Blind\'s Hideout - Far Right': (0xEB1B, False, 'in a basement'),
'Link\'s Uncle': (0x2DF45, False, 'with your uncle'),
'Secret Passage': (0xE971, False, 'near your uncle'),
'King Zora': (0xEE1C3, False, 'at a high price'),
'Zora\'s Ledge': (0x180149, False, 'near Zora'),
'Waterfall Fairy - Left': (0xE9B0, False, 'near a fairy'),
'Waterfall Fairy - Right': (0xE9D1, False, 'near a fairy'),
'King\'s Tomb': (0xE97A, False, 'alone in a cave'),
'Floodgate Chest': (0xE98C, False, 'in the dam'),
'Link\'s House': (0xE9BC, False, 'in your home'),
'Kakariko Tavern': (0xE9CE, False, 'in the bar'),
'Chicken House': (0xE9E9, False, 'near poultry'),
'Aginah\'s Cave': (0xE9F2, False, 'with Aginah'),
'Sahasrahla\'s Hut - Left': (0xEA82, False, 'near the elder'),
'Sahasrahla\'s Hut - Middle': (0xEA85, False, 'near the elder'),
'Sahasrahla\'s Hut - Right': (0xEA88, False, 'near the elder'),
'Sahasrahla': (0x2F1FC, False, 'with the elder'),
'Kakariko Well - Top': (0xEA8E, False, 'in a well'),
'Kakariko Well - Left': (0xEA91, False, 'in a well'),
'Kakariko Well - Middle': (0xEA94, False, 'in a well'),
'Kakariko Well - Right': (0xEA97, False, 'in a well'),
'Kakariko Well - Bottom': (0xEA9A, False, 'in a well'),
'Blacksmith': (0x18002A, False, 'with the smith'),
'Magic Bat': (0x180015, False, 'with the bat'),
'Sick Kid': (0x339CF, False, 'with the sick'),
'Hobo': (0x33E7D, False, 'with the hobo'),
'Lost Woods Hideout': (0x180000, False, 'near a thief'),
'Lumberjack Tree': (0x180001, False, 'in a hole'),
'Cave 45': (0x180003, False, 'alone in a cave'),
'Graveyard Cave': (0x180004, False, 'alone in a cave'),
'Checkerboard Cave': (0x180005, False, 'alone in a cave'),
'Mini Moldorm Cave - Far Left': (0xEB42, False, 'near Moldorms'),
'Mini Moldorm Cave - Left': (0xEB45, False, 'near Moldorms'),
'Mini Moldorm Cave - Right': (0xEB48, False, 'near Moldorms'),
'Mini Moldorm Cave - Far Right': (0xEB4B, False, 'near Moldorms'),
'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'),
'Ice Rod Cave': (0xEB4E, False, 'in a frozen cave'),
'Bonk Rock Cave': (0xEB3F, False, 'alone in a cave'),
'Library': (0x180012, False, 'near books'),
'Potion Shop': (0x180014, False, 'near potions'),
'Lake Hylia Island': (0x180144, False, 'on an island'),
'Maze Race': (0x180142, False, 'at the race'),
'Desert Ledge': (0x180143, False, 'in the desert'),
'Desert Palace - Big Chest': (0xE98F, False, 'in Desert Palace'),
'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'),
'Desert Palace - Map Chest': (0xE9B6, False, 'in Desert Palace'),
'Desert Palace - Compass Chest': (0xE9CB, False, 'in Desert Palace'),
'Desert Palace - Big Key Chest': (0xE9C2, False, 'in Desert Palace'),
'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'),
'Eastern Palace - Compass Chest': (0xE977, False, 'in Eastern Palace'),
'Eastern Palace - Big Chest': (0xE97D, False, 'in Eastern Palace'),
'Eastern Palace - Cannonball Chest': (0xE9B3, False, 'in Eastern Palace'),
'Eastern Palace - Big Key Chest': (0xE9B9, False, 'in Eastern Palace'),
'Eastern Palace - Map Chest': (0xE9F5, False, 'in Eastern Palace'),
'Eastern Palace - Boss': (0x180150, False, 'with the Armos'),
'Master Sword Pedestal': (0x289B0, False, 'at the pedestal'),
'Hyrule Castle - Boomerang Chest': (0xE974, False, 'in Hyrule Castle'),
'Hyrule Castle - Map Chest': (0xEB0C, False, 'in Hyrule Castle'),
'Hyrule Castle - Zelda\'s Chest': (0xEB09, False, 'in Hyrule Castle'),
'Sewers - Dark Cross': (0xE96E, False, 'in the sewers'),
'Sewers - Secret Room - Left': (0xEB5D, False, 'in the sewers'),
'Sewers - Secret Room - Middle': (0xEB60, False, 'in the sewers'),
'Sewers - Secret Room - Right': (0xEB63, False, 'in the sewers'),
'Sanctuary': (0xEA79, False, 'in Sanctuary'),
'Castle Tower - Room 03': (0xEAB5, False, 'in Castle Tower'),
'Castle Tower - Dark Maze': (0xEAB2, False, 'in Castle Tower'),
'Old Man': (0xF69FA, False, 'with the old man'),
'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'),
'Paradox Cave Lower - Far Left': (0xEB2A, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Left': (0xEB2D, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Right': (0xEB30, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Far Right': (0xEB33, False, 'in a cave with seven chests'),
'Paradox Cave Lower - Middle': (0xEB36, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Left': (0xEB39, False, 'in a cave with seven chests'),
'Paradox Cave Upper - Right': (0xEB3C, False, 'in a cave with seven chests'),
'Spiral Cave': (0xE9BF, False, 'in spiral cave'),
'Ether Tablet': (0x180016, False, 'at a monolith'),
'Spectacle Rock': (0x180140, False, 'atop a rock'),
'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'),
'Tower of Hera - Map Chest': (0xE9AD, False, 'in Tower of Hera'),
'Tower of Hera - Big Key Chest': (0xE9E6, False, 'in Tower of Hera'),
'Tower of Hera - Compass Chest': (0xE9FB, False, 'in Tower of Hera'),
'Tower of Hera - Big Chest': (0xE9F8, False, 'in Tower of Hera'),
'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'),
'Pyramid': (0x180147, False, 'on the pyramid'),
'Catfish': (0xEE185, False, 'with a catfish'),
'Stumpy': (0x330C7, False, 'with tree boy'),
'Digging Game': (0x180148, False, 'underground'),
'Bombos Tablet': (0x180017, False, 'at a monolith'),
'Hype Cave - Top': (0xEB1E, False, 'near a bat-like man'),
'Hype Cave - Middle Right': (0xEB21, False, 'near a bat-like man'),
'Hype Cave - Middle Left': (0xEB24, False, 'near a bat-like man'),
'Hype Cave - Bottom': (0xEB27, False, 'near a bat-like man'),
'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'),
'Peg Cave': (0x180006, False, 'alone in a cave'),
'Pyramid Fairy - Left': (0xE980, False, 'near a fairy'),
'Pyramid Fairy - Right': (0xE983, False, 'near a fairy'),
'Brewery': (0xE9EC, False, 'alone in a home'),
'C-Shaped House': (0xE9EF, False, 'alone in a home'),
'Chest Game': (0xEDA8, False, 'as a prize'),
'Bumper Cave Ledge': (0x180146, False, 'on a ledge'),
'Mire Shed - Left': (0xEA73, False, 'near sparks'),
'Mire Shed - Right': (0xEA76, False, 'near sparks'),
'Superbunny Cave - Top': (0xEA7C, False, 'in a connection'),
'Superbunny Cave - Bottom': (0xEA7F, False, 'in a connection'),
'Spike Cave': (0xEA8B, False, 'beyond spikes'),
'Hookshot Cave - Top Right': (0xEB51, False, 'across pits'),
'Hookshot Cave - Top Left': (0xEB54, False, 'across pits'),
'Hookshot Cave - Bottom Right': (0xEB5A, False, 'across pits'),
'Hookshot Cave - Bottom Left': (0xEB57, False, 'across pits'),
'Floating Island': (0x180141, False, 'on an island'),
'Mimic Cave': (0xE9C5, False, 'in a cave of mimicry'),
'Swamp Palace - Entrance': (0xEA9D, False, 'in Swamp Palace'),
'Swamp Palace - Map Chest': (0xE986, False, 'in Swamp Palace'),
'Swamp Palace - Big Chest': (0xE989, False, 'in Swamp Palace'),
'Swamp Palace - Compass Chest': (0xEAA0, False, 'in Swamp Palace'),
'Swamp Palace - Big Key Chest': (0xEAA6, False, 'in Swamp Palace'),
'Swamp Palace - West Chest': (0xEAA3, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Left': (0xEAA9, False, 'in Swamp Palace'),
'Swamp Palace - Flooded Room - Right': (0xEAAC, False, 'in Swamp Palace'),
'Swamp Palace - Waterfall Room': (0xEAAF, False, 'in Swamp Palace'),
'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'),
'Thieves\' Town - Big Key Chest': (0xEA04, False, 'in Thieves\' Town'),
'Thieves\' Town - Map Chest': (0xEA01, False, 'in Thieves\' Town'),
'Thieves\' Town - Compass Chest': (0xEA07, False, 'in Thieves\' Town'),
'Thieves\' Town - Ambush Chest': (0xEA0A, False, 'in Thieves\' Town'),
'Thieves\' Town - Attic': (0xEA0D, False, 'in Thieves\' Town'),
'Thieves\' Town - Big Chest': (0xEA10, False, 'in Thieves\' Town'),
'Thieves\' Town - Blind\'s Cell': (0xEA13, False, 'in Thieves\' Town'),
'Thieves\' Town - Boss': (0x180156, False, 'with Blind'),
'Skull Woods - Compass Chest': (0xE992, False, 'in Skull Woods'),
'Skull Woods - Map Chest': (0xE99B, False, 'in Skull Woods'),
'Skull Woods - Big Chest': (0xE998, False, 'in Skull Woods'),
'Skull Woods - Pot Prison': (0xE9A1, False, 'in Skull Woods'),
'Skull Woods - Pinball Room': (0xE9C8, False, 'in Skull Woods'),
'Skull Woods - Big Key Chest': (0xE99E, False, 'in Skull Woods'),
'Skull Woods - Bridge Room': (0xE9FE, False, 'near Mothula'),
'Skull Woods - Boss': (0x180155, False, 'with Mothula'),
'Ice Palace - Compass Chest': (0xE9D4, False, 'in Ice Palace'),
'Ice Palace - Freezor Chest': (0xE995, False, 'in Ice Palace'),
'Ice Palace - Big Chest': (0xE9AA, False, 'in Ice Palace'),
'Ice Palace - Iced T Room': (0xE9E3, False, 'in Ice Palace'),
'Ice Palace - Spike Room': (0xE9E0, False, 'in Ice Palace'),
'Ice Palace - Big Key Chest': (0xE9A4, False, 'in Ice Palace'),
'Ice Palace - Map Chest': (0xE9DD, False, 'in Ice Palace'),
'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'),
'Misery Mire - Big Chest': (0xEA67, False, 'in Misery Mire'),
'Misery Mire - Map Chest': (0xEA6A, False, 'in Misery Mire'),
'Misery Mire - Main Lobby': (0xEA5E, False, 'in Misery Mire'),
'Misery Mire - Bridge Chest': (0xEA61, False, 'in Misery Mire'),
'Misery Mire - Spike Chest': (0xE9DA, False, 'in Misery Mire'),
'Misery Mire - Compass Chest': (0xEA64, False, 'in Misery Mire'),
'Misery Mire - Big Key Chest': (0xEA6D, False, 'in Misery Mire'),
'Misery Mire - Boss': (0x180158, False, 'with Vitreous'),
'Turtle Rock - Compass Chest': (0xEA22, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Left': (0xEA1C, False, 'in Turtle Rock'),
'Turtle Rock - Roller Room - Right': (0xEA1F, False, 'in Turtle Rock'),
'Turtle Rock - Chain Chomps': (0xEA16, False, 'in Turtle Rock'),
'Turtle Rock - Big Key Chest': (0xEA25, False, 'in Turtle Rock'),
'Turtle Rock - Big Chest': (0xEA19, False, 'in Turtle Rock'),
'Turtle Rock - Crystaroller Room': (0xEA34, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Left': (0xEA31, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Bottom Right': (0xEA2E, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Left': (0xEA2B, False, 'in Turtle Rock'),
'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'),
'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'),
'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'),
'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'),
'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'),
'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'),
'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Basement - Right': (0xEA4F, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Top': (0xEA55, False, 'in Palace of Darkness'),
'Palace of Darkness - Dark Maze - Bottom': (0xEA58, False, 'in Palace of Darkness'),
'Palace of Darkness - Big Chest': (0xEA40, False, 'in Palace of Darkness'),
'Palace of Darkness - Harmless Hellway': (0xEA46, False, 'in Palace of Darkness'),
'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'),
'Ganons Tower - Bob\'s Torch': (0x180161, False, 'in Ganon\'s Tower'),
'Ganons Tower - Hope Room - Left': (0xEAD9, False, 'in Ganon\'s Tower'),
'Ganons Tower - Hope Room - Right': (0xEADC, False, 'in Ganon\'s Tower'),
'Ganons Tower - Tile Room': (0xEAE2, False, 'in Ganon\'s Tower'),
'Ganons Tower - Compass Room - Top Left': (0xEAE5, False, 'in Ganon\'s Tower'),
'Ganons Tower - Compass Room - Top Right': (0xEAE8, False, 'in Ganon\'s Tower'),
'Ganons Tower - Compass Room - Bottom Left': (0xEAEB, False, 'in Ganon\'s Tower'),
'Ganons Tower - Compass Room - Bottom Right': (0xEAEE, False, 'in Ganon\'s Tower'),
'Ganons Tower - DMs Room - Top Left': (0xEAB8, False, 'in Ganon\'s Tower'),
'Ganons Tower - DMs Room - Top Right': (0xEABB, False, 'in Ganon\'s Tower'),
'Ganons Tower - DMs Room - Bottom Left': (0xEABE, False, 'in Ganon\'s Tower'),
'Ganons Tower - DMs Room - Bottom Right': (0xEAC1, False, 'in Ganon\'s Tower'),
'Ganons Tower - Map Chest': (0xEAD3, False, 'in Ganon\'s Tower'),
'Ganons Tower - Firesnake Room': (0xEAD0, False, 'in Ganon\'s Tower'),
'Ganons Tower - Randomizer Room - Top Left': (0xEAC4, False, 'in Ganon\'s Tower'),
'Ganons Tower - Randomizer Room - Top Right': (0xEAC7, False, 'in Ganon\'s Tower'),
'Ganons Tower - Randomizer Room - Bottom Left': (0xEACA, False, 'in Ganon\'s Tower'),
'Ganons Tower - Randomizer Room - Bottom Right': (0xEACD, False, 'in Ganon\'s Tower'),
'Ganons Tower - Bob\'s Chest': (0xEADF, False, 'in Ganon\'s Tower'),
'Ganons Tower - Big Chest': (0xEAD6, False, 'in Ganon\'s Tower'),
'Ganons Tower - Big Key Room - Left': (0xEAF4, False, 'in Ganon\'s Tower'),
'Ganons Tower - Big Key Room - Right': (0xEAF7, False, 'in Ganon\'s Tower'),
'Ganons Tower - Big Key Chest': (0xEAF1, False, 'in Ganon\'s Tower'),
'Ganons Tower - Mini Helmasaur Room - Left': (0xEAFD, False, 'atop Ganon\'s Tower'),
'Ganons Tower - Mini Helmasaur Room - Right': (0xEB00, False, 'atop Ganon\'s Tower'),
'Ganons Tower - Pre-Moldorm Chest': (0xEB03, False, 'atop Ganon\'s Tower'),
'Ganons Tower - Validation Chest': (0xEB06, False, 'atop Ganon\'s Tower'),
'Ganon': (None, False, 'from me'),
'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'),
'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'),
'Floodgate': (None, False, None),
'Frog': (None, False, None),
'Missing Smith': (None, False, None),
'Dark Blacksmith Ruins': (None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE], True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53E7A, 0x53E7B, 0x180053, 0x180072, 0xC6FF], True, 'Desert Palace'),
'Tower of Hera - Prize': ([0x120A5, 0x53E78, 0x53E79, 0x18005A, 0x180071, 0xC706], True, 'Tower of Hera'),
'Palace of Darkness - Prize': ([0x120A1, 0x53E7C, 0x53E7D, 0x180056, 0x180073, 0xC702], True, 'Palace of Darkness'),
'Swamp Palace - Prize': ([0x120A0, 0x53E88, 0x53E89, 0x180055, 0x180079, 0xC701], True, 'Swamp Palace'),
'Thieves\' Town - Prize': ([0x120A6, 0x53E82, 0x53E83, 0x18005B, 0x180076, 0xC707], True, 'Thieves\' Town'),
'Skull Woods - Prize': ([0x120A3, 0x53E7E, 0x53E7F, 0x180058, 0x180074, 0xC704], True, 'Skull Woods'),
'Ice Palace - Prize': ([0x120A4, 0x53E86, 0x53E87, 0x180059, 0x180078, 0xC705], True, 'Ice Palace'),
'Misery Mire - Prize': ([0x120A2, 0x53E84, 0x53E85, 0x180057, 0x180077, 0xC703], True, 'Misery Mire'),
'Turtle Rock - Prize': ([0x120A7, 0x53E80, 0x53E81, 0x18005C, 0x180075, 0xC708], True, 'Turtle Rock')}
+224 -1597
View File
File diff suppressed because it is too large Load Diff
+161 -203
View File
@@ -1,3 +1,5 @@
import logging
from BaseClasses import Item
@@ -9,213 +11,169 @@ def ItemFactory(items, player):
singleton = True
for item in items:
if item in item_table:
advancement, priority, type, code, price, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text = item_table[item]
ret.append(Item(item, advancement, priority, type, code, price, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text, player))
advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text = item_table[item]
ret.append(Item(item, advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text, player))
else:
raise RuntimeError(f'Unknown Item: {item}')
logging.getLogger('').warning('Unknown Item: %s', item)
return None
if singleton:
return ret[0]
return ret
# Format: Name: (Advancement, Priority, Type, ItemCode, BasePrice, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
item_table = {'Bow': (True, False, None, 0x0B, 200, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
'Progressive Bow': (True, False, None, 0x64, 150, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
'Hammer': (True, False, None, 0x09, 250, 'Stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'),
'Hookshot': (True, False, None, 0x0A, 250, 'Hookshot!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
'Magic Mirror': (True, False, None, 0x1A, 250, 'Mirror!\nTake some time to\nreflect on this moment!', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'),
'Ocarina': (True, False, None, 0x14, 250, 'Ocarina!\nA Flute by another name', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'),
'Ocarina (Activated)': (True, False, None, 0x4A, 250, 'Ocarina!\nA Flute by another name', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'),
'Pegasus Boots': (True, False, None, 0x4B, 250, 'Pegasus Boots!\nGotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'),
'Power Glove': (True, False, None, 0x1B, 100, 'Glove!\nNow you can\nlift weak stuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the Glove'),
'Cape': (True, False, None, 0x19, 50, 'Cape!\nInvisbility cloak activate!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the Cape'),
'Mushroom': (True, False, None, 0x29, 50, 'Mushroom!\nDon\'t eat it. Find a witch.', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'),
'Shovel': (True, False, None, 0x13, 50, 'Shovel!\nCan you dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'),
'Lamp': (True, False, None, 0x12, 150, 'Lamp!\nYou can see in the dark,\nand light torches.', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'),
'Magic Powder': (True, False, None, 0x0D, 50, 'Powder!\nSprinkle it on a dancing pickle!', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'),
'Moon Pearl': (True, False, None, 0x1F, 200, 'Moon Pearl!\nRabbit Be Gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'),
'Cane of Somaria': (True, False, None, 0x15, 250, 'Cane of Somaria!\nMake blocks, throw blocks', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'),
'Fire Rod': (True, False, None, 0x07, 250, 'Fire Rod!\nI\'m burning for you!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'),
'Flippers': (True, False, None, 0x1E, 250, 'Flippers!\nFancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'),
'Ice Rod': (True, False, None, 0x08, 250, 'Ice Rod!\nActivate Freeze Ray!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'),
'Titans Mitts': (True, False, None, 0x1C, 200, 'Mitts!\nLift ALL the rocks!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'),
'Bombos': (True, False, None, 0x0F, 100, 'Bombos!\nExplosions! Fire!\nBurn it all!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
'Ether': (True, False, None, 0x10, 100, 'Ether!\nLet\'s cool things down!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
'Quake': (True, False, None, 0x11, 100, 'Quake!\nLet\'s shake the ground!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
'Bottle': (True, False, None, 0x16, 50, 'Bottle!\nStore all manner of things!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'),
'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Red Potion!\nFill your hearts!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'),
'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Green Potion!\nRestore your magic!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'),
'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Blue Potion!\nHeal and restore!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'),
'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Fairy Bottle!\nI\'ll save your life!', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'),
'Bottle (Bee)': (True, False, None, 0x3C, 50, 'Bee Bottle!\nI\'ll sting your enemies a bit!', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'),
'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'Good Bee Bottle!\nI\'ll sting your enemies\na whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'),
'Master Sword': (True, False, 'Sword', 0x50, 100, 'Master Sword!\nEvil\'s bane!', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'Tempered Sword!\nMore slashy!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'Fighter Sword!\nStarter level slashy!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'),
'Sword and Shield': (True, False, 'Sword', 0x00, 'Sword and Shield!\nUncle sword ahoy!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
'Golden Sword': (True, False, 'Sword', 0x03, 200, 'Golden Sword!\nBest slashy!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'Sword!\nA better sword for your time!', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
'Progressive Glove': (True, False, None, 0x61, 150, 'Glove!\nLift more than you can now!', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
'Silver Arrows': (True, False, None, 0x58, 100, 'Silver Arrows!\nDefeat Ganon faster!', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Arrows'),
'Green Pendant': (True, False, 'Prize', 0x37, 250, 'Green Pendant! Too\nbad Sahasrahla\nwill never see it!', 'and the green pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'),
'Blue Pendant': (True, False, 'Prize', 0x39, 150, 'Blue Pendant!\nBut how?!', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'),
'Red Pendant': (True, False, 'Prize', 0x38, 150, 'Red Pendant!\nBut how?!', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'),
'Triforce': (True, False, None, 0x6A, 777, 'Triforce!\nGrab me to win!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
'Power Star': (True, False, None, 0x6B, 100, 'Power Star!\nA small step\ntowards victory!', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
'Triforce Piece': (True, False, None, 0x6C, 100, 'Triforce Piece!\nA small step\ntowards victory!', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce piece'),
'Crystal 1': (True, False, 'Prize', 0xB1, 200, 'Crystal 1! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 2': (True, False, 'Prize', 0xB4, 200, 'Crystal 2! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 3': (True, False, 'Prize', 0xB6, 200, 'Crystal 3! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 4': (True, False, 'Prize', 0xB5, 200, 'Crystal 4! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 5': (True, False, 'Prize', 0xB2, 250, 'Crystal 5! It unlocks a bomb!', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 6': (True, False, 'Prize', 0xB0, 250, 'Crystal 6! It unlocks a bomb!', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 7': (True, False, 'Prize', 0xB3, 200, 'Crystal 7! It\'s very shiny!', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Single Arrow': (False, False, None, 0x43, 3, 'Single Arrow!\nDestiny awaits!', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
'Arrows (10)': (False, False, None, 0x44, 30, '10 Arrows!\nAn archer\'s bailout!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'),
'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, '10 Arrow Upgrade!\nGain 10 more arrow capacity!', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, '5 Arrow Upgrade!\nGain 5 more arrow capacity!', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Single Bomb': (False, False, None, 0x27, 5, '1 Bomb!\nBoom boom pow!', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
'Arrows (5)': (False, False, None, 0xD5, 15, '5 Arrows!\nAn archer\'s bailout!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'),
'Small Magic': (False, False, None, 0x45, 5, 'Magic Decanter!\nJust a little one', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'),
'Big Magic': (False, False, None, 0xD4, 40, 'Magic Decanter!\nFull refill', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'),
'Chicken': (False, False, None, 0xD3, 5, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'),
'Bombs (3)': (False, False, None, 0x28, 15, '3 Bombs!\nExplosions!\nIn a handy 3-pack', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
'Bombs (10)': (False, False, None, 0x31, 50, '10 Bombs!\nExplosions!\nIn a thrifty 10-pack', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, '10 Bomb Upgrade!\nGain 10 more bomb capacity!', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, '5 Bomb Upgrade!\nGain 5 more bomb capacity!', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
'Blue Mail': (False, True, None, 0x22, 50, 'Blue Mail!\nLess damage activate!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'),
'Red Mail': (False, True, None, 0x23, 100, 'Red Mail!\nEven less damage!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'),
'Progressive Armor': (False, True, None, 0x60, 50, 'Upgrade Mail!\nNot the kind you read!', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
'Blue Boomerang': (True, False, None, 0x0C, 50, 'Blue Boomerang!\nAlways comes back!', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'),
'Red Boomerang': (True, False, None, 0x2A, 50, 'Red Boomerang!\nAlways comes back!', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'),
'Blue Shield': (False, True, None, 0x04, 50, 'Blue Shield!\nBlock rocks!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a blue shield'),
'Red Shield': (False, True, None, 0x05, 500, 'Red Shield!\nBlock fireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'the Red Shield'),
'Mirror Shield': (True, False, None, 0x06, 200, 'Mirror Shield!\nBlock lasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'),
'Progressive Shield': (True, False, None, 0x5F, 50, 'Shield!\nA better shield for your time!', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'),
'Bug Catching Net': (True, False, None, 0x21, 50, 'Bug Net!\nCatch all manner\nof things!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'),
'Cane of Byrna': (True, False, None, 0x18, 50, 'Cane of Byrna!\nSwirly protection!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'),
'Boss Heart Container': (False, True, None, 0x3E, 40, 'Heart Container!\nHealth Increased!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
'Sanctuary Heart Container': (False, True, None, 0x3F, 50, 'Heart Container!\nHealth Increased!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
'Piece of Heart': (False, False, None, 0x17, 10, 'Heart Piece!\nOne step closer\nto more health!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'),
'Rupee (1)': (False, False, None, 0x34, 0, 'Rupees!\nJust pocket\nchange.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'),
'Rupees (5)': (False, False, None, 0x35, 2, 'Rupees!\nJust pocket\nchange.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'),
'Rupees (20)': (False, False, None, 0x36, 10, 'Rupees!\nJust couch\ncash.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'destitute boy has lunch again', 'a red rupee'),
'Rupees (50)': (False, False, None, 0x41, 25, 'Rupees!\nA big pile!', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'),
'Rupees (100)': (False, False, None, 0x40, 50, 'Rupees!\nA big stash!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'),
'Rupees (300)': (False, True, None, 0x46, 150, 'Rupees!\nA big hoard!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'),
'Rupoor': (False, False, None, 0x59, 0, 'Rupoor!\nI\'ll take your rupees!', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'),
'Red Clock': (False, True, None, 0x5B, 0, 'Red Clock!\nA waste of time.', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'),
'Blue Clock': (False, True, None, 0x5C, 50, 'Blue Clock!\nA bit of time!', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'),
'Green Clock': (False, True, None, 0x5D, 200, 'Green Clock!\nTons of time!', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'),
'Single RNG': (False, True, None, 0x62, 300, 'Something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'),
'Multi RNG': (False, True, None, 0x63, 100, 'Something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'),
'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Half Magic!\nDouble your magic power!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Quarter Magic!\nMagic is basically free now!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key for\nEastern Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Eastern Palace'),
'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 60, 'A big key for\nEastern Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Eastern Palace'),
'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 10, 'A compass for\nEastern Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Eastern Palace'),
'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Eastern Palace'),
'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 40, 'A small key for\nDesert Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Desert Palace'),
'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 60, 'A big key for\nDesert Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Desert Palace'),
'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 10, 'A compass for\nDesert Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Desert Palace'),
'Map (Desert Palace)': (False, True, 'Map', 0x7C, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Desert Palace'),
'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 40, 'A small key for\nTower of Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Tower of Hera'),
'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 60, 'A big key for\nTower of Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Tower of Hera'),
'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 10, 'A compass for\nTower of Hera', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Tower of Hera'),
'Map (Tower of Hera)': (False, True, 'Map', 0x75, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Tower of Hera'),
'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 40, 'A small key for\nHyrule Castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Hyrule Castle'),
'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 60, 'A big key for\nHyrule Castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Hyrule Castle'),
'Compass (Escape)': (False, True, 'Compass', 0x8F, 10, 'A compass for\nHyrule Castle', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Hyrule Castle'),
'Map (Escape)': (False, True, 'Map', 0x7F, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Hyrule Castle'),
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 40, 'A small key for\nAgahnim\'s Tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'),
'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 60, 'A big key for\nAgahnim\'s Tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'),
'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 10, 'A compass for\nAgahnim\'s Tower', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'),
'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'),
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 40, 'A small key for\nDark Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'),
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 60, 'A big key for\nDark Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'),
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 10, 'A compass for\nDark Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Palace of Darkness'),
'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 40, 'A small key for\nThieves Town', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Thieves\' Town'),
'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 60, 'A big key for\nThieves Town', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Thieves\' Town'),
'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 10, 'A compass for\nThieves Town', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Thieves\' Town'),
'Map (Thieves Town)': (False, True, 'Map', 0x74, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Thieves\' Town'),
'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 40, 'A small key for\nSkull Woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Skull Woods'),
'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 60, 'A big key for\nSkull Woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Skull Woods'),
'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 10, 'A compass for\nSkull Woods', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Skull Woods'),
'Map (Skull Woods)': (False, True, 'Map', 0x77, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Skull Woods'),
'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 40, 'A small key for\nSwamp Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Swamp Palace'),
'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 60, 'A big key for\nSwamp Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Swamp Palace'),
'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 10, 'A compass for\nSwamp Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Swamp Palace'),
'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Swamp Palace'),
'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 40, 'A small key for\nIce Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ice Palace'),
'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 60, 'A big key for\nIce Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ice Palace'),
'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 10, 'A compass for\nIce Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ice Palace'),
'Map (Ice Palace)': (False, True, 'Map', 0x76, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ice Palace'),
'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 40, 'A small key for\nMisery Mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Misery Mire'),
'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 60, 'A big key for\nMisery Mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Misery Mire'),
'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 10, 'A compass for\nMisery Mire', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Misery Mire'),
'Map (Misery Mire)': (False, True, 'Map', 0x78, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Misery Mire'),
'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 40, 'A small key for\nTurtle Rock', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Turtle Rock'),
'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 60, 'A big key for\nTurtle Rock', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Turtle Rock'),
'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 10, 'A compass for\nTurtle Rock', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Turtle Rock'),
'Map (Turtle Rock)': (False, True, 'Map', 0x73, 20, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Turtle Rock'),
'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 40, 'A small key for\nGanon\'s Tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ganon\'s Tower'),
'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 60, 'A big key for\nGanon\'s Tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ganon\'s Tower'),
'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 10, 'A compass for\nGanon\'s Tower', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ganon\'s Tower'),
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 10, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a Map to Ganon\'s Tower'),
'Small Key (Universal)': (False, True, None, 0xAF, 100, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key'),
'Nothing': (False, False, None, 0x5A, 1, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'),
'Bee Trap': (False, False, None, 0xD0, 50, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'friendship'),
'Red Potion': (False, False, None, 0x2E, 150, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'),
'Green Potion': (False, False, None, 0x2F, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'),
'Blue Potion': (False, False, None, 0x30, 160, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'),
'Bee': (False, False, None, 0x0E, 10, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'),
'Good Bee': (False, False, None, 0xD6, 10, 'I will sting your foes a lot', 'and the cold buddy', 'the beekeeper kid', 'cold insect for sale', 'shroom pollenation', 'bottle boy has cold bee again', 'a good bee'),
'Small Heart': (False, False, None, 0x42, 10, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'),
'Apples': (False, False, None, 0xD1, 30, 'Just a few pieces of fruit!', 'and the juicy fruit', 'the fruity kid', 'the fruit stand', 'expired fruit', 'bottle boy has fruit again', 'an apple hoard'),
'Fairy': (False, False, None, 0xD2, 50, 'Just a pixie!', 'and the pixie', 'the pixie kid', 'pixie for sale', 'pixie fungus', 'bottle boy has pixie again', 'a pixie'),
'Beat Boss': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Beat Agahnim 1': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Beat Agahnim 2': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Pick Up Kiki': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Dark Palace Opened': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Get Frog': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Return Smith': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Pick Up Purple Chest': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Deliver Purple Chest': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Open Floodgate': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Pick Up Big Bomb': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Detonate Big Bomb': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Trench 1 Filled': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Trench 2 Filled': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Drained Swamp': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Turtle Opened': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Shining Light': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Maiden Rescued': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Maiden Unmasked': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Convenient Block': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Hidden Pits': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Sign Vandalized': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Escort Old Man': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Return Old Man': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Farmable Bombs': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Farmable Rupees': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
}
prize_item_table = {
'Green Pendant': [0x04, 0x38, 0x60, 0x00, 0x69, 0x08],
'Blue Pendant': [0x02, 0x34, 0x60, 0x00, 0x69, 0x09],
'Red Pendant': [0x01, 0x32, 0x60, 0x00, 0x69, 0x0a],
'Crystal 1': [0x02, 0x34, 0x64, 0x40, 0x7F, 0x01],
'Crystal 2': [0x10, 0x34, 0x64, 0x40, 0x79, 0x02],
'Crystal 3': [0x40, 0x34, 0x64, 0x40, 0x6C, 0x03],
'Crystal 4': [0x20, 0x34, 0x64, 0x40, 0x6D, 0x04],
'Crystal 5': [0x04, 0x32, 0x64, 0x40, 0x6E, 0x05],
'Crystal 6': [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06],
'Crystal 7': [0x08, 0x34, 0x64, 0x40, 0x7C, 0x07]
}
# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
item_table = {'Bow': (True, False, None, 0x0B, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'The Bow'),
'Progressive Bow': (True, False, None, 0x64, 'Bow!\nJoin the archer class!', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'A Bow'),
'Book of Mudora': (True, False, None, 0x1D, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'The Book'),
'Hammer': (True, False, None, 0x09, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'The Hammer'),
'Hookshot': (True, False, None, 0x0A, 'Hookshot!\nBOING! BOING!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'The Hookshot'),
'Magic Mirror': (True, False, None, 0x1A, 'Mirror!\nTake some time to\nreflect on this moment!', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'The Mirror'),
'Ocarina': (True, False, None, 0x14, 'Ocarina!\nA Flute by another name', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'The Flute'),
'Pegasus Boots': (True, False, None, 0x4B, 'Pegasus Boots!\nGotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'The Boots'),
'Power Glove': (True, False, None, 0x1B, 'Glove!\nNow you can\nlift weak stuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'The Glove'),
'Cape': (True, False, None, 0x19, 'Cape!\nInvisbility cloak activate!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'The Cape'),
'Mushroom': (True, False, None, 0x29, 'Mushroom!\nDon\'t eat it. Find a witch.', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'The Mushroom'),
'Shovel': (True, False, None, 0x13, 'Shovel!\nCan you dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'The Shovel'),
'Lamp': (True, False, None, 0x12, 'Lamp!\nYou can see in the dark,\nand light torches.', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'The Lamp'),
'Magic Powder': (True, False, None, 0x0D, 'Powder!\nSprinkle it on a dancing pickle!', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'The Powder'),
'Moon Pearl': (True, False, None, 0x1F, 'Moon Pearl!\nRabbit Be Gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'The Moon Pearl'),
'Cane of Somaria': (True, False, None, 0x15, 'Cane of Somaria!\nMake blocks, throw blocks', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'The Red Cane'),
'Fire Rod': (True, False, None, 0x07, 'Fire Rod!\nI\'m burning for you!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'The Fire Rod'),
'Flippers': (True, False, None, 0x1E, 'Flippers!\nFancy a swim!', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'The Flippers'),
'Ice Rod': (True, False, None, 0x08, 'Ice Rod!\nActivate Freeze Ray!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'The Ice Rod'),
'Titans Mitts': (True, False, None, 0x1C, 'Mitts!\nLift ALL the rocks!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'The Mitts'),
'Ether': (True, False, None, 0x10, 'Ether!\nLet\'s cool things down!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
'Bombos': (True, False, None, 0x0F, 'Bombos!\nExplosions! Fire!\nBurn it all!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
'Quake': (True, False, None, 0x11, 'Quake!\nLet\'s shake the ground!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
'Bottle': (True, False, None, 0x16, 'Bottle!\nStore all manner of things!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'A Bottle'),
'Bottle (Red Potion)': (True, False, None, 0x2B, 'Red Potion!\nFill your hearts!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'A Bottle'),
'Bottle (Green Potion)': (True, False, None, 0x2C, 'Green Potion!\nRestore your magic!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'A Bottle'),
'Bottle (Blue Potion)': (True, False, None, 0x2D, 'Blue Potion!\nHeal and restore!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'A Bottle'),
'Bottle (Fairy)': (True, False, None, 0x3D, 'Fairy Bottle\nI\'ll save your life!', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'A Bottle'),
'Bottle (Bee)': (True, False, None, 0x3C, 'Bee Bottle\nI\'ll sting your enemies a bit!', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'A Bottle'),
'Bottle (Good Bee)': (True, False, None, 0x48, 'Good Bee Bottle\nI\'ll sting your enemies\na whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'A Bottle'),
'Master Sword': (True, False, 'Sword', 0x50, 'Master Sword!\nEvil\'s bane!', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'The Master Sword'),
'Tempered Sword': (True, False, 'Sword', 0x02, 'Tempered Sword!\nMore slashy!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'The Tempered Sword'),
'Sword and Shield': (True, False, 'Sword', 0x00, 'Sword and Shield!\nUncle sword ahoy!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'The Sword and Shield'),
'Fighter Sword': (True, False, 'Sword', 0x49, 'Fighter Sword!\nStarter level slashy!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'The Small Sword'),
'Golden Sword': (True, False, 'Sword', 0x03, 'Golden Sword!\nBest slashy!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'The Golden Sword'),
'Progressive Sword': (True, False, 'Sword', 0x5E, 'Sword!\nA better sword for your time!', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'A sword'),
'Progressive Glove': (True, False, None, 0x61, 'Glove!\nLift more than you can now!', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'A Glove'),
'Silver Arrows': (True, False, None, 0x58, 'Silver Arrows!\nDefeat Ganon faster!', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'The Silver Arrows'),
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x37], None, None, None, None, None, None, None),
'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x39], None, None, None, None, None, None, None),
'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x38], None, None, None, None, None, None, None),
'Triforce': (True, False, None, 0x6A, 'Triforce\nGrab me to win!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'The Triforce'),
'Power Star': (True, False, None, 0x6B, 'Power Star\nA small step\ntowards victory!', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'A Power Star'),
'Triforce Piece': (True, False, None, 0x6C, 'Triforce Piece\nA small step\ntowards victory!', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'A Triforce Piece'),
'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x20], None, None, None, None, None, None, None),
'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x20], None, None, None, None, None, None, None),
'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x20], None, None, None, None, None, None, None),
'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x20], None, None, None, None, None, None, None),
'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x20], None, None, None, None, None, None, None),
'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x20], None, None, None, None, None, None, None),
'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x20], None, None, None, None, None, None, None),
'Single Arrow': (False, False, None, 0x43, 'Single Arrow!\nDestiny awaits!', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'An arrow'),
'Arrows (10)': (False, False, None, 0x44, '10 Arrows\nAn archer\'s bailout!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'Ten arrows'),
'Arrow Upgrade (+10)': (False, False, None, 0x54, '10 Arrow Upgrade\nGain 10 more arrow capacity!', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'An arrow capacity upgrade'),
'Arrow Upgrade (+5)': (False, False, None, 0x53, '5 Arrow Upgrade\nGain 5 more arrow capacity!', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'An arrow capacity upgrade'),
'Single Bomb': (False, False, None, 0x27, '1 Bomb\nBoom boom pow!', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'A bomb'),
'Bombs (3)': (False, False, None, 0x28, '3 Bombs\nExplosions!\nIn a handy 3-pack', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'Three bombs'),
'Bombs (10)': (False, False, None, 0x31, '10 Bombs\nExplosions!\nIn a thrifty 10-pack', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'Ten bombs'),
'Bomb Upgrade (+10)': (False, False, None, 0x52, '10 Bomb Upgrade\nGain 10 more bomb capacity!', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'A bomb capacity upgrade'),
'Bomb Upgrade (+5)': (False, False, None, 0x51, '5 Bomb Upgrade\nGain 5 more bomb capacity!', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'A bomb capacity upgrade'),
'Blue Mail': (False, True, None, 0x22, 'Blue Mail!\nLess damage activate!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'The Blue Mail'),
'Red Mail': (False, True, None, 0x23, 'Red Mail!\nEven less damage!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'The Red Mail'),
'Progressive Armor': (False, True, None, 0x60, 'Upgrade Mail!\nNot the kind you read!', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'Some Armor'),
'Blue Boomerang': (True, False, None, 0x0C, 'Blue Boomerang!\nAlways comes back!', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'The Blue Boomerang'),
'Red Boomerang': (True, False, None, 0x2A, 'Red Boomerang!\nAlways comes back!', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'The Red Boomerang'),
'Blue Shield': (False, True, None, 0x04, 'Blue Shield\nBlock rocks!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'The Blue Shield'),
'Red Shield': (False, True, None, 0x05, 'Red Shield\nBlock fireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'The Red Shield'),
'Mirror Shield': (True, False, None, 0x06, 'Mirror Shield\nBlock lasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'The Mirror Shield'),
'Progressive Shield': (True, False, None, 0x5F, 'Shield\nA better shield for your time!', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'A Shield'),
'Bug Catching Net': (True, False, None, 0x21, 'Bug Net!\nCatch all manner\nof things!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'The Bug Net'),
'Cane of Byrna': (True, False, None, 0x18, 'Cane of Byrna!\nSwirly protection!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'The Blue Cane'),
'Boss Heart Container': (False, False, None, 0x3E, 'Heart Container!\nHealth Increased!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'A Heart'),
'Sanctuary Heart Container': (False, False, None, 0x3F, 'Heart Container!\nHealth Increased!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'A Heart'),
'Piece of Heart': (False, False, None, 0x17, 'Heart Piece!\nOne step closer\nto more health!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'A Heart Piece'),
'Rupee (1)': (False, False, None, 0x34, 'Rupees!\nNeat I guess?', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'A green rupee'),
'Rupees (5)': (False, False, None, 0x35, 'Rupees!\nNeat I guess?', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'A blue rupee'),
'Rupees (20)': (False, False, None, 0x36, 'Rupees!\nThat\'s alright.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'destitute boy has lunch again', 'A red rupee'),
'Rupees (50)': (False, False, None, 0x41, 'Rupees!\nThat\'s neat!', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'Fifty rupees'),
'Rupees (100)': (False, False, None, 0x40, 'Rupees!\nThat\'s awesome!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'One hundred rupees'),
'Rupees (300)': (False, False, None, 0x46, 'Rupees!\nThat\'s fantastic!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'Three hundred rupees'),
'Rupoor': (False, False, None, 0x59, 'Rupoor!\nI\'ll take your rupees!', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'A rupoor'),
'Red Clock': (False, True, None, 0x5B, 'Red Clock!\nA waste of time.', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'A red clock'),
'Blue Clock': (False, True, None, 0x5C, 'Blue Clock!\nA bit of time!', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'A blue clock'),
'Green Clock': (False, True, None, 0x5D, 'Green Clock!\nTons of time!', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'A green clock'),
'Single RNG': (False, True, None, 0x62, 'something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'A new mystery'),
'Multi RNG': (False, True, None, 0x63, 'something you may already have', None, None, None, None, 'unknown boy somethings again', 'A total mystery'),
'Magic Upgrade (1/2)': (True, False, None, 0x4E, 'Half Magic!\nDouble your magic power!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half magic'), # can be required to beat mothula in an open seed in very very rare circumstance
'Magic Upgrade (1/4)': (True, False, None, 0x4F, 'Quarter Magic!\nMagic is basically free now!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter magic'), # can be required to beat mothula in an open seed in very very rare circumstance
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 'A Small Key for\nDesert Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Eastern Palace'),
'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 'A Big Key for\nEastern Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Eastern Palace'),
'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 'A Compass for\nEastern Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Eastern Palace'),
'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Eastern Palace'),
'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 'A Small Key for\nDesert Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Desert Palace'),
'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 'A Big Key for\nDesert Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Desert Palace'),
'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 'A Compass for\nDesert Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Desert Palace'),
'Map (Desert Palace)': (False, True, 'Map', 0x7C, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Desert Palace'),
'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 'A Small Key for\nTower of Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Tower of Hera'),
'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 'A Big Key for\nTower of Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Tower of Hera'),
'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 'A Compass for\nTower of Hera', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Tower of Hera'),
'Map (Tower of Hera)': (False, True, 'Map', 0x75, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Tower of Hera'),
'Small Key (Escape)': (False, False, 'SmallKey', 0xA0, 'A Small Key for\nEscape', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Hyrule Castle'),
'Big Key (Escape)': (False, False, 'BigKey', 0x9F, 'A Big Key for\nEscape', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Hyrule Castle'),
'Compass (Escape)': (False, True, 'Compass', 0x8F, 'A Compass for\nEscape', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Hyrule Castle'),
'Map (Escape)': (False, True, 'Map', 0x7F, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Hyrule Castle'),
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A Small Key for\nAganim\'s Tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Castle Tower'),
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A Small Key for\nDark Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Palace of Darkness'),
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A Big Key for\nDark Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Palace of Darkness'),
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'A Compass for\nDark Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Palace of Darkness'),
'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Palace of Darkness'),
'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 'A Small Key for\nThieves Town', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Thieves\' Town'),
'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 'A Big Key for\nThieves Town', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Thieves\' Town'),
'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 'A Compass for\nThieves Town', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Thieves\' Town'),
'Map (Thieves Town)': (False, True, 'Map', 0x74, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Thieves\' Town'),
'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 'A Small Key for\nSkull Woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Skull Woods'),
'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 'A Big Key for\nSkull Woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Skull Woods'),
'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 'A Compass for\nSkull Woods', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Skull Woods'),
'Map (Skull Woods)': (False, True, 'Map', 0x77, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Skull Woods'),
'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 'A Small Key for\nSwamp Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Swamp Palace'),
'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 'A Big Key for\nSwamp Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Swamp Palace'),
'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 'A compass for\nSwamp Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Swamp Palace'),
'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Swamp Palace'),
'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 'A Small Key for\nIce Palace', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Ice Palace'),
'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 'A Big Key for\nIce Palace', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Ice Palace'),
'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 'A Compass for\nIce Palace', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Ice Palace'),
'Map (Ice Palace)': (False, True, 'Map', 0x76, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Ice Palace'),
'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 'A Small Key for\nMisery Mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Misery Mire'),
'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 'A Big Key for\nMisery Mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Misery Mire'),
'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 'A Compass for\nMisery Mire', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Misery Mire'),
'Map (Misery Mire)': (False, True, 'Map', 0x78, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Misery Mire'),
'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 'A Small Key for\nTurtle Rock', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Turtle Rock'),
'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 'A Big Key for\nTurtle Rock', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Turtle Rock'),
'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 'A Compass for\nTurtle Rock', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'A compass to Turtle Rock'),
'Map (Turtle Rock)': (False, True, 'Map', 0x73, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Turtle Rock'),
'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 'A Small Key for\nGanon\'s Tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key to Ganon\'s Tower'),
'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 'A Big Key for\nGanon\'s Tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'A big key to Ganon\'s Tower'),
'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 'A Compass for\nGanon\'s Tower', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a comapss to Ganon\'s Tower'),
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'A map to Ganon\'s Tower'),
'Small Key (Universal)': (False, True, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key'),
'Small Key (Universal)': (False, True, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'A small key'),
'Teleporter': (False, True, None, 0xB8, 'A teleporter', 'and the teleporter', 'the teleporting kid', 'teleportation for sale', 'teleporting fungus', 'teleporter boy warps again', 'A teleporter'),
'IFrames (10)': (False, False, None, 0xB9, 'More invincibility frames', 'and the i-frames', 'the invincible kid', 'invincibility for sale', 'invincible fungus', 'invincble boy damaged again', 'Some invincibility frames'),
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'Nothing'),
'Red Potion': (False, False, None, 0x2E, None, None, None, None, None, None, None),
'Green Potion': (False, False, None, 0x2F, None, None, None, None, None, None, None),
'Blue Potion': (False, False, None, 0x30, None, None, None, None, None, None, None),
'Bee': (False, False, None, 0x0E, None, None, None, None, None, None, None),
'Small Heart': (False, False, None, 0x42, None, None, None, None, None, None, None),
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None, None),
'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None, None),
}
-2186
View File
File diff suppressed because it is too large Load Diff
+173 -1151
View File
File diff suppressed because it is too large Load Diff
-1148
View File
File diff suppressed because it is too large Load Diff
-458
View File
@@ -1,458 +0,0 @@
import argparse
import asyncio
import functools
import json
import logging
import re
import shlex
import ssl
import urllib.request
import zlib
import aioconsole
import websockets
import Items
import PotShuffle
import Regions
import source.dungeon.EnemyList as EnemyList
import source.rom.DataTables as DataTables
from BaseClasses import PotFlags, PotItem
from MultiClient import (
ReceivedItem,
get_item_name_from_id,
get_location_name_from_address,
)
class Client:
def __init__(self, socket):
self.socket = socket
self.auth = False
self.name = None
self.team = None
self.slot = None
self.send_index = 0
class Context:
def __init__(self, host, port, password):
self.data_filename = None
self.save_filename = None
self.disable_save = False
self.player_names = {}
self.rom_names = {}
self.remote_items = set()
self.locations = {}
self.host = host
self.port = port
self.password = password
self.server = None
self.countdown_timer = 0
self.clients = []
self.received_items = {}
self.lookup_name_to_id = {}
self.lookup_id_to_name = {}
self.disable_client_forfeit = False
async def send_msgs(websocket, msgs):
if not websocket or not websocket.open or websocket.closed:
return
try:
await websocket.send(json.dumps(msgs))
except websockets.ConnectionClosed:
pass
def broadcast_all(ctx : Context, msgs):
for client in ctx.clients:
if client.auth:
asyncio.create_task(send_msgs(client.socket, msgs))
def broadcast_team(ctx : Context, team, msgs):
for client in ctx.clients:
if client.auth and client.team == team:
asyncio.create_task(send_msgs(client.socket, msgs))
def notify_all(ctx : Context, text):
logging.info("Notice (all): %s" % text)
broadcast_all(ctx, [['Print', text]])
def notify_team(ctx : Context, team : int, text : str):
logging.info("Notice (Team #%d): %s" % (team+1, text))
broadcast_team(ctx, team, [['Print', text]])
def notify_client(client : Client, text : str):
if not client.auth:
return
logging.info("Notice (Player %s in team %d): %s" % (client.name, client.team+1, text))
asyncio.create_task(send_msgs(client.socket, [['Print', text]]))
async def server(websocket, ctx : Context):
client = Client(websocket)
ctx.clients.append(client)
try:
await on_client_connected(ctx, client)
async for data in websocket:
for msg in json.loads(data):
if len(msg) == 1:
cmd = msg
args = None
else:
cmd = msg[0]
args = msg[1]
await process_client_cmd(ctx, client, cmd, args)
except Exception as e:
if not isinstance(e, websockets.WebSocketException):
logging.exception(e)
finally:
await on_client_disconnected(ctx, client)
ctx.clients.remove(client)
async def on_client_connected(ctx : Context, client : Client):
await send_msgs(client.socket, [['RoomInfo', {
'password': ctx.password is not None,
'players': [(client.team, client.slot, client.name) for client in ctx.clients if client.auth]
}]])
async def on_client_disconnected(ctx : Context, client : Client):
if client.auth:
await on_client_left(ctx, client)
async def on_client_joined(ctx : Context, client : Client):
notify_all(ctx, "%s (Team #%d) has joined the game" % (client.name, client.team + 1))
async def on_client_left(ctx : Context, client : Client):
notify_all(ctx, "%s (Team #%d) has left the game" % (client.name, client.team + 1))
async def countdown(ctx : Context, timer):
notify_all(ctx, f'[Server]: Starting countdown of {timer}s')
if ctx.countdown_timer:
ctx.countdown_timer = timer
return
ctx.countdown_timer = timer
while ctx.countdown_timer > 0:
notify_all(ctx, f'[Server]: {ctx.countdown_timer}')
ctx.countdown_timer -= 1
await asyncio.sleep(1)
notify_all(ctx, f'[Server]: GO')
def get_connected_players_string(ctx : Context):
auth_clients = [c for c in ctx.clients if c.auth]
if not auth_clients:
return 'No player connected'
auth_clients.sort(key=lambda c: (c.team, c.slot))
current_team = 0
text = 'Team #1: '
for c in auth_clients:
if c.team != current_team:
text += f':: Team #{c.team + 1}: '
current_team = c.team
text += f'{c.name} '
return 'Connected players: ' + text[:-1]
def get_received_items(ctx : Context, team, player):
return ctx.received_items.setdefault((team, player), [])
def tuplize_received_items(items):
return [(item.item, item.location, item.player) for item in items]
def send_new_items(ctx : Context):
for client in ctx.clients:
if not client.auth:
continue
items = get_received_items(ctx, client.team, client.slot)
if len(items) > client.send_index:
asyncio.create_task(send_msgs(client.socket, [['ReceivedItems', (client.send_index, tuplize_received_items(items)[client.send_index:])]]))
client.send_index = len(items)
def forfeit_player(ctx : Context, team, slot):
all_locations = set(ctx.lookup_id_to_name.keys())
notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
register_location_checks(ctx, team, slot, all_locations)
def register_location_checks(ctx : Context, team, slot, locations):
found_items = False
for location in locations:
if (location, slot) in ctx.locations:
target_item, target_player = ctx.locations[(location, slot)]
if target_player != slot or slot in ctx.remote_items:
found = False
recvd_items = get_received_items(ctx, team, target_player)
for recvd_item in recvd_items:
if recvd_item.location == location and recvd_item.player == slot:
found = True
break
if not found:
new_item = ReceivedItem(target_item, location, slot)
recvd_items.append(new_item)
if slot != target_player:
broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]])
loc_name = get_location_name_from_address(ctx, location)
logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], loc_name))
found_items = True
send_new_items(ctx)
if found_items and not ctx.disable_save:
try:
with open(ctx.save_filename, "wb") as f:
jsonstr = json.dumps((list(ctx.rom_names.items()),
[(k, [i.__dict__ for i in v]) for k, v in ctx.received_items.items()]))
f.write(zlib.compress(jsonstr.encode("utf-8")))
except Exception as e:
logging.exception(e)
async def process_client_cmd(ctx : Context, client : Client, cmd, args):
if type(cmd) is not str:
await send_msgs(client.socket, [['InvalidCmd']])
return
if cmd == 'Connect':
if not args or type(args) is not dict or \
'password' not in args or type(args['password']) not in [str, type(None)] or \
'rom' not in args or type(args['rom']) is not list:
await send_msgs(client.socket, [['InvalidArguments', 'Connect']])
return
errors = set()
if ctx.password is not None and args['password'] != ctx.password:
errors.add('InvalidPassword')
if tuple(args['rom']) not in ctx.rom_names:
errors.add('InvalidRom')
else:
team, slot = ctx.rom_names[tuple(args['rom'])]
if any([c.slot == slot and c.team == team for c in ctx.clients if c.auth]):
errors.add('SlotAlreadyTaken')
else:
client.name = ctx.player_names[(team, slot)]
client.team = team
client.slot = slot
if errors:
await send_msgs(client.socket, [['ConnectionRefused', list(errors)]])
else:
client.auth = True
reply = [['Connected', [(client.team, client.slot), [(p, n) for (t, p), n in ctx.player_names.items() if t == client.team]]]]
items = get_received_items(ctx, client.team, client.slot)
if items:
reply.append(['ReceivedItems', (0, tuplize_received_items(items))])
client.send_index = len(items)
await send_msgs(client.socket, reply)
await on_client_joined(ctx, client)
if not client.auth:
return
if cmd == 'Sync':
items = get_received_items(ctx, client.team, client.slot)
if items:
client.send_index = len(items)
await send_msgs(client.socket, [['ReceivedItems', (0, tuplize_received_items(items))]])
if cmd == 'LocationChecks':
if type(args) is not list:
await send_msgs(client.socket, [['InvalidArguments', 'LocationChecks']])
return
register_location_checks(ctx, client.team, client.slot, args)
if cmd == 'LocationScouts':
if type(args) is not list:
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
return
locs = []
for location in args:
if type(location) is not int or 0 >= location > len(ctx.lookup_id_to_name.keys()):
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
return
loc_name = list(ctx.lookup_id_to_name.keys())[location - 1]
target_item, target_player = ctx.locations[(ctx.lookup_name_to_id[loc_name], client.slot)]
replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D}
item_type = [i[2] for i in Items.item_table.values() if type(i[3]) is int and i[3] == target_item]
if item_type:
target_item = replacements.get(item_type[0], target_item)
locs.append([loc_name, location, target_item, target_player])
logging.info(f"{client.name} in team {client.team+1} scouted {', '.join([l[0] for l in locs])}")
await send_msgs(client.socket, [['LocationInfo', [l[1:] for l in locs]]])
if cmd == 'Say':
if type(args) is not str or not args.isprintable():
await send_msgs(client.socket, [['InvalidArguments', 'Say']])
return
notify_all(ctx, client.name + ': ' + args)
if args.startswith('!players'):
notify_all(ctx, get_connected_players_string(ctx))
if args.startswith('!forfeit'):
if ctx.disable_client_forfeit:
notify_client(client, 'Client-initiated forfeits are disabled. Please ask the host of this game to forfeit on your behalf.')
else:
forfeit_player(ctx, client.team, client.slot)
if args.startswith('!countdown'):
try:
timer = int(args.split()[1])
except (IndexError, ValueError):
timer = 10
asyncio.create_task(countdown(ctx, timer))
def set_password(ctx : Context, password):
ctx.password = password
logging.warning('Password set to ' + password if password is not None else 'Password disabled')
async def console(ctx : Context):
while True:
input = await aioconsole.ainput()
command = shlex.split(input)
if not command:
continue
if command[0] == '/exit':
ctx.server.ws_server.close()
break
if command[0] == '/players':
logging.info(get_connected_players_string(ctx))
if command[0] == '/password':
set_password(ctx, command[1] if len(command) > 1 else None)
if command[0] == '/kick' and len(command) > 1:
team = int(command[2]) - 1 if len(command) > 2 and command[2].isdigit() else None
for client in ctx.clients:
if client.auth and client.name.lower() == command[1].lower() and (team is None or team == client.team):
if client.socket and not client.socket.closed:
await client.socket.close()
if command[0] == '/forfeitslot' and len(command) > 1 and command[1].isdigit():
if len(command) > 2 and command[2].isdigit():
team = int(command[1]) - 1
slot = int(command[2])
else:
team = 0
slot = int(command[1])
forfeit_player(ctx, team, slot)
if command[0] == '/forfeitplayer' and len(command) > 1:
team = int(command[2]) - 1 if len(command) > 2 and command[2].isdigit() else None
for client in ctx.clients:
if client.auth and client.name.lower() == command[1].lower() and (team is None or team == client.team):
if client.socket and not client.socket.closed:
forfeit_player(ctx, client.team, client.slot)
if command[0] == '/senditem' and len(command) > 2:
[(player, item)] = re.findall(r'\S* (\S*) (.*)', input)
if item in Items.item_table:
for client in ctx.clients:
if client.auth and client.name.lower() == player.lower():
new_item = ReceivedItem(Items.item_table[item][3], "cheat console", client.slot)
get_received_items(ctx, client.team, client.slot).append(new_item)
notify_all(ctx, 'Cheat console: sending "' + item + '" to ' + client.name)
send_new_items(ctx)
else:
logging.warning("Unknown item: " + item)
if command[0][0] != '/':
notify_all(ctx, '[Server]: ' + input)
def init_lookups(ctx):
ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()}
ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()}
for super_tile, pot_list in PotShuffle.vanilla_pots.items():
for pot_index, pot in enumerate(pot_list):
if pot.item != PotItem.Hole:
if pot.item == PotItem.Key:
loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items()
if datum[1] == super_tile)
else:
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
loc_name = f'{pot.room} {descriptor}'
location_id = Regions.pot_address(pot_index, super_tile)
ctx.lookup_name_to_id[loc_name] = location_id
ctx.lookup_id_to_name[location_id] = loc_name
uw_table = DataTables.get_uw_enemy_table()
key_drop_data = {(v[1][1], v[1][2]): k for k, v in PotShuffle.key_drop_data.items() if v[0] == 'Drop'}
for super_tile, enemy_list in uw_table.room_map.items():
for index, sprite in enumerate(enemy_list):
if (super_tile, index) in key_drop_data:
loc_name = key_drop_data[(super_tile, index)]
location_id = PotShuffle.key_drop_data[loc_name][1][0]
else:
loc_name = f'{sprite.region} Enemy #{index+1}'
location_id = EnemyList.drop_address(index, super_tile)
ctx.lookup_name_to_id[loc_name] = location_id
ctx.lookup_id_to_name[location_id] = loc_name
async def main():
parser = argparse.ArgumentParser()
parser.add_argument('--host', default=None)
parser.add_argument('--port', default=38281, type=int)
parser.add_argument('--password', default=None)
parser.add_argument('--multidata', default=None)
parser.add_argument('--savefile', default=None)
parser.add_argument('--disable_save', default=False, action='store_true')
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
args = parser.parse_args()
logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
ctx = Context(args.host, args.port, args.password)
init_lookups(ctx)
ctx.data_filename = args.multidata
try:
if not ctx.data_filename:
import tkinter
import tkinter.filedialog
root = tkinter.Tk()
root.withdraw()
ctx.data_filename = tkinter.filedialog.askopenfilename(filetypes=(("Multiworld data","*multidata"),))
with open(ctx.data_filename, 'rb') as f:
jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
for team, names in enumerate(jsonobj['names']):
for player, name in enumerate(names, 1):
ctx.player_names[(team, player)] = name
ctx.rom_names = {tuple(rom): (team, slot) for slot, team, rom in jsonobj['roms']}
ctx.remote_items = set(jsonobj['remote_items'])
ctx.locations = {tuple(k): tuple(v) for k, v in jsonobj['locations']}
except Exception as e:
logging.error('Failed to read multiworld data (%s)' % e)
return
ip = urllib.request.urlopen('https://v4.ident.me', context=ssl._create_unverified_context()).read().decode('utf8') if not ctx.host else ctx.host
logging.info('Hosting game at %s:%d (%s)' % (ip, ctx.port, 'No password' if not ctx.password else 'Password: %s' % ctx.password))
ctx.disable_save = args.disable_save
if not ctx.disable_save:
if not ctx.save_filename:
ctx.save_filename = (ctx.data_filename[:-9] if ctx.data_filename[-9:] == 'multidata' else (ctx.data_filename + '_')) + 'multisave'
try:
with open(ctx.save_filename, 'rb') as f:
jsonobj = json.loads(zlib.decompress(f.read()).decode("utf-8"))
rom_names = jsonobj[0]
received_items = {tuple(k): [ReceivedItem(**i) for i in v] for k, v in jsonobj[1]}
if not all([ctx.rom_names[tuple(rom)] == (team, slot) for rom, (team, slot) in rom_names]):
raise Exception('Save file mismatch, will start a new game')
ctx.received_items = received_items
logging.info('Loaded save file with %d received items for %d players' % (sum([len(p) for p in received_items.values()]), len(received_items)))
except FileNotFoundError:
logging.error('No save data found, starting a new game')
except Exception as e:
logging.info(e)
ctx.server = websockets.serve(functools.partial(server,ctx=ctx), ctx.host, ctx.port, ping_timeout=None, ping_interval=None)
await ctx.server
await console(ctx)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.run_until_complete(asyncio.gather(*asyncio.Task.all_tasks()))
loop.close()
-110
View File
@@ -1,110 +0,0 @@
import argparse
import logging
from yaml.constructor import SafeConstructor
import RaceRandom as random
from DungeonRandomizer import parse_cli
from Main import main as DRMain
from source.classes.BabelFish import BabelFish
from source.tools.MysteryUtils import get_weights, roll_settings
def add_bool(self, node):
return self.construct_scalar(node)
SafeConstructor.add_constructor(u'tag:yaml.org,2002:bool', add_bool)
def main():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
multiargs, _ = parser.parse_known_args()
parser = argparse.ArgumentParser()
parser.add_argument('--weights', help='Path to the weights file to use for rolling game settings, urls are also valid')
parser.add_argument('--samesettings', help='Rolls settings per weights file rather than per player', action='store_true')
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
parser.add_argument('--names', default='')
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
parser.add_argument('--spoiler', default='none', choices=['none', 'settings', 'semi', 'full', 'debug'])
parser.add_argument('--no_race', action='store_true')
parser.add_argument('--print_template_yaml', action='store_true')
parser.add_argument('--suppress_rom', action='store_true')
parser.add_argument('--suppress_meta', action='store_true')
parser.add_argument('--bps', action='store_true')
parser.add_argument('--rom')
parser.add_argument('--outputpath')
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
for player in range(1, multiargs.multi + 1):
parser.add_argument(f'--p{player}', help=argparse.SUPPRESS)
args = parser.parse_args()
if args.seed is None:
random.seed(None)
seed = random.randint(0, 999999999)
else:
seed = args.seed
random.seed(seed)
seedname = f'M{random.randint(0, 999999999)}'
print(f"Generating mystery for {args.multi} player{'s' if args.multi > 1 else ''}, {seedname} Seed {seed}")
weights_cache = {}
if args.weights:
weights_cache[args.weights] = get_weights(args.weights)
print(f"Weights: {args.weights} >> {weights_cache[args.weights]['description']}")
for player in range(1, args.multi + 1):
path = getattr(args, f'p{player}')
if path:
if path not in weights_cache:
weights_cache[path] = get_weights(path)
print(f"P{player} Weights: {path} >> {weights_cache[path]['description']}")
erargs = parse_cli(['--multi', str(args.multi)])
erargs.seed = seed
erargs.names = args.names
erargs.spoiler = args.spoiler
erargs.suppress_rom = args.suppress_rom
erargs.suppress_meta = args.suppress_meta
erargs.print_template_yaml = args.print_template_yaml
erargs.bps = args.bps
erargs.race = not args.no_race
erargs.outputname = seedname
if args.outputpath:
erargs.outputpath = args.outputpath
erargs.loglevel = args.loglevel
erargs.mystery = True
if args.rom:
erargs.rom = args.rom
mw_settings = {'algorithm': False}
settings_cache = {k: (roll_settings(v) if args.samesettings else None) for k, v in weights_cache.items()}
for player in range(1, args.multi + 1):
path = getattr(args, f'p{player}') if getattr(args, f'p{player}') else args.weights
if path:
settings = settings_cache[path] if settings_cache[path] else roll_settings(weights_cache[path])
for k, v in vars(settings).items():
if v is not None:
if k == 'algorithm': # multiworld wide parameters
if not mw_settings[k]: # only use the first roll
setattr(erargs, k, v)
mw_settings[k] = True
else:
getattr(erargs, k)[player] = v
else:
raise RuntimeError(f'No weights specified for player {player}')
# set up logger
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[erargs.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel)
DRMain(erargs, seed, BabelFish())
if __name__ == '__main__':
main()
-1807
View File
File diff suppressed because it is too large Load Diff
-523
View File
@@ -1,523 +0,0 @@
"""
Helper functions to deliver entrance/exit/region sets to OWG rules.
"""
from BaseClasses import Entrance, Region
from OWEdges import OWTileRegions
# Cave regions that superbunny can get through - but only with a sword.
sword_required_superbunny_mirror_regions = ["Spiral Cave (Top)"]
# Cave regions that superbunny can get through - but only with boots.
boots_required_superbunny_mirror_regions = ["Two Brothers House"]
# Cave locations that superbunny can access - but only with boots.
boots_required_superbunny_mirror_locations = [
"Sahasrahla's Hut - Left",
"Sahasrahla's Hut - Middle",
"Sahasrahla's Hut - Right",
]
# Entrances that can't be superbunny-mirrored into.
invalid_mirror_bunny_entrances = [
"Hype Cave",
"Bonk Fairy (Dark)",
"Thieves Town",
"Hammer Peg Cave",
"Brewery",
"Hookshot Cave",
"Dark Lake Hylia Ledge Fairy",
"Dark Lake Hylia Ledge Spike Cave",
"Palace of Darkness",
"Misery Mire",
"Turtle Rock",
"Bonk Rock Cave",
"Bonk Fairy (Light)",
"50 Rupee Cave",
"20 Rupee Cave",
"Checkerboard Cave",
"Light Hype Fairy",
"Waterfall of Wishing",
"Light World Bomb Hut",
"Mini Moldorm Cave",
"Ice Rod Cave",
"Sanctuary Grave",
"Kings Grave",
"Sanctuary Grave",
"Hyrule Castle Secret Entrance Drop",
"Skull Woods Second Section Hole",
"Skull Woods First Section Hole (North)",
]
# Interior locations that can be accessed with superbunny state.
superbunny_accessible_locations = [
"Waterfall of Wishing - Left",
"Waterfall of Wishing - Right",
"King's Tomb",
"Floodgate",
"Floodgate Chest",
"Cave 45",
"Bonk Rock Cave",
"Brewery",
"C-Shaped House",
"Chest Game",
"Mire Shed - Left",
"Mire Shed - Right",
"Secret Passage",
"Ice Rod Cave",
"Pyramid Fairy - Left",
"Pyramid Fairy - Right",
"Superbunny Cave - Top",
"Superbunny Cave - Bottom",
"Blind's Hideout - Left",
"Blind's Hideout - Right",
"Blind's Hideout - Far Left",
"Blind's Hideout - Far Right",
"Kakariko Well - Left",
"Kakariko Well - Middle",
"Kakariko Well - Right",
"Kakariko Well - Bottom",
"Kakariko Tavern",
"Library",
"Spiral Cave",
] + boots_required_superbunny_mirror_locations
# TODO: Add pottery locations
def get_non_mandatory_exits(world, player):
"""
Entrances that can be reached with full equipment using overworld glitches and don't need to be an exit.
The following are still be mandatory exits:
Open:
Turtle Rock Isolated Ledge Entrance
Skull Woods Second Section Door (West) (or Skull Woods Final Section)
Inverted:
Two Brothers House (West)
Desert Palace Entrance (East)
"""
yield 'Bumper Cave (Top)'
yield 'Death Mountain Return Cave (West)'
yield 'Hookshot Cave Back Entrance'
if world.is_tile_swapped(0x30, player):
yield 'Desert Palace Entrance (North)'
yield 'Desert Palace Entrance (West)'
else:
yield 'Desert Palace Entrance (East)'
if world.is_tile_swapped(0x1b, player):
yield 'Agahnims Tower'
yield 'Hyrule Castle Entrance (West)'
yield 'Hyrule Castle Entrance (East)'
if not world.is_tile_swapped(0x05, player):
yield 'Dark Death Mountain Ledge (West)'
yield 'Dark Death Mountain Ledge (East)'
#yield 'Mimic Cave' #TODO: This was here, I don't think this is true
def get_boots_clip_exits_lw(world, player):
"""
Special Light World region exits that require boots clips.
"""
for name, parent_region, target_region in boots_clips_local:
if world.is_tile_lw_like(OWTileRegions[parent_region], player):
yield(name, parent_region, target_region)
for names, parent_regions, target_regions in boots_clips:
region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions), True)
if region_pair and region_pair[2]:
assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}'
yield(region_pair[0], region_pair[1], region_pair[2])
def get_boots_clip_exits_dw(world, player):
"""
Special Dark World region exits that require boots clips.
"""
for name, parent_region, target_region in boots_clips_local:
if not world.is_tile_lw_like(OWTileRegions[parent_region], player):
yield(name, parent_region, target_region)
for names, parent_regions, target_regions in boots_clips:
region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions), False)
if region_pair and region_pair[2]:
assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}'
yield(region_pair[0], region_pair[1], region_pair[2])
def get_glitched_speed_drops_lw(world, player):
"""
Light World drop-down ledges that require glitched speed.
"""
def get_glitched_speed_drops_dw(world, player):
"""
Dark World drop-down ledges that require glitched speed.
"""
def get_mirror_clip_spots(world, player):
"""
Out of bounds transitions using the mirror
"""
for name, parent_region, target_region in mirror_clips_local:
if not world.is_tile_lw_like(OWTileRegions[parent_region], player):
yield(name, parent_region, target_region)
for names, parent_regions, target_regions in mirror_clips:
region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions), False)
if region_pair and region_pair[2] and not world.is_tile_lw_like(OWTileRegions[region_pair[1]], player):
assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}'
yield(region_pair[0], region_pair[1], region_pair[2])
def get_mirror_offset_spots(world, player):
"""
Mirror shenanigans placing a mirror portal with a broken camera
"""
# TODO: These really should check to see if there is a mirrorless path to the mirror portal
# but being that OWG is very very open, it's very unlikely there isn't a path, but possible
for names, parent_regions, target_regions, path_to in mirror_offsets:
region_pair = get_world_pair(world, player, get_region_pairs(world, player, names, parent_regions, target_regions, path_to), False)
if region_pair and region_pair[2] and not world.is_tile_lw_like(OWTileRegions[region_pair[1]], player):
assert region_pair[0], f'Exit name missing in OWG pairing from {region_pair[1]} to {region_pair[2]}'
yield(region_pair[0], region_pair[1], region_pair[2], region_pair[3])
def get_swapped_status(world, player, parents, targets):
if parents[0]:
parent_swapped = world.is_tile_swapped(OWTileRegions[parents[0]], player)
else:
parent_swapped = world.is_tile_swapped(OWTileRegions[parents[1]], player)
if targets[0] and targets[0] in (glitch_regions[0] + glitch_regions[1]):
target_swapped = targets[0] in glitch_regions[1]
else:
if targets[0]:
target_swapped = world.is_tile_swapped(OWTileRegions[targets[0]], player)
else:
target_swapped = world.is_tile_swapped(OWTileRegions[targets[1]], player)
return parent_swapped, target_swapped
def get_region_pairs(world, player, names, parent_regions, target_regions, path_regions=None):
# this pairs the source region to the proper destination
region_pairs = [None, None]
parent_swapped, target_swapped = get_swapped_status(world, player, parent_regions, target_regions)
if parent_regions[0]:
region_pairs[0] = [names[0], parent_regions[0]]
if parent_swapped == target_swapped:
region_pairs[0].append(target_regions[0])
if path_regions:
region_pairs[0].append(path_regions[0])
else:
region_pairs[0].append(target_regions[1])
if path_regions:
region_pairs[0].append(path_regions[1])
if parent_regions[1]:
region_pairs[1] = [names[1], parent_regions[1]]
if parent_swapped == target_swapped:
region_pairs[1].append(target_regions[1])
if path_regions:
region_pairs[1].append(path_regions[1])
else:
region_pairs[1].append(target_regions[0])
if path_regions:
region_pairs[1].append(path_regions[0])
return region_pairs
def get_world_pair(world, player, region_pairs, get_light_world):
# this chooses the region pair that is in the right world
if region_pairs[0]:
is_lw = world.is_tile_lw_like(OWTileRegions[region_pairs[0][1]], player)
else:
is_lw = not world.is_tile_lw_like(OWTileRegions[region_pairs[1][1]], player)
if is_lw == get_light_world:
return region_pairs[0]
else:
return region_pairs[1]
def create_owg_connections(world, player):
"""
Add OWG transitions to player's world without logic
"""
create_no_logic_connections(player, world, get_boots_clip_exits_lw(world, player))
create_no_logic_connections(player, world, get_boots_clip_exits_dw(world, player))
# Glitched speed drops.
#create_no_logic_connections(player, world, get_glitched_speed_drops_lw(world, player))
#create_no_logic_connections(player, world, get_glitched_speed_drops_dw(world, player))
# Mirror clip spots.
create_no_logic_connections(player, world, get_mirror_clip_spots(world, player))
# Mirror offset spots.
for data in get_mirror_offset_spots(world, player):
create_no_logic_connections(player, world, [data[0:3]])
def overworld_glitches_rules(world, player):
# Boots-accessible locations.
set_owg_rules(player, world, get_boots_clip_exits_lw(world, player), lambda state: state.can_boots_clip_lw(player))
set_owg_rules(player, world, get_boots_clip_exits_dw(world, player), lambda state: state.can_boots_clip_dw(player))
# Glitched speed drops.
#set_owg_rules(player, world, get_glitched_speed_drops_lw(world, player), lambda state: state.can_get_glitched_speed_lw(player))
#set_owg_rules(player, world, get_glitched_speed_drops_dw(world, player), lambda state: state.can_get_glitched_speed_dw(player))
# Mirror clip spots.
# TODO: Should this also require can_boots_clip
set_owg_rules(player, world, get_mirror_clip_spots(world, player), lambda state: state.has_Mirror(player))
# Mirror offset spots.
for data in get_mirror_offset_spots(world, player):
set_owg_rules(player, world, [data[0:3]], lambda state: state.has_Mirror(player) and state.can_boots_clip_lw(player) and state.can_reach(data[3], None, player))
# Regions that require the boots and some other stuff.
# TODO: Revisit below when we can guarantee water walk
# if world.mode[player] != 'inverted':
# add_alternate_rule(world.get_entrance('Waterfall of Wishing Cave Entry', player), lambda state: state.has_Pearl(player) or state.has_Boots(player))
# else:
# add_alternate_rule(world.get_entrance('Waterfall of Wishing Cave Entry', player), lambda state: state.has_Pearl(player))
# Zora's Ledge via waterwalk setup.
#add_alternate_rule(world.get_location('Zora\'s Ledge', player), lambda state: state.has_Boots(player)) #revisit when we can guarantee water walk
# Adding additional item requirements to OWG Clips
if world.is_tile_swapped(0x18, player) != world.is_tile_swapped(0x28, player):
add_additional_rule(world.get_entrance('Kakariko To Dig Game Hook Clip', player), lambda state: state.has('Hookshot', player))
else:
add_additional_rule(world.get_entrance('VoO To Dig Game Hook Clip', player), lambda state: state.has('Hookshot', player))
add_additional_rule(world.get_entrance('Tree Line Water Clip', player), lambda state: state.has('Flippers', player))
add_additional_rule(world.get_entrance('Dark Tree Line Water Clip', player), lambda state: state.has('Flippers', player))
# Bunny pocket
if not world.is_tile_swapped(0x00, player):
add_alternate_rule(world.get_entrance("Skull Woods Final Section", player), lambda state: state.can_bunny_pocket(player) and state.has("Fire Rod", player))
if world.is_tile_swapped(0x05, player):
add_alternate_rule(world.get_entrance("DM Hammer Bridge (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player))
add_alternate_rule(world.get_entrance("DM Hammer Bridge (East)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player))
if not world.is_tile_swapped(0x18, player):
add_alternate_rule(world.get_entrance("Bush Yard Pegs (Inner)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player))
add_alternate_rule(world.get_entrance("Bush Yard Pegs (Outer)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player))
if world.is_tile_swapped(0x22, player):
add_alternate_rule(world.get_entrance("Blacksmith Ledge Peg (West)", player), lambda state: state.can_bunny_pocket(player) and state.has("Hammer", player))
def add_alternate_rule(entrance, rule):
old_rule = entrance.access_rule
entrance.access_rule = lambda state: old_rule(state) or rule(state)
def add_additional_rule(entrance, rule):
old_rule = entrance.access_rule
entrance.access_rule = lambda state: old_rule(state) and rule(state)
def create_no_logic_connections(player, world, connections, connect_external=False):
for entrance, parent_region, target_region, *_ in connections:
parent = world.get_region(parent_region, player)
if isinstance(target_region, Region):
target_region = target_region.name
if connect_external and target_region.endswith(" Portal"):
target = world.get_portal(target_region[:-7], player).find_portal_entrance().parent_region
else:
target = world.get_region(target_region, player)
connection = Entrance(player, entrance, parent)
connection.spot_type = 'OWG'
parent.exits.append(connection)
connection.connect(target)
def set_owg_rules(player, world, connections, default_rule):
for entrance, _, _, *rule_override in connections:
connection = world.get_entrance(entrance, player)
rule = rule_override[0] if len(rule_override) > 0 else default_rule
connection.access_rule = rule
glitch_regions = (['Central Cliffs', 'Eastern Cliff', 'Desert Northern Cliffs'],
['Dark Central Cliffs', 'Darkness Cliff', 'Mire Northern Cliffs'])
# same screen clips, no Tile Flip OWR implications
boots_clips_local = [ # (name, from_region, to_region)
('Hera Ascent Clip', 'West Death Mountain (Bottom)', 'West Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists
('WDDM Bomb Clip', 'West Dark Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists
('Ganons Tower Screen Wrap Clip', 'West Dark Death Mountain (Bottom)', 'GT Stairs'), # This only gets you to the GT entrance
('Spectacle Rock Ledge Clip', 'West Death Mountain (Top)', 'Spectacle Rock Ledge'),
('Floating Island Clip', 'East Death Mountain (Top East)', 'Death Mountain Floating Island'),
('Floating Island Return Clip', 'Death Mountain Floating Island', 'East Death Mountain (Top East)'),
#('DW Floating Island Clip', 'East Dark Death Mountain (Bottom)', 'Death Mountain Floating Island'), #cannot guarantee camera correction
('EDM East Dropdown Clip', 'East Death Mountain (Top East)', 'East Death Mountain (Bottom Left)'),
('EDM Hammer Bypass Teleport', 'East Death Mountain (Top West)', 'East Death Mountain (Top East)'),
('EDDM West Dropdown Clip', 'East Dark Death Mountain (Top)', 'East Dark Death Mountain (Bottom Left)'),
('WDM To EDM Bottom Clip', 'East Death Mountain (Bottom Left)', 'East Death Mountain (Bottom)'),
('WDDM To EDDM Bottom Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'),
('TR Bridge Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge'),
('TR Pegs Ledge Clip', 'Death Mountain TR Pegs Area', 'Death Mountain TR Pegs Ledge'),
('TR Pegs Ledge Descent Clip', 'Death Mountain TR Pegs Ledge', 'Death Mountain TR Pegs Area'), # inverted only, but doesn't hurt to exist always
('Turtle Rock Ledge Clip', 'Turtle Rock Area', 'Turtle Rock Ledge'),
('Mountain Pass To Ledge Clip', 'Mountain Pass Area', 'Mountain Pass Ledge'),
('Bumper Cave Ledge Clip', 'Bumper Cave Area', 'Bumper Cave Ledge'),
('Mountain Ledge Drop Clip', 'Mountain Pass Ledge', 'Mountain Pass Entry'),
('Bumper Cave Ledge Drop Clip', 'Bumper Cave Ledge', 'Bumper Cave Entry'),
('Potion Shop Northbound Rock Bypass Clip', 'Potion Shop Area', 'Potion Shop Northeast'),
('Potion Shop Southbound Rock Bypass Clip', 'Potion Shop Northeast', 'Potion Shop Area'),
('Dark Witch Northbound Rock Bypass Clip', 'Dark Witch Area', 'Dark Witch Northeast'),
('Dark Witch Southbound Rock Bypass Clip', 'Dark Witch Northeast', 'Dark Witch Area'),
('Hyrule Castle To Water Clip', 'Hyrule Castle Area', 'Hyrule Castle Water'), #fake flipper
#('Bat Cave River Clip Spot', 'Blacksmith Area', 'Blacksmith Ledge'), #TODO: This should be added in MG (screenwrap transition)
('Maze Race Item Get Ledge Clip', 'Maze Race Area', 'Maze Race Prize'),
#('Hobo Water Clip', 'Stone Bridge South Area', 'Stone Bridge Water'), # TODO: Doesn't work with OW Free Terrain
('Tree Line Water Clip', 'Tree Line Area', 'Tree Line Water'), #requires flippers
('Dark Tree Line Water Clip', 'Dark Tree Line Area', 'Dark Tree Line Water'), #requires flippers
('Desert To Teleporter Clip', 'Desert Area', 'Desert Teleporter Ledge'),
('Mire To Teleporter Clip', 'Mire Area', 'Mire Teleporter Ledge'),
('Desert To Bombos Tablet Clip', 'Desert Area', 'Bombos Tablet Ledge'),
('Lake Hylia To Shore Clip', 'Lake Hylia Northwest Bank', 'Lake Hylia South Shore'),
('Ice Lake To Shore Clip', 'Ice Lake Northwest Bank', 'Ice Lake Southwest Ledge')
#('Desert Pass To Zora Clip', 'Desert Pass Area', 'Zoras Domain', ) #revisit when Zora is shuffled
]
# Common structure for cross-screen connections:
# (name, from_region, to_region) <- each three consists of [LW, DW]
# This is so Tile Flip OWR can properly connect both connections, and simultaneously be aware of which one requires pearl
# Note: Some clips have no way to reach the OOB area, and others have no way to get from the OOB area
# to a proper destination, these are marked with 'None'; these connections will not be made
boots_clips = [
(['Lumberjack DMA Clip', 'Dark Lumberjack DMA Clip'], ['Lumberjack Area', 'Dark Lumberjack Area'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)']),
(['Lumberjack DMD Clip', None], ['West Death Mountain (Top)', None], ['Lumberjack Area', 'Dark Lumberjack Area']),
(['DM Glitched Bridge Clip', 'DDM Glitched Bridge Clip'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)']),
(['WDM to EDM Top Clip', 'WDDM to EDDM Top Clip'], ['West Death Mountain (Top)', 'West Dark Death Mountain (Top)'], ['East Death Mountain (Top West)', None]),
(['Sanctuary DMD Clip', 'Chapel DMD Clip'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Sanctuary Area', 'Dark Chapel Area']),
(['Graveyard Ledge DMD Clip', 'Dark Graveyard DMD Clip', ], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Graveyard Ledge', 'Dark Graveyard North']),
(['Kings Grave DMD Clip', 'Dark Kings Grave DMD Clip'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Kings Grave Area', None]),
(['EDM to WDM Top Clip', 'EDDM To WDDM Clip'], ['East Death Mountain (Top West)', 'East Dark Death Mountain (Top)'], ['West Death Mountain (Top)', 'West Dark Death Mountain (Top)']),
(['EDM To TR Pegs Clip', 'EDDM To TR Clip'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)'], ['Death Mountain TR Pegs Area', None]),
(['EDM DMD FAWT Clip', 'Dark Witch DMD FAWT Clip'], ['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], ['Potion Shop Area', 'Dark Witch Area']),
(['WDM DMD To River Bend Clip', 'WDDM DMD To Qirn Jump Clip'], ['East Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom Left)'], ['River Bend Area', 'Qirn Jump Area']),
(['EDM DMD To River Bend Clip', 'EDDM DMD To Qirn Jump Clip'], ['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], ['River Bend Area', 'Qirn Jump Area']),
(['TR Pegs To EDM Clip', 'TR To EDDM Clip'], ['Death Mountain TR Pegs Area', 'Turtle Rock Area'], ['East Death Mountain (Top East)', 'East Dark Death Mountain (Top)']),
(['Zora DMD Clip', 'Catfish DMD Clip'], ['Death Mountain TR Pegs Area', 'Turtle Rock Area'], ['Zora Waterfall Area', 'Catfish Area']),
(['Mountain Pass To Pond Clip', 'Bumper Cave To Pond Clip'], ['Mountain Pass Area', 'Bumper Cave Area'], ['Kakariko Pond Area', 'Outcast Pond Area']),
(['Zora Waterfall Ledge Clip', 'Catfish Ledge Clip'], ['Zora Waterfall Area', 'Catfish Area'], ['Zora Approach Area', 'Catfish Approach Area']),
#(['Pond DMA Clip', 'Dark Pond DMA Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)']), #cannot guarantee camera correction
(['Pond To Mountain Pass Clip', 'Pond To Bumper Cave Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['Mountain Pass Area', 'Bumper Cave Area']),
(['Pond To Bonk Rocks Clip', 'Pond To Chapel Clip'], ['Kakariko Pond Area', 'Outcast Pond Area'], ['Bonk Rock Ledge', 'Dark Chapel Area']),
(['River Bend To Potion Shop Clip', 'Qirn Jump To Dark Witch Clip'], ['River Bend East Bank', 'Qirn Jump East Bank'], ['Potion Shop Area', 'Dark Witch Area']),
(['River Bend To Wooden Bridge Clip', 'Qirn Jump To Broken Bridge North Clip'], ['River Bend East Bank', 'Qirn Jump East Bank'], ['Wooden Bridge Area', 'Broken Bridge Northeast']),
(['River Bend To Broken Bridge Clip', 'Qirn Jump To Broken Bridge Clip'], ['River Bend East Bank', 'Qirn Jump East Bank'], [None, 'Broken Bridge Area']),
(['Potion Shop To EP Clip', 'Dark Witch To PoD Clip'], ['Potion Shop Area', 'Dark Witch Area'], ['Eastern Palace Area', 'Palace of Darkness Area']),
(['Potion Shop To River Bend Clip', 'Dark Witch To Qirn Jump Clip'], ['Potion Shop Area', 'Dark Witch Area'], ['River Bend East Bank', 'Qirn Jump East Bank']),
(['Potion Shop To Zora Approach Clip', 'Dark Witch To Catfish Approach Clip'], ['Potion Shop Northeast', 'Dark Witch Northeast'], ['Zora Approach Area', 'Catfish Approach Area']),
(['Zora Approach To Potion Shop Clip', 'Catfish Approach To Dark Witch Clip'], ['Zora Approach Area', 'Catfish Approach Area'], ['Potion Shop Area', 'Dark Witch Area']),
(['Zora Approach To PoD Clip', 'Catfish Approach To PoD Clip'], ['Zora Approach Area', 'Catfish Approach Area'], [None, 'Palace of Darkness Area']),
(['Kakariko Bomb Hut Clip', 'VoO To Dig Game Clip'], ['Kakariko Southwest', 'Village of Outcasts'], ['Maze Race Area', 'Dig Game Area']),
(['Kakariko To Dig Game Hook Clip', 'VoO To Dig Game Hook Clip'], ['Kakariko Southwest', 'Village of Outcasts'], [None, 'Dig Game Ledge']), #requires hookshot
(['Forgotten Forest To Blacksmith Clip', None], ['Forgotten Forest Area', None], ['Hyrule Castle Water', 'Pyramid Water']), #fake flipper
(['Wooden Bridge To Dunes Clip', 'Broken Bridge To Dunes Clip'], ['Wooden Bridge Area', 'Broken Bridge West'], ['Sand Dunes Area', 'Dark Dunes Area']),
(['Wooden Bridge To Water Clip', 'Broken Bridge To Water Clip'], ['Wooden Bridge Area', 'Broken Bridge West'], [None, 'Pyramid Water']), #fake flipper
(['Eastern Palace To Zora Approach Clip', None], ['Eastern Palace Area', None], ['Zora Approach Area', 'Catfish Approach Area']),
(['Eastern Palace To Nook Clip', None], ['Eastern Palace Area', None], ['Eastern Nook Area', 'Darkness Nook Area']),
(['Eastern Palace To Cliff Clip', 'PoD To Cliff Clip'], ['Eastern Palace Area', 'Palace of Darkness Area'], ['Eastern Cliff', 'Darkness Cliff']),
(['Sand Dunes To Cliff Clip', 'Dark Dunes To Cliff Clip'], ['Sand Dunes Area', 'Dark Dunes Area'], ['Eastern Cliff', 'Darkness Cliff']),
(['Sand Dunes To Water Clip', 'Dark Dunes To Water Clip'], ['Sand Dunes Area', 'Dark Dunes Area'], [None, 'Pyramid Water']), #fake flipper
(['Maze Race To Desert Ledge Clip', 'Dig Game To Mire Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Ledge', 'Mire Area']),
(['Maze Race To Desert Boss Clip', 'Dig Game To Desert Boss Clip'], ['Maze Race Area', 'Dig Game Area'], ['Desert Ledge Keep', None]),
(['Suburb To Cliff Clip', 'Archery Game To Cliff Clip'], ['Kakariko Suburb Area', 'Archery Game Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']),
(['Central Bonk Rocks To Cliff Clip', 'Dark Bonk Rocks To Cliff Clip'], ['Central Bonk Rocks Area', 'Dark Bonk Rocks Area'], ['Central Cliffs', 'Dark Central Cliffs']),
(['Links House To Cliff Clip', 'Bomb Shop To Cliff Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Central Cliffs', 'Dark Central Cliffs']),
(['Stone Bridge To Cliff Clip', 'Hammer Bridge To Cliff Clip'], ['Stone Bridge South Area', 'Hammer Bridge South Area'], ['Central Cliffs', 'Dark Central Cliffs']),
(['Eastern Nook To Eastern Clip', None], ['Eastern Nook Area', None], ['Eastern Palace Area', 'Palace of Darkness Area']),
(['Eastern Nook To Ice Cave FAWT Clip', 'PoD Nook To Shopping Mall FAWT Clip'], ['Eastern Nook Area', 'Darkness Nook Area'], ['Ice Cave Area', 'Shopping Mall Area']),
(['Links To Bridge FAWT Clip', 'Bomb Shop To Hammer Bridge FAWT Clip'], ['Links House Area', 'Big Bomb Shop Area'], ['Stone Bridge North Area', 'Hammer Bridge North Area']), #fake flipper
(['Stone Bridge To Water Clip', 'Hammer Bridge To Water Clip'], ['Stone Bridge North Area', 'Hammer Bridge North Area'], [None, 'Pyramid Water']), #fake flipper
(['Desert To Maze Race Clip', None], ['Desert Ledge', None], ['Maze Race Area', 'Dig Game Area']),
(['Desert To Cliff Clip', 'Mire To Cliff Clip'], ['Desert Area', 'Mire Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']),
(['Flute Boy To Cliff Clip', 'Stumpy To Cliff Clip'], ['Flute Boy Approach Area', 'Stumpy Approach Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']),
(['Cave 45 To Cliff Clip', None], ['Cave 45 Ledge', None], ['Desert Northern Cliffs', 'Mire Northern Cliffs']),
(['C Whirlpool To Cliff Clip', 'Dark C Whirlpool To Cliff Clip'], ['C Whirlpool Area', 'Dark C Whirlpool Area'], ['Central Cliffs', 'Dark Central Cliffs']),
(['C Whirlpool Outer To Cliff Clip', 'Dark C Whirlpool Outer To Cliff Clip'], ['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], ['Central Cliffs', 'Dark Central Cliffs']),
(['C Whirlpool Portal Bomb Clip', 'Dark C Whirlpool Portal Bomb Clip'], ['C Whirlpool Portal Area', 'Dark C Whirlpool Portal Area'], ['Central Cliffs', 'Dark Central Cliffs']), # bomb TODO: bombbag not considered
(['Statues To Cliff Clip', 'Hype To Cliff Clip'], ['Statues Area', 'Hype Cave Area'], ['Central Cliffs', 'Dark Central Cliffs']),
(['Lake Hylia To Statues Clip', 'Ice Lake To Hype Clip'], ['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], ['Statues Area', 'Hype Cave Area']),
(['Lake Hylia To South Pass Clip', 'Ice Lake To South Pass Clip'], ['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], ['South Pass Area', 'Dark South Pass Area']),
(['Desert Pass To Cliff Clip', 'Swamp Nook To Cliff Clip'], ['Desert Pass Area', 'Swamp Nook Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']),
(['Desert Pass Southeast To Cliff Clip', None], ['Desert Pass Southeast', None], ['Desert Northern Cliffs', 'Mire Northern Cliffs']),
(['Dam To Cliff Clip', 'Swamp To Cliff Clip'], ['Dam Area', 'Swamp Area'], ['Desert Northern Cliffs', 'Mire Northern Cliffs']),
(['Dam To Desert Pass Southeast Clip', 'Swamp To Desert Pass Southeast Clip'], ['Dam Area', 'Swamp Area'], ['Desert Pass Southeast', None]),
(['South Pass To Lake Hylia Clip', 'South Pass To Ice Lake Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank']),
(['South Pass To Shore Clip', 'South Pass To Dark Shore Clip'], ['South Pass Area', 'Dark South Pass Area'], ['Lake Hylia South Shore', 'Ice Lake Southwest Ledge']),
#(['Octoballoon To Shore Clip', 'Bomber Corner To Shore Clip'], ['Octoballoon Area', 'Bomber Corner Area'], ['Lake Hylia South Shore', 'Ice Lake Southeast Ledge']), #map wrap hardlock risk
(['HC Water To Blacksmith Clip', 'Pyramid Water To Hammerpegs Clip'], ['Hyrule Castle Water', 'Pyramid Water'], ['Blacksmith Area', 'Hammer Pegs Area']), #TODO: THIS IS NOT A BOOTS CLIP, this is a normal connection that needs to occur somewhere
([None, 'Pyramid Water To Bomb Shop Clip'], [None, 'Pyramid Water'], ['Links House Area', 'Big Bomb Shop Area']) #TODO: THIS IS NOT A BOOTS CLIP, this is a normal connection that needs to occur somewhere
]
mirror_clips_local = [
('Desert East Mirror Clip', 'Mire Area', 'Desert Mouth'),
('EDDM Mirror Clip', 'East Dark Death Mountain (Bottom Left)', 'East Dark Death Mountain (Bottom)'),
('EDDM Mirror Clip', 'East Dark Death Mountain (Top)', 'Dark Death Mountain Ledge')
]
mirror_clips = [
([None, 'Qirn Jump Bunny DMD Clip'], [None, 'East Dark Death Mountain (Bottom Left)'], ['River Bend Area', 'Qirn Jump Area'])
]
mirror_offsets = [
(['DM Offset Mirror', 'DDM Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Courtyard Northeast', 'Pyramid Crack'], ['Pyramid Area', 'Hyrule Castle Courtyard']),
(['DM To HC Ledge Offset Mirror', 'DDM To Pyramid Offset Mirror'], ['West Death Mountain (Bottom)', 'West Dark Death Mountain (Bottom)'], ['Hyrule Castle Ledge', 'Pyramid Area'], ['Pyramid Area', 'Hyrule Castle Area'])
]
-2490
View File
File diff suppressed because it is too large Load Diff
-439
View File
@@ -1,439 +0,0 @@
# Past Feature Notes
1.4.3: File Select/End Game screen: Mirror Scroll and Pseudoboots added (Thanks Hiimcody!)
# Patch Notes
Changelog archive
* 1.4.11
* Rom fixes (all thanks to Codemann, I believe)
* Pot bug when at sprite limit
* Kodongo AI vanilla in vanilla rooms
* Issue with music silencing across certain room transitions
* Glitched: Some HMG logic fixed. Thanks Muffins! (Sorry it took me forever to get this PR in)
* Glitched: Blind fight VRAM fix. Thanks Mufffins!
* Text: Updated tourney winners. Thanks clearmouse!
* Enemizer: Banned Swamola enemy everywhere as they may be causing crashes
* Enemizer: Enemy bans from Q2 and Q3. Thank you for all the reports.
* 1.4.10
* Logic: Changed the rule surrounding the Bumper Cave Ledge Drop (the murderdactyl) to only require Pearl AND (Cape OR Byrna OR a Sword) in OHKO mode for now. This is a temporary solution until a better way to prevent needing to use a framerule to get past the bird is invented.
* Helmacopter Fix.
* Fixed a bug in shopsanity that caused the generation to fail with certain prices.
* Enemizer: fairies were invalid on the overworld (unless a bonk occurred nearby), so they will no longer be randomized there.
* 1.4.9
* Attempted fix for Moth conveyor room timing. Thanks for many people's input. Unsure if Helmacopter is still acceptable.
* Mirror scroll will show up on file start screen if enabled (thanks Clearmouse!)
* Fixes for HMG by Muffins
* Various fixes for Enemizer by Codemann (gfx fixes, more randomization options)
* Vanilla door shuffle prevents big key doors changes from door_type_mode
* Couple of minor fixes to custom generation. Deals with a complete specification of all dungeons
* Various enemizer bans for blocked paths (thanks to all the reports, Q1 2025)
* 1.4.8.1
- Fixed broken doors generation
- Fixed bomb/arrow upgrade ignoring custom pricing
- Extended `money_balance` to apply to price balancing for non-custom shops.
* 1.4.8
- New option: Mirror Scroll - to add the item to the starting inventory in non-doors modes (Thanks Telethar!)
- Customizer: Ability to customize shop prices and control money balancing. `money_balance` is a percentage betwen 0 and 100 that attempts to ensure you have that much percentage of money available for purchases. (100 is default, 0 essentially ignores money considerations)
- Fixed a key logic bug with decoupled doors when a big key door leads to a small key door (the small key door was missing appropriate logic)
- Fixed an ER bug where Bonk Fairy could be used for a mandatory connector in standard mode (boots could allow escape to be skipped)
- Fixed an issue with flute activation in rain mode. (thanks Codemann!)
- Fixed an issue with enemies in TR Dark Ride room not requiring Somaria. (Refactored the room for decoupled logic better)
- More HMG fixes by Muffins
- Fixed an issue with multi-player HMG
- Fixed an issue limiting number of items specified in the item pool on the GUI
- Minor documentation fixes (thanks Codemann!)
* 1.4.7.2
- Fixed an issue with shuffle_ganon/fix_gtower_exit causing a generation failure
- More HMG fixes by Muffins
* 1.4.7.1
- Fixed an issue with the repaired "beemizer" setting not being backwards compatible
* 1.4.7
- Fixed generation error with Big Key in starting inventory (thanks Cody!)
- HMG/NL logic fixes by Muffins
- Enemizer: Disabled Walking Zora in the UW due to crash with Swamola (they ignore a lot of collison anyway)
- Enemizer: Fixed an issue with enemizer bush sprites
- Enemizer: Banned new Mimics from being the randomized bush sprite due to crash
- "Beatable" or "accessibility: none" can now use randomized trap doors to seal off entire parts of dungeons (was intended, bug prevented the logic skip)
- Logic error with enemizer and standard should use new enemy logic rules
- Fixed a bug with the inconsistent treatment of the beemizer setting
- Fixed an issue with returning Blacksmith in Simple shuffle (when blacksmith is at Link's House)
- Fixed an issue with dark sanctuary spawn at tavern north door (thanks Codemann!)
- Various enemy bans for the last few months
* 1.4.6
- Restores original Sanc & Quit behavior, if Aga1 is not dead, then no quick pyramid warp
- Fixed problem with Lite/Lean shuffling some fairy caves unnecessarily
- The 300 Rupees item is now marked as priority, so it will not be used as GT junk fill (this matches a change in the VT randomizer)
- Murahdahla is now logically accessible in inverted mode as a bunny. May affect some beatable-only TFH seeds by allowing Moon Pearl to be inaccessible.
- Fixed an issue around PreferredLocationGroups in customizer which would not work with pre-activated Ocraina or priority items.
- Minor fix on triforce text
- Enemy bans for poor placements
* 1.4.5
- Logic: Added appropriate enemy logic to GT Mimics 1 and 2 rooms
- Logic: Added appropriate enemy logic to Mire 2 room. Note this does change the default logical strats, due to how enemy kill logic works.
- Ice Rod + 1 Bomb is now out of logic
- Fire Rod + 1 Bomb is now out of logic
- Fire Rod + 1 magic extension is still in logic
- Byrna + 1 magic extension is newly in logic
(One magic extension is either half magic or a bottle with the ability to purchase a blue or green potion) In general, making up for a lack of magic extension with a few bombs is something that could be added to the logic. Using the ice rod to freeze an enemy and then using that enemy to deal blunt damage and then using bombs to clear the frozen enemy is another strategy that could be added to the logic someday. If these are important to you, let me know.
- Generation: Fixed an issue with dungeonsfull shuffle
* 1.4.4
- Fixed a crash near the end of the credits when total collection rate was over 1000
* 1.4.3
* Key Logic Algorithm: Renamed "Default" to "Dangerous" to indicate the potential soft-lock issues
* Hera Basement Cage: Fix for small key counting multiple times (again)
* Generation: Fixed several generation problems with ER and intensity 3
* Customizer: Generation bug when attempting to place small keys
* Hints: Updated pedestal/tablet text to be more clear
* Enemizer: Various enemy bans
* 1.4.2
* New ER Options:
* [Skull Woods shuffle options](#skull-woods-shuffle)
* [New option](#linked-drops-override) to override linked drop down behavior
* Customizer: You can now start with a "RandomWeapon" in the start inventory section
* Customizer: You may now start with "Big Magic" or "Small Magic" items
* Customizer: Suppress warning for missing items if they are in start inventory
* MultiClient: change default port to 23074 for newer SNI versions
* Generation: Fixed typo causing ER gen failure with Bomb Shop at Graveyard Ledge
* 1.4.1.12u
* New Entrance Shuffle Algorithm no longer experimental
* Back of Tavern Shuffle now on by default
* Enemizer: Wallmasters banned from tiles where spiral staircases are. (Softlock issue)
* Packaged build of unstable now available
* Customizer: New PreferredLocationGroup for putting a set of items in a set of locations. See customizer docs.
* Customizer: Fixed an issue with starting with `Ocarina` and flute_mode is active
* Spoiler: Some reformatting. Crystal req. for GT/Ganon moved to requirements section so randomized requirements don't show up in the meta section
* Algorithm: Major_Only. Supports up to 16 extra locations (the visible heart pieces) for when major item count exceeds major location count. Examples: Triforce Hunt, Trinity (Triforce on Ped), Bombbag shuffle
* Fix: HC Big Key drop doesn't count on Basic Doors
* Fix: Small Key for this dungeon in Hera Basement doesn't count twice for the key counter
* Fix: All cross-dungeon modes with restrict boss items should require map/compass for the boss
* Fixed a small bug with traversal algorithm
* Enemizer: Enemy bans+
* 1.4.1.11u
* New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug
* Semi includes only entrances, prizes, and medallions (potential new spoiler mode being worked on, definition may change)
* Entrance: Lite/Lean support enemy drop shuffle
* Standard: Re-added tutorial guard near large rock
* Enemizer
* Fixed the overwriting of bonk fairies
* Fixed broken graphics on hyrule castle
* Enemy bans
* Customizer: Fixed bug with customizing prize packs
* 1.4.1.10u
* Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access are only locked by 2 keys thanks to that being the minimum in Mire to reach one of two crystal switches.
* Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody)
* Enemizer: Enemy bans
* Rom: Code prettification and fixing byte designations by Codemann
* Support added for BPS patches via jsonout setting (Thanks Veetorp!)
* 1.4.1.9u
* Enemy Drop Underworld: Changed enemy drop indicator to not require compass
* Experimental: Moved dark world bunny spawns out of experimental. (It is now always on)
* Fix: Red/Blue pendants were swapped for autotracking. (Thanks Muffins!)
* Fix: Red square sometimes wasn't blinking
* Updated tournament winners
* Enemizer: Enemy bans
* 1.4.1.8u
* HUD: New dungeon indicators based on common abbreviations
* OWG+HMG: EG is allowed to be armed
* Drop Shuffle: Fixed an issue with minor drops counting for the wrong dungeon
* Enemizer: Trinexx ice breath should be properly disabled if Trinexx is located outside of Turtle Rock
* Enemizer: Enemy bans
* 1.4.1.7u
* Some bugs around Triforce Pieces smoothed out
* Enemizer: No exception for mimics/eyegores in vanilla rooms if enemy logic is turned to off
* Enemizer: Various enemy bans
* 1.4.1.6u
* Difficulty: Fixed some issues around item caps not being respected
* Enemezier: Tutorial guards remove from South Kakariko
* Map: Pendant colors fixed
* Minor rom code cleanup
* Enemizer: Hovers added to problematic pool near pits. Some other bans
* 1.4.1.5u
* Major Fix: Problem with Ganon's Room sprites
* HMG: Remove extra Swamp Smalls in the pool due to pottery settings
* Enemizer: Couple enemy bans
* 1.4.1.4u
* Logic: Fixed logic bugs surrounding dynammic doors missing logic from big keys and other door types
* Inverted: Castle warp should not appear after defeating Aga 1
* Enemzier: Fixed a crash with cached sprites Zora/Swamola
* 1.4.1.3v
* Enemizer: Raven/Murderdactyls using the correct damage table
* Enemzier: Boss drops only center when boss shuffle is on
* 1.4.1.2v
* Expert/Hard Item Pool: Capacity fairy no longer gives out free crystal
* Vanilla door + Universal Keys: Generation fixed
* Boss Shuffle: Generation fixed (thanks Codemann for easy solution)
* Vanilla ER: No need for ability to check prizes on keysanity menu
* Swapped ER: Possible generation issue fixed (thanks Codemann)
* Enemizer: Roller ban
* Performance: Faster text boxes. Thanks Kan!
* 1.4.1.1v
* Logic: Moon pearl logic respects blocked doors
* 1.4.1.0v
* World Model Refactor: The overworld has been split up by screen, brings OR and DR a bit closer together in the model sense. A few OWG clips have been rewritten to fit into this new logic better.
* Logic: New logic for some bosses on ice
* Helmasaur on Ice: Bombs for mask, sword or arrows for 2nd phase
* Blind on Ice: Beam sword, Somaria, or Byrna plus magic extension for damage. Red shield or Byrna for protection.
* Kholdstare on Ice: Three options (after cracking the shell)
* Beam sword
* Fire Rod with 1.5 magic extensions
* Fire Rod & Bombos & any Sword & 1 Magic Extension
* Vitreous on Ice: Arrows and Bombs or a Beam Sword
* Trinexx on Ice: Boots always required for dodging. Damage options:
* Gold sword
* Tempered sword with magic extension
* Hammer or Master sword with 3 magic extensions (Rod spam for elemental heads, non-ideal weapon for last phase)
* Trinexx on Ice forbidden in doors seeds until we can model some health requirements. Low health Trinexx still isn't realistically feasible (bascially playing OHKO)
* Logic: Added silver arrows as Arrghus damage option when item functionality is not set to hard or expert
* Logic: Byrna not in logic for laser bridge when item functionality is set to hard or expert
* Enemizer Damage Rework:
* Shuffled: Actually shuffles the damage groups in the table instead of picking random numbers and reducing for mails from there. Enemies will still be assigned to a damage group randomly.
* There will always be at least one group which does no damage. The thief will always be in that group. Ganon always has his own group.
* Glitched modes: Aga 1 should be vulnerable in rain state for glitched modes
* Generation: Trinexx and Lanmolas room allowed as lobbies in intensity 3 (works with enemizer now)
* Enemy AI: Terrorpin AI code removed. May help with unusual enemy behavior?
* 1.4.0.1v
* Key logic: Vanilla key logic fixes. Statically set some HC logic and PoD front door
* Generation: Fix a broken tile pattern
* Inverted: Castle warp should not appear after defeating Aga 1
* Murahdahla: Should not disappear after Aga 1. May fix other subtle issues.
* Shopsanity: Buying multiple of an item in the potion shop should no longer increase item count.
* 1.4.0.0v
* Initial support for HMG (Thanks Muffins!)
* Generation: fix for bunny walk logic taking up too much memory
* Key Logic: Partial is now the new default
* Enemizer: enemy bans
* 1.3.0.9v
* ER: New Swapped ER mode borrowed from OWR
* ER: fixed a generation error where TR chooses all "must-exits"
* Ganonhunt: playthrough no longer collects crystals
* Vanilla Fill: Uncle weapon is always a sword, medallions for Mire/TR will be vanilla
* Customizer: support shufflebosses/shuffleenemies as well as boss_shuffle/enemy_shuffle
* Enemizer: enemy bans
* 1.3.0.8v
* Enemizer: Red Mimics correctly banned from challenge rooms in appropriate logic setting
* No Logic Standard ER: Rain doors aren't blocked if no logic is enabled.
* Trinexx: attempt to fix early start
* MW Progression Balancing: Change to be percentage based instead of raw count. (80% threshold)
* Take anys: Good Bee cave chosen as take any should no longer prevent generation
* Money balancing: Fixed generation issue
* Enemizer: various enemy bans
* 1.3.0.7v
* Fix for Mimic Cave enemy drops
* Fix for Spectacle Rock Cave enemy drops (the mini-moldorms)
* Fix for multiworld lamps with incorrect graphics
* No longer shuffles fairy bonks (from trees) as part of Enemizer
* 1.3.0.6v
* Flute can't be activated in rain state (except glitched modes) (Thanks codemann!)
* Enemizer
* Arrghus at Lanmo 2 no longer prevents pot pickups
* Trinexx at Lanmo 2 requires the Cape go backwards to face him
* Lift-able Blocks require a sprite slot (should help reduce problems)
* Fixed logic issues:
* Self-locking key not allowed in Sanctuary in standard (typo fixed)
* More advanced bunny-walking logic in dungeons (multiple paths considered)
* ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used)
* MSU: GTBK song fix for DR (Thanks codemann!)
* District Algorithm: Fails if no available location outside chosen districts
* Various enemy bans
* More Gibos near kiki and Old Man
* Bumper/AntiFairy obstacles
* Damaging roller
* Statue + Pots don't mix
* Statues on Skull Big Key Chest tile
* Toppo in challenge rooms
* Misc others
* 1.3.0.5v
* Hud/Map Counter: Collecting a keys for this dungeon of a bonk torch no longer increments the counter twice and immediately updates the hud.
* Enemizer: Hera basement item counting twice fixed by banning wallmasters on the tile.
* Enemizer: Statues banned offscreen for pull switches
* Enemizer: Several sprite producing enemies have been limited on crowded tiles. Offenders: Hinox, Sluggula, Bomb Guard, Beamos, Gibo, Wall Cannons, Probe using Guards. Others do not spam as many projectiles.
* Enemizer: More enemy bans (mostly Wizzrobes near walls where they won't spawn, couple missed firebar spots)
* 1.3.0.4v
* Enemizer: The bunny beam near Lanmo 2 and the 4 fairies near Ice Armos are not shuffled anymore. This is due to how bosses shuffle works and since it cannot be guaranteed to work within the current system, they are vanilla. (Vitreous still overwrites the fairies and Arrghus only lets two spawn, etc.)
* Dropshuffle: Pokey 1 has been fixed to drop his item
* Mystery/Customizer: true/false and on/off in yaml files should behave the same.
* More enemy bans as have been reported
* 1.3.0.3v
* Faeries now part of the enemy shuffle pool. Take note, this will increase enemy drop locations to include fairy pools both in dungeons and in caves.
* Enemy drop indicator (blue square) now works in caves based on entrance used
* Fixes:
* Collection rate counter is properly hidden in mystery seeds
* Sprite limit lowered where possible to allow for lifting of pots
* Hovers in Swamp Waterway properly do not drop items anymore
* Lots more bans (thanks to jsd in particular but also thanks to all the reports)
* Minor issue with customizer/mystery files not allowing "true" for booleans
* 1.3.0.2v
* Fix for multiworld received keys not counting correctly
* Fix for multiworld lamps incorrect graphics
* Fix for collection rate decreasing on item "pickup"
* Fix for pendants as prizes counting as items
* Fix for castle barrier gfx in rain state
* Enemizer fixes and bans:
* Fixed a generation issue where ChainChomp placement would cause a failure. (Invincible enemies banned in Sprial Cave for early game traversal for now)
* Skull Pot Prison should not be blocked by "impassable" enemies
* Bumpers banned in Ice Hookshot room
* Fixed issue in GT Spike Crystal room
* Fixed blockage issues in TT Ambush and Compass rooms
* Forbid Bumper in Fairy Ascension cave; needed to clip into wall weirdly to pass.
* Enemy Drop bans
* Forbid Stals in many places where they cannot be woken up. Behind rails and on top of blocks, for example.
* A couple minor wizzrobes bans because of despawns.
* Enemies over pits and on conveyors near pits have been issued standard bans for falling enemies. Mimics join the ranks here as they don't work well on pits or on conveyors.
* Mimics banned where conveyors touch walls and could clip out unintentionally
* 1.3.0.1v
* Fixed bugs with item duping and disappearing drops
* Fixed multiworld crash
* Fixed assured sword missing when using start inventory (via GUI/CLI)
* Forbid extra statues in Swamp Push Statue room
* Forbid bumpers on OW water
* Forbid Stal on pits
* Text fix on sprite author (thanks Synack)
* 1.2.0.23u
* Generation: fix for bunny walk logic taking up too much memory
* Key Logic: Partial is now the new default
* 1.2.0.22u
* Flute can't be activated in rain state (except glitched modes) (Thanks codemann!)
* ER: Minor fix for Link's House on DM in Insanity (escape cave should not be re-used)
* Logic issues:
* Self-locking key not allowed in Sanctuary in standard (typo fixed)
* More advanced bunny-walking logic in dungeons (multiple paths considred)
* MSU: GTBK song fix for DR (Thanks codemann!)
* 1.2.0.21u
* Fix that should force items needed for leaving Zelda's cell to before the throne room, so S&Q isn't mandatory
* Small fix for Tavern Shuffle (thanks Catobat)
* Several small generation fixes
* 1.2.0.20u
* New generation feature that allows Spiral Stair to link to themselves (thank Catobat)
* Added logic for trap doors that could be opened using existing room triggers
* Fixed a problem with inverted generation and the experimental flag
* Added a notes field for user added notes either via CLI or Customizer (thanks Hiimcody and Codemann)
* Fixed a typo for a specific pot hint
* Fix for Hera Boss music (thanks Codemann)
* 1.1.6 (from Stable)
* Minor issue with dungeon counter hud interfering with timer
* 1.2.0.19u
* Added min/max for triforce pool, goal, and difference for CLI and Customizer. (Thanks Catobat)
* Fixed a bug with dungeon generation
* Multiworld: Fixed /missing command to not list all the pots
* Changed the "Ganonhunt" goal to use open pyramid on the Auto setting
* Customizer: Fixed the example yaml for shopsanity
* 1.2.0.18u
* Fixed an issue with pyramid hole being in logic when it is not opened.
* Crystal cutscene at GT use new symmetrical layouts (thanks Codemann)
* Fix for Hera Boss music (thanks Codemann)
* Fixed an issue where certain vanilla door types would not allow other types to be placed.
* Customizer: fixed an issue where last ditch placements would move customized items. Those are now locked and the generation will fail instead if no alternatives are found.
* Customizer: fixed an issue with assured sword and start_inventory
* Customizer: warns when trying to specifically place an item that's not in the item pool
* Fixed "accessibility: none" displaying a spoiling message
* Fixed warning message about custom item pool when it is fine
* 1.2.0.17u
* Fixed logic bug that allowed Pearl to be behind Graveyard Cave or King's Tomb entrances with only Mirror and West Dark World access (cross world shuffles only)
* Removed backup locations for Dungeon Only and Major Only algorithms. If item cannot be placed in the appropriate location, the seed will fail to generate instead
* Fix for Non-ER Inverted Experimental (Aga and GT weren't logically swapped)
* Fix for customizer setting crystals to 0 for either GT/Ganon
* 1.2.0.16u
* Fix for partial key logic on vanilla Mire
* Fix for Kholdstare Shell collision when at Lanmo 2
* Fix for Mire Attic Hint door (direction was swapped)
* Dungeon at Chest Game displays correctly on OW map option
* 1.2.0.15u
* GUI reorganization
* Logic fix for pots in GT conveyor cross
* Auto option for pyramid open (trinity or ER + crystals goal)
* World model refactor (combining inverted and normal world models)
* Partitioned fix for lamp logic and links house
* Fix starting flute logic
* Reduced universal keys in pool slightly for non-vanilla dungeons
* Fake world fix finally
* Some extra restrictions on links house placement for lite/lean
* Collection_rate works in customizer files
* 1.2.0.14u
* Small fix for key logic validation (got rid of a false negative)
* Customized doors in ice cross work properly now
* 1.2.0.13u
* Allow green/blue potion refills to be customized
* OW Map showing dungeon entrance at Snitch Lady (West) fixed (instead of @ HC Courtyard)
* Standing item data is cleared on transition to overworld (enemy drops won't bleed to overworld sprites)
* Escape assist won't give you a free quiver in retro bow mode
* Fixed an issue where a door would be opened magically (due to original pairing)
* MultiServer can now disable forfeits if desired
* 1.2.0.12u
* Fix for mirror portal in inverted
* Yet another fix for blocked door in Standard ER
* 1.2.0.11u
* Fixed an issue with lower layer doors in Standard
* Fix for doors in cave state (will no longer be vanilla)
* Added a logic rule for th murderdactyl near bumper ledge for OHKO purposes
* Enemizer alteration for Hovers and normal enemies in shallow water
* Fix for beemizer including modes with an increased item pool
* Fix for district algorithm
* 1.2.0.10u
* Fixed overrun issues with edge transitions
* Better support for customized start_inventory with dungeon items
* Colorized pots now available with lottery. Default is on.
* Dungeon_only support pottery
* Fix AllowAccidentalGlitches flag in OWG
* Potential fix for mirror portal and entering cave on same frame
* A few other minor issues, generation and graphical
* 1.2.0.9-u
* Disallowed standard exits (due to ER) are now graphically half blocked instead of missing
* Graphical issues with Sanctuary and Swamp Hub lobbies are fixed
* Fixes an issue surrounding door state and decoupled doors leading to blocked doors
* Customizer improvements:
* Better logic around customized lobbies
* Better logic around customized door types
* Fix to key doors that was causing extra key doors
* Generation improvement around crystal switches
* Fix bug in dungeon_only that wasn't using pot key locations (known issue still exists in pottery modes)
* Fixes for multiworld:
* Fixes an issue when keys are found in own dungeon for another player when using the bizhawk plugin.
* Fixes an issue with absorbables for another player also being received by the player picking it up.
* 1.2.0.8-u
* New Features: trap_door_mode and key_logic_algorithm
* Change S&Q in door shuffle + standard during escape to spawn as Uncle
* Fix for vanilla doors + certain ER modes
* Fix for unintentional decoupled door in standard
* Fix a problem with BK doors being one-sided
* Change to how wilds keys are placed in standard, better randomization
* Removed a Triforce text
* Fix for Desert Tiles 1 key door
* 1.2.0.7-u
* Fix for some misery mire key logic
* Minor standard generation fix
* Fix for inactive flute start
* Settingsfile for multiworld generation support
* Fix for duped HC/AT Maps/Compasses
* 1.2.0.6-u
* Fix for light cone in Escape when entering from Dark World post-zelda
* Fix for light cone in Escape when lighting a torch with fire rod
* 1.2.0.5.u
* Logic fix for Sanctuary mirror (it wasn't resetting the crystal state)
* Minor bugfixes for customizer
* 1.2.0.4-u
* Starting inventory fixes if item not present in the item pool.
* Support for Assured sword setting and OWG Boots when using a custom item pool. (Customizer or GUI)
* Logic fix for the skull woods star tile that lets you into the X pot room. Now accounts for small key or big key door there blocking the way from the star tile. A trap door is not allowed there.
* Standard logic improvement that requires a path from Zelda to the start so that you cannot get softlocked by rescuing Zelda. Standard mirror scroll change may need to be reverted if impossible seed are still generated.
* 1.2.0.3-u
* Starting inventory taken into account with default item pool. (Custom pools must do this themselves)
* Fast ROM update
* Fix for restricted boss item counting maps & compasses as vital
* Bug fix for vanilla ER + inverted + experimental
* 1.2.0.2-u
* Fixed a bug with certain trap doors missing
* Added a hint reference for district hints
* 1.2.0.1-u
* Added new ganonhunt and completionist goals
* Fixed the issue when defeating Agahnim and standing in the doorway can cause door state to linger.
* Fix for Inverted Lean/Lite ER
* Fix for vanilla Doors + Standard + ER
* Added a limit per dungeon on small key doors to ensure reasonable generation
* Fixed many small bugs
# Known Issues
* Decoupled doors can lead to situations where you aren't logically supposed to go back through a door without a big key or small key, but you can if you press the correct direction back through the door first. There are some transitions where you may get stuck without a bomb. These problems are planned to be fixed.
* Logic getting to Skull X room may be wrong if a trap door, big key door, or bombable wall is shuffled there. A bomb jump to get to those pot may be required if you don't have boots to bonk across.
+100 -141
View File
@@ -3,40 +3,27 @@ import argparse
import hashlib
import logging
import os
import sys
import time
import RaceRandom as random
from BaseClasses import World
from Dungeons import create_dungeons
from ItemList import difficulties
from Items import ItemFactory
from Main import create_playthrough
from OverworldShuffle import link_overworld
from Regions import create_regions
from Rom import (
LocalRom,
apply_rom_settings,
get_sprite_from_name,
patch_rom,
write_string_to_rom,
)
from Rules import set_rules
from source.overworld.EntranceShuffle2 import (
connect_entrance,
connect_exit,
connect_two_way,
link_entrances_new,
)
import time
import sys
PlandoVersion = '0.2-dev'
from BaseClasses import World
from Regions import create_regions
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom
from Rules import set_rules
from Dungeons import create_dungeons
from Items import ItemFactory
from ItemList import difficulties
from Main import create_playthrough
__version__ = '0.2-dev'
def main(args):
start_time = time.process_time()
start_time = time.perf_counter()
# initialize the world
world = World(1, 'vanilla', True, 'vanilla', 'vanilla', 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, False, False, False, None, False)
world.player_names[1].append("Player 1")
world = World(1, 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None, 'none', False)
logger = logging.getLogger('')
hasher = hashlib.md5()
@@ -47,19 +34,14 @@ def main(args):
random.seed(world.seed)
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', PlandoVersion, args.plando)
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', __version__, args.plando)
world.difficulty_requirements[1] = difficulties[world.difficulty[1]]
world.difficulty_requirements = difficulties[world.difficulty]
create_regions(world, 1)
create_dungeons(world, 1)
text_patches = []
prefill_world(world, args.plando, text_patches)
link_overworld(world, 1)
link_entrances_new(world, 1)
link_entrances(world, 1)
logger.info('Calculating Access Rules.')
@@ -67,9 +49,11 @@ def main(args):
logger.info('Fill the world.')
fill_world(world, args.plando)
text_patches = []
if not (world.get_entrance('Dam', 1).connected_region.name in ['Dam', 'Swamp Portal'] and world.get_entrance('Swamp Palace', 1).connected_region.name in ['Swamp Portal', 'Dam']):
fill_world(world, args.plando, text_patches)
if world.get_entrance('Dam', 1).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', 1).connected_region.name != 'Swamp Palace (Entrance)':
world.swamp_patch_required[1] = True
logger.info('Calculating playthrough.')
@@ -84,10 +68,13 @@ def main(args):
logger.info('Patching ROM.')
rom = LocalRom(args.rom)
patch_rom(world, rom, 1, 1, False, str(args.rom_header) if args.rom_header else None)
if args.sprite is not None:
sprite = Sprite(args.sprite)
else:
sprite = None
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.triforce_gfx, args.ow_palettes, args.uw_palettes)
rom = LocalRom(args.rom)
patch_rom(world, 1, rom, args.heartbeep, args.heartcolor, sprite)
for textname, texttype, text in text_patches:
if texttype == 'text':
@@ -98,23 +85,84 @@ def main(args):
outfilebase = 'Plando_%s_%s' % (os.path.splitext(os.path.basename(args.plando))[0], world.seed)
rom.write_to_file('%s.sfc' % outfilebase)
if args.spoiler != 'none':
if args.create_spoiler:
world.spoiler.to_file('%s_Spoiler.txt' % outfilebase)
logger.info('Done. Enjoy.')
logger.debug('Total Time: %s', time.process_time() - start_time)
logger.debug('Total Time: %s', time.perf_counter() - start_time)
return world
def fill_world(world, plando):
def fill_world(world, plando, text_patches):
mm_medallion = 'Ether'
tr_medallion = 'Quake'
logger = logging.getLogger('')
with open(plando, 'r') as plandofile:
for line in plandofile.readlines():
line = line.lstrip()
if line.startswith('#'):
continue
elif ':' in line:
if ':' in line:
line = line.lstrip()
if line.startswith('!'):
if line.startswith('!mm_medallion'):
_, medallionstr = line.split(':', 1)
mm_medallion = medallionstr.strip()
elif line.startswith('!tr_medallion'):
_, medallionstr = line.split(':', 1)
tr_medallion = medallionstr.strip()
elif line.startswith('!mode'):
_, modestr = line.split(':', 1)
world.mode = modestr.strip()
elif line.startswith('!logic'):
_, logicstr = line.split(':', 1)
world.logic = logicstr.strip()
elif line.startswith('!goal'):
_, goalstr = line.split(':', 1)
world.goal = goalstr.strip()
elif line.startswith('!light_cone_sewers'):
_, sewerstr = line.split(':', 1)
world.sewer_light_cone = sewerstr.strip().lower() == 'true'
elif line.startswith('!light_cone_lw'):
_, lwconestr = line.split(':', 1)
world.light_world_light_cone = lwconestr.strip().lower() == 'true'
elif line.startswith('!light_cone_dw'):
_, dwconestr = line.split(':', 1)
world.dark_world_light_cone = dwconestr.strip().lower() == 'true'
elif line.startswith('!fix_trock_doors'):
_, trdstr = line.split(':', 1)
world.fix_trock_doors = trdstr.strip().lower() == 'true'
elif line.startswith('!fix_trock_exit'):
_, trfstr = line.split(':', 1)
world.fix_trock_exit = trfstr.strip().lower() == 'true'
elif line.startswith('!fix_gtower_exit'):
_, gtfstr = line.split(':', 1)
world.fix_gtower_exit = gtfstr.strip().lower() == 'true'
elif line.startswith('!fix_pod_exit'):
_, podestr = line.split(':', 1)
world.fix_palaceofdarkness_exit = podestr.strip().lower() == 'true'
elif line.startswith('!fix_skullwoods_exit'):
_, swestr = line.split(':', 1)
world.fix_skullwoods_exit = swestr.strip().lower() == 'true'
elif line.startswith('!check_beatable_only'):
_, chkbtstr = line.split(':', 1)
world.check_beatable_only = chkbtstr.strip().lower() == 'true'
elif line.startswith('!ganon_death_pyramid_respawn'):
_, gnpstr = line.split(':', 1)
world.ganon_at_pyramid = gnpstr.strip().lower() == 'true'
elif line.startswith('!save_quit_boss'):
_, sqbstr = line.split(':', 1)
world.save_and_quite_from_boss = sqbstr.strip().lower() == 'true'
elif line.startswith('!text_'):
textname, text = line.split(':', 1)
text_patches.append([textname.lstrip('!text_').strip(), 'text', text.strip()])
#temporarilly removed. New credits system not ready to handle this.
#elif line.startswith('!credits_'):
# textname, text = line.split(':', 1)
# text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()])
continue
locationstr, itemstr = line.split(':', 1)
location = world.get_location(locationstr.strip(), 1)
if location is None:
@@ -124,9 +172,8 @@ def fill_world(world, plando):
item = ItemFactory(itemstr.strip(), 1)
if item is not None:
world.push_item(location, item)
if item.smallkey or item.bigkey:
if item.key:
location.event = True
#TODO: move entrance stuff to prefill_world to work like OW stuff
elif '<=>' in line:
entrance, exit = line.split('<=>', 1)
connect_two_way(world, entrance.strip(), exit.strip(), 1)
@@ -137,104 +184,18 @@ def fill_world(world, plando):
entrance, exit = line.split('<=', 1)
connect_exit(world, exit.strip(), entrance.strip(), 1)
world.required_medallions[1] = (mm_medallion, tr_medallion)
# set up Agahnim Events
world.get_location('Agahnim 1', 1).event = True
world.get_location('Agahnim 1', 1).item = ItemFactory('Beat Agahnim 1', 1)
world.get_location('Agahnim 2', 1).event = True
world.get_location('Agahnim 2', 1).item = ItemFactory('Beat Agahnim 2', 1)
def prefill_world(world, plando, text_patches):
mm_medallion = 'Ether'
tr_medallion = 'Quake'
logger = logging.getLogger('')
with open(plando, 'r') as plandofile:
for line in plandofile.readlines():
line = line.lstrip()
if line.startswith('#'):
continue
elif line.startswith('!'):
if line.startswith('!mm_medallion'):
_, medallionstr = line.split(':', 1)
mm_medallion = medallionstr.strip()
elif line.startswith('!tr_medallion'):
_, medallionstr = line.split(':', 1)
tr_medallion = medallionstr.strip()
elif line.startswith('!mode'):
_, modestr = line.split(':', 1)
world.mode = {1: modestr.strip()}
elif line.startswith('!logic'):
_, logicstr = line.split(':', 1)
world.logic = {1: logicstr.strip()}
elif line.startswith('!goal'):
_, goalstr = line.split(':', 1)
world.goal = {1: goalstr.strip()}
elif line.startswith('!owLayout'):
_, modestr = line.split(':', 1)
world.owLayout = {1: modestr.strip()}
elif line.startswith('!owCrossed'):
_, modestr = line.split(':', 1)
world.owCrossed = {1: modestr.strip()}
elif line.startswith('!owKeepSimilar'):
_, modestr = line.split(':', 1)
modestr = modestr.strip().lower()
world.owKeepSimilar = {1: True if modestr in ('true', 'yes', 'on', 'enabled') else False}
elif line.startswith('!light_cone_sewers'):
_, sewerstr = line.split(':', 1)
world.sewer_light_cone = {1: sewerstr.strip().lower() == 'true'}
elif line.startswith('!fix_trock_doors'):
_, trdstr = line.split(':', 1)
world.fix_trock_doors = {1: trdstr.strip().lower() == 'true'}
elif line.startswith('!fix_trock_exit'):
_, trfstr = line.split(':', 1)
world.fix_trock_exit = {1: trfstr.strip().lower() == 'true'}
elif line.startswith('!fix_gtower_exit'):
_, gtfstr = line.split(':', 1)
world.fix_gtower_exit = {1: gtfstr.strip().lower() == 'true'}
elif line.startswith('!fix_pod_exit'):
_, podestr = line.split(':', 1)
world.fix_palaceofdarkness_exit = {1: podestr.strip().lower() == 'true'}
elif line.startswith('!fix_skullwoods_exit'):
_, swestr = line.split(':', 1)
world.fix_skullwoods_exit = {1: swestr.strip().lower() == 'true'}
elif line.startswith('!check_beatable_only'):
_, chkbtstr = line.split(':', 1)
world.check_beatable_only = chkbtstr.strip().lower() == 'true'
elif line.startswith('!ganon_death_pyramid_respawn'):
_, gnpstr = line.split(':', 1)
world.ganon_at_pyramid = gnpstr.strip().lower() == 'true'
elif line.startswith('!save_quit_boss'):
_, sqbstr = line.split(':', 1)
world.save_and_quite_from_boss = sqbstr.strip().lower() == 'true'
elif line.startswith('!text_'):
textname, text = line.split(':', 1)
text_patches.append([textname.lstrip('!text_').strip(), 'text', text.strip()])
#temporarilly removed. New credits system not ready to handle this.
#elif line.startswith('!credits_'):
# textname, text = line.split(':', 1)
# text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()])
continue
elif line.startswith('$'):
edge1, edge2 = line.split('=', 1)
if world.custom_overworld is None:
world.custom_overworld = {1: []}
world.custom_overworld[1].append(edge1.strip(), edge2.strip())
#TODO: Do entrances similar to OW
elif '<=>' in line:
entrance, exit = line.split('<=>', 1)
#connect_two_way(world, entrance.strip(), exit.strip(), 1)
elif '=>' in line:
entrance, exit = line.split('=>', 1)
#connect_entrance(world, entrance.strip(), exit.strip(), 1)
elif '<=' in line:
entrance, exit = line.split('<=', 1)
#connect_exit(world, exit.strip(), entrance.strip(), 1)
world.required_medallions[1] = (mm_medallion, tr_medallion)
def start():
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--spoiler', default='none', help='Output a Spoiler File')
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
parser.add_argument('--ignore_unsolvable', help='Do not abort if seed is deemed unsolvable.', action='store_true')
parser.add_argument('--rom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) rom to use as a base.')
parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
@@ -250,8 +211,6 @@ def start():
help='Select the rate at which the heart beep sound is played at low health.')
parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow'],
help='Select the color of Link\'s heart meter. (default: %(default)s)')
parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout'])
parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout'])
parser.add_argument('--sprite', help='Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes.')
parser.add_argument('--plando', help='Filled out template to use for setting up the rom.')
args = parser.parse_args()
@@ -263,8 +222,8 @@ def start():
if not os.path.isfile(args.plando):
input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando)
sys.exit(1)
if args.sprite is not None and not os.path.isfile(args.sprite) and not get_sprite_from_name(args.sprite):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.')
if args.sprite is not None and not os.path.isfile(args.rom):
input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite)
sys.exit(1)
# set up logger
-7
View File
@@ -242,10 +242,3 @@ Ganons Tower - Mini Helmasaur Room - Right: Nothing
Ganons Tower - Pre-Moldorm Chest: Small Key (Ganons Tower)
Ganons Tower - Validation Chest: Nothing
Ganon: Triforce
# set Overworld connections (lines starting with $, separate edges with =)
!owLayout: wild
#!owMixed: true # Mixed OW not supported yet
!owCrossed: none
!owKeepSimilar: true
$Links House NE = Kakariko Village SE
-1082
View File
File diff suppressed because it is too large Load Diff
+343 -313
View File
@@ -1,459 +1,489 @@
# Overworld Randomizer
# ALttPEntranceRandomizer
This is a overworld randomizer for _The Legend of Zelda: A Link to the Past_ for the SNES
based on the Door Randomizer found at [Aerinon's Github Project.](https://github.com/Aerinon/ALttPDoorRandomizer)
This is a entrance randomizer for _The Legend of Zelda: A Link to the Past_ for the SNES.
See https://alttpr.com/ for more details on the normal randomizer.
### Trackers & Guides
# Installation
This is a very new mode of LTTPR so the tools and info is very limited.
- There is an [OW Rando Cheat Sheet](https://zelda.codemann8.com/images/shared/ow-rando-reference-sheet.png) that shows all the transitions that exist and are candidates for shuffle.
- There is OW tracking capability within the following trackers:
- [Community Tracker](https://alttptracker.dunka.net/)
- CodeTracker, an [EmoTracker](https://emotracker.net) package for LTTPR
- There is an [OW OWG Reference Sheet](https://zelda.codemann8.com/images/shared/ow-owg-reference-sheet.png) that shows all the in-logic places where boots/mirror clips and fake flippers are expected from the player.
Clone this repository and then run ```EntranceRandomizer.py``` (requires Python 3).
### If you want to playtest this, know these things:
- If you fake flipper, beware of transitioning south. You could end up at the top of the waterfall in the southeast of either world. If you mistakenly drop down, it is important to know that altho the game may appear as frozen for a bit of time, the game is simply scrolling Link back to the original point of water entry. Upon "landing", you'll be able to S+Q properly. Falling from the waterfall is avoidable but it is super easy to do as it is super close to the transition.
Alternatively, run ```Gui.py``` for a simple graphical user interface.
# Feedback and Bug Reports
All feedback and dev conversation happens in the #ow-rando channel on the [ALTTP Randomizer discord](https://discordapp.com/invite/alttprandomizer).
# Installation from Source
1. Download the source code from the repository directly and put it in a folder of your choosing. Any method of grabbing the source code is fine, but due to some changes on GitHub's site, some of the other alternatives might be better options for some people.
- Use [GitHub Desktop](https://desktop.github.com) to clone this repository to a folder on your computer. Once a repository is established, you can click `Fetch origin` or `Pull` to re-download whenever you want to grab the latest version. This is the best option for a simple one-click solution.
- Download the [source code](https://github.com/codemann8/ALttPDoorRandomizer/archive/refs/heads/OverworldShuffle.zip) from GitHub manually.
2. You must have [Python](https://www.python.org/downloads) installed (version 3.7 - 3.10 supported), and ensure PATH is included during the installation.
- A common issue with users is that there are multiple instances of Python installed on the same computer. This causes the computer to be confused with which Python instance it uses. Ensure that any older Python installations have been removed.
3. This program requires all python dependencies that are necessary to run OW Randomizer. There are multiple ways to install them:
- Try running ```pip install missingdependency``` or ```python -m pip install missingdependency``` on the command line (replace ```missingdependency``` with the specific package that is missing) to install the dependency.
- The simpler method, run (double-click) ```resources/ci/common/local_install.py``` to install all the missing dependencies as well.
4. Once installed, you should be able to run (double-click) ```Gui.py``` and the OWR program will appear, where you can select your desired settings.
See the following link if you have additional trouble: https://github.com/codemann8/ALttPDoorRandomizer/blob/OverworldShuffle/docs/BUILDING.md
# Running the Program
- Run (or double-click) ```Gui.py``` for a simple graphical user interface.
- Alternatively, you can generate thru the CLI, run ```DungeonRandomizer.py```.
# Getting Started
## "I heard about OWR and want to give it a try, what do I do?"
This is a common sentiment among those who are unfamiliar with OWR's offerings. One might be tempted to simply turn on a bunch of the options and give it a go. I cannot express this enough:
**^ DO NOT do this! ^**
OWR definitely has a lot of options, and all of them by themselves are pretty simple to grasp, but combining multiple OWR options together increases the complexity and confusion in exponential fashion. Now, of course, some OWR options like Flute Shuffle can safely be combined at any level and isn't gonna make anything more complicated. But specifically, avoid combining these 3 options, at least when going for your first seed:
- OW Layout Shuffle
- OW Tile Flip (Mixed)
- Crossed OW
## "Any recommendations for a first-timer?"
For a first (and second) seed... *and I say "second" because I feel like both of these recommendations I'm about to make have VERY different vibes, have different levels of challenge, but are both, of their own right, worthy of being tried at least once.* Your first OWR experience can be combined with any mode combination that you are already familiar with and have a lot of experience in playing. If you like Crosskeys and feel very comfortable running that, feel free to turn on all those settings in addition to ONE of these two options:
1. `OW Tile Flip (Mixed)` - Overly, a pretty easy-breezy mode, it doesn't require too much big brain, and is pretty managable even without proper logic tracking, as long as you at least have a standard map tracker. This is actually my favorite way to run OWR today
- DO NOT turn on Layout or Whirlpool Shuffle, leave this on `Vanilla`
- DO NOT turn on Crossed OWR
- `Flute Shuffle` or `Bonk Drops` could be enabled if desired, altho I'd recommend against it, at least for a fresh viewpoint of Mixed OWR
2. `OW Layout Shuffle` - Set to `Parallel`. This is the original spirit and vision of OWR from the time of its own founding. It's definitely much more complicated to run than OW Tile Flip, so keep that in mind.
- `Starting Boots` - Either actual boots or pseudoboots, you will be spending a lot of time navigating the OW, so it's best to do it with the ability to run fast.
- DO NOT turn on OW Tile Flip (Mixed)
- DO NOT turn on Crossed OWR
- Enable `Whirlpool Shuffle` - Recommended to always be enabled with Layout Shuffle
- Enable `Keep Similar Edges Together` - This just helps keep some of your sanity for a first experience
- Enable `Flute Shuffle` - I recommend setting this to `Balanced`, this helps space out the flute spots across the world. Being that the world itself is shuffled, the vanilla flute spots are likely NOT as conveniently located as they normally are.
- `Free Terrain` - Recommend to NOT turn this on for a first run, but definitely on a second run.
- `Bonk Drops` - Recommend to NOT turn this on, especially if you don't have starting boots, but you could enable this in future seeds if you've become more familiar with OWR, as you'll be visiting these screens anyways, you might as well grab the items on the way :)
## "What tracker should I use?"
I personally use 2 trackers together, at least if Layout Shuffle is enabled. Currently, DunkaTracker is the ONLY tracker that is useful for tracking Layout Shuffle, highly recommended. However, I personally don't like the main portion of the tracker and ignore the main window completely. For tracking everything else, I use `CodeTracker`, a tracker pack that is installable within `EmoTracker`, this handles ALL OWR modes EXCEPT Layout Shuffle (and generally Crossed OW, but missing that doesn't make much of a difference). I am unaware of ANY trackers outside of these 2 that handle OWR modes, so my advice would be to familiarize yourself with both trackers and pick and choose the parts you find most useful for yourself.
# Terminology
### OW / OWR
OW is shorthand for Overworld, referring to anywhere that is "outside" in the game: ie. not caves or dungeons, which are considered Underworld.
OWR is shorthand for Overworld Rando/Shuffle, the concept of randomizing certain elements present in the Overworld.
### LW / DW (**IMPORTANT TO READ**)
Light World and Dark World. Some of the references to LW and DW can be a bit ambiguous. Sometimes, if a LW tile is being referenced, it's usually referring to the normal LW tile that you already know of. Other times, I might be referring to a DW tile that has now become part of the LW, due to a certain way that the game can be shuffled. This can be particularly confusing when trying to understand these modes, and what they do, based on the text description of them, but it's important to keep in mind the context of which is being described.
Things get extra confusing when adding Crossed OWR into the mix, due to the fluidity of navigating between LW and DW freely. It's often best to think of Crossed OWR like this: You've been used to a two-part Light and Dark World, but Crossed works more close to a Starting Plane and Other Plane, ie. You can walk around and never be able to reach the "other" plane until you have the items to enter portals, including using mirror. Both of these planes consist of Light and Dark World tiles.
During gameplay and in the OW, there is an L or D in the upper left corner, indicating which *world* you are in. As complicated as all the rules can be, this L or D is the simplest way to digest it all. Whenever it says you are in DW, this means you are subject to being transformed to a bunny, cannot use the flute, and you will be able to use the mirror to gain access to the LW. When in LW, you are transformed to Link (if not already), and you're able to use the flute.
### Tile
This refers to one OW screen, containing one or more transitions (or edges) that lead to another tile. Most tiles have a "other world" version of it, linked by either a portal or mirror; some do not like Zora's Domain or Master Sword Pedestal.
### Edge / Transition
This refers to the sides/edges of the screen, where you walk into them and causes another tile to load, the camera pans over, and Link continues movement that destination tile.
### Parallel (as a concept)
Various things in this readme are referred to as parallel or non-parallel. Parallel refers to elements that exist in similar orientation in the opposite world. For instance, Kakariko Village and Village of Outcasts each have 5 transitions, all in the same geographical area and all leading in the same direction, that makes all those transitions parallel. In another instance, The Purple Chest screen (where you turn it in) has transitions on the West side of the screen, but in the DW, those transitions do not exist, therefore, these West transitions are non-parallel, and the tile itself is not parallel. Determining whether something is parallel or not dictates whether something can be shuffled or not, or whether it requires something else to be grouped with it and shuffled together.
### Tile Groups
These are groups of tiles that must be shuffled (or not shuffled) as a group. For instance, if Standard World State is enabled, Link's House, Hyrule Castle, and Sanctuary must be kept together, to ensure that the opening sequence can commence. There are a number of scenarios that can cause various tiles to be grouped, this is largely determined by the specific combination of OWR settings and is necessary to ensure correct logic.
### Similar Edge
These refer to groups of edges that are near each other. For instance, at Link's House, there are 3 different edges on the West side of the screen, separated by the cliffside terrain. All 3 of these can potentially lead to 3 different places. But conversely, these 3 edges are also considered similar edges and can be made to all lead to the same area.
Similar edges on the larger OW tiles are a bit harder to intuitively determine. For instance, in Kakariko Village, there are 3 North edges, but all 3 are NOT part of the same Similar Edge group. Instead, the 2 on the West End are deemed Similar, while the one to the far East is its own edge. The most definitive way to figure out which edges are actually grouped, look at a world map image with gridlines (one is linked above in `Trackers & Guides` labeled OW Rando Cheat Sheet), if you draw imaginary lines, splitting up large tiles into 2x2 parts (continuing the small tiles' patterns), all edges within these bounds are considered Similar Edges.
Similar edges are used when `Keep Similar Edges Together` is enabled and have meaning when used with `Layout Shuffle` (with and without `Free Terrain`) and `Crossed` settings.
# Overworld Map Changes
The overworld map check screen has been completely overhauled in this OWR fork. In other LTTP forks, there are numbered icons to indicate the crystal number of the crystal and those are shown alongside crystal prizes. OWR has changed this so the numbers instead correspond to dungeon numbers. In the LW, you have the 3 pendant dungeons, these are indicated by a blue 1, 2, and 3. In the DW, you have the 7 crystal dungeon, these are indicated by red numbers (1-7). In addition, there may be some mode combinations where HC, AT, and GT may be visible via icon, indicated by a white H, a white A, and a skull. An example of what you can expect to see on a map check can be found [here](https://zelda.codemann8.com/images/shared/newmapcheck.gif).
# Inverted Changes
The version of Inverted included in OWR varies quite a bit compared to the Door Rando and VT (main rando) forks. This is often referred to as Inverted 2.0, as this is a plan to shift it to a newer/updated concept, intended to either enhance/improve the Inverted experience or to restore some behaviors more closer to vanilla. Some of these changes are likely going to be added to all rando forks/branches and subject to change, but this will be based on user feedback.
- Links House start now spawns inside the Big Bomb Shop, where there is now a bed
- Old Man Cave/Bumper Cave entrances are restored to their original state
- Mountain Cave S+Q now spawns in his usual Cave
- Removed the ladder that leads from West Dark Death Mountain up to the top near the GT entrance
- Flute Spot 1 is moved to the very Top of Dark Death Mountain, next to the GT entrance
- When finding Flute, it comes pre-activated (will hear SFX on collecting)
- Spiral and Mimic Cave are now bridged together
- TR Peg Puzzle is restored but instead reveals a ladder
- Houlihan now exits same place as the Link's House start does
- Ganon Hole Exit now exits out a new door on top of HC
- Ice Palace has been re-sealed to vanilla, portal moved to outer edge of moat (makes IP mirror locked)
- Glitched modes will now use vanilla terrain except where necessary
Note: These changes do impact the logic. If you use `CodeTracker`, these Inverted 2.0 logic rules are automatically detected if using autotracker, indicated by a 2 in the corner of the World State mode icon. This can also be manually applied if you right-click the World State mode icon.
For releases, a Windows standalone executable is available for users without Python 3.
# Settings
Only settings specifically added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md)
## Game Mode
## Overworld Layout Shuffle (--ow_layout)
OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://zelda.codemann8.com/images/shared/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Flip)
### Standard
### Vanilla
Fixes Hyrule Castle Secret Entrance and Front Door, but may lead to weird rain state issues if you exit through the Hyrule Castle side exits before rescuing Zelda in a full shuffle.
OW Transitions are not shuffled.
Gives lightcone in Hyrule Castle Sewers even without the Lamp.
### Grid
### Open
OW Screens are shuffled in such a way that they are still arranged on an 8x8 grid for each world like vanilla, and the OW Transitions are based on that arrangement.
This mode starts with the option to start in your house or the sanctuary, you are free to explore.
### Wild
Special notes:
OW Transitions are shuffled with no respect to geometric coherence.
- Uncle already in sewers and most likely does not have a sword.
- Sewers do not get a free light cone.
- It may be a while before you find a sword, think of other ways to do damage to enemies. (bombs are a great tool, as well as picking up bushes in over world).
## Parallel (--ow_unparallel to disable)
### Swordless
With OW Layout Shuffle, this forces both worlds to have a matching layout.
This mode removes all swords from the itempool. Otherwise just like open.
## Free Terrain (--ow_terrain)
Special notes:
With OW Layout Shuffle, this allows land and water edges to be connected.
- The Medallions to open Misery Mire and Turtle Rock can be used without a sword if you stand on the symbol.
- The curtains in Skull Woods and Hyrule Castle Tower that normally require a sword to cut have been removed.
- Ganon takes damage from the Hammer.
- The magic barrier to Hyrule Castle Tower can be broken with a Hammer.
- The Hammer can be used to activate the Ether and Bombos tablets.
## Crossed Options (--ow_crossed)
### Inverted
This allows OW connections to be shuffled cross-world. There are 2 main methodologies of Crossed OWR:
This mode is similar to Open but requires the Moon Pearl in order to not transform into a bunny in the Light World and the Sanctuary spawn point is moved to the Dark Sanctuary
- `Grouped` and `Polar` both are guaranteed to result in two separated planes of tiles, similar to that of vanilla. This means you cannot simply walk around and be able to visit all the tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a tile that was previously unreachable
Special Notes:
- `Unrestricted` is not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal.
- Link's House is shuffled freely. The Dark Sanctuary is shuffled within West Dark World.
- There are a number of overworld changes to account for the inability to mirror from the Light World to the Dark World.
- Legacy shuffles are not implemente for this mode.
See each option to get more details on the differences.
## Game Logic
This determines the Item Requirements for each location.
### No Glitches
The game can be completed without knowing how to perform glitches of any kind.
### Minor Glitches
May require Fake Flippers, Bunny Revival.
### No Logic
Items are placed without regard for progression or the seed being possible. Major glitches are likely required.
## Game Goal
### Ganon
Standard game completion requiring you to collect the 7 crystals, defeat Agahnim 2 and then beat Ganon.
### Pedestal
Places the Triforce at the Master Sword Pedestal. Ganon cannot be damaged.
### All Dungeons
Ganon cannot be damaged until all dungeons (including Hyrule Castle Tower and Ganons Tower) are cleared.
### Triforce Hunt
Triforce Pieces are added to the item pool, and some number of them being found will trigger game completion. Ganon cannot be damaged.
By default 30 Triforce Pieces are placed while 20 are needed to beat the game. Both values can be adjusted with the custom item pool feature.
### Crystals
Standard game completion requiring you to collect the 7 crystals and then beat Ganon.
This is only noticeably different if the the Ganon shuffle option is enabled.
## Game Difficulty
### Normal
This is the default setting that has an item pool most similar to the original
The Legend of Zelda: A Link to the Past.
### Hard
This setting reduces the availability of a variety of minor helpful items, most notably
limiting the player to two bottles, a Tempered Sword, and Blue Mail. Several minor game
mechanics are adjusted to increase difficulty, most notably weakening potions and preventing
the player from having fairies in bottles.
### Expert
This setting is a more extreme version of the Hard setting. Potions are further nerfed, the item
pool is less helpful, and the player can find no armor, only a Master Sword, and only a single bottle.
## Timer Setting
### None
Transitions will remain same-world.
Does not invoke a timer.
### Grouped
### Display
This option shuffles connections cross-world in the same manner as Tile Flip (Mixed), the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike `Unrestricted`). This is considered the simplest way to play Crossed OWR.
Displays a timer on-screen but does not alter the item pool.
This will prevent the dungeon item count feature in Easy and Keysanity from working.
### Polar
### Timed
Only effective if Tile Flip (Mixed) is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are flipped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Tile Flip works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Flip / Mixed` section for more details)
Displays a count-up timer on screen that can be reduced with Green Clocks and Blue Clocks or
increased with Red Clocks found in chests that will be added to the itempool.
### Unrestricted
### Timed-OHKO
Every transition is independently a candidate to be chosen as a cross-world connection. This option abides by the `Keep Similar Edges Together` option and will guarantee same effect on all edges in a Similar Edge group if enabled. If a Similar Edge group is chosen from the pool of candidates, it only counts as one portal, not multiple. Each transition has an equal 50/50 chance of being a crossed connection.
Displays a countdown timer on screen that, when it hits zero, will put the player into a one hit
knockout state until more time is added to the clock via some of the Green Clocks that will be added
to the itempool.
Note: If Whirlpool Shuffle is enabled, those connections can be cross-world.
### OHKO
## Keep Similar Edges Together (--ow_keepsimilar)
The player will be in a one hit knockout state the entire game. This is the same as Timed-OHKO except
without the Clock items and the timer permanently at zero.
This keeps similar edge transitions together. ie. The 2 west land edges of Potion Shop will be paired to another set of two similar edges, unless Free Terrain is also enabled, in which case these 2 edges together with the west water edge form a group of 3 similar edges. See `Terminology` section above for a more detailed explanation of Similar Edges.
### Timed-countdown
Note: This affects OW Layout Shuffle mostly, but also affects `Unrestricted` mode in Crossed OW.
Displays a countdown timer on screen that can be increased with Green Clocks and Blue Clocks or
decreased with Red Clocks found in chests that will be added to the itempool. The goal of this mode
is to finish the game without the timer reaching zero, but the game will continue uninterrupted if
the player runs out of time.
## Tile Flip / Mixed Overworld (--ow_mixed)
## Progressive equipment
Tile Flip (often referred to as Mixed OWR) can be thought of as a hybrid of Open and Inverted, where OW tiles are randomly chosen to be flipped to become a part of the opposite world. When this occurs, that tile will use the Inverted version of that tile. For instance, if the Cave 45 tile becomes flipped, that means while walking around in the LW, you will find the screen that's south of Stumpy instead, and Cave 45 will instead be found in the DW; but like Inverted, the Cave 45 tile is modified to not have a ledge, this ensures that it will be possible to access it.
Determines if Sword, Shield, and gloves are progressive (upgrading in sequence) or not.
Being that this uses concepts from Inverted, it will be important to review the OWR-exclusive changes that have been made to Inverted (often referred to as Inverted 2.0). See `Inverted Changes` for more details.
### On (Default)
During gameplay:
- When on the OW, there will be an L or D in the upper left corner, indicating which world you are currently in. Mirroring still works the same, you must be in the DW to mirror to the LW.
- When doing a map check (pressing X while on the OW), the tiles shown will reflect the flipped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world. Here is an image showing the difference of appearance when tiles are flipped on the [map check](https://zelda.codemann8.com/images/shared/lttp-lw-mapcheck.gif) screen.
This setting makes swords, shields, armor, and gloves progressive. The first of any type of equipment found
by the player will be the lowest level item, and each subsequent find of a category will upgrade that type of
equipment.
Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must flip together.
### Off
## Tile Flip vs Crossed Explained
The above OWR options are very difficult to describe. The above descriptions are written in a way that is most correct even when these options are combined with more complicated modes. But, this section aims to simplify the explanation by assuming the user chooses a normal 'Open 7/7 Defeat Ganon' seed but with just one OWR setting enabled.
Both of these options are very similar and often confused from each other.
- Tile Flip is a mode where some DW tiles are moved and BECOME part of the LW (and the LW counterparts become part of the DW). What does it mean to "become" part of a world? It means that it will inherit (NOT bring over) the properties of that world it is moving to (such as being able to flute, ability to use the mirror, or being susceptible to bunnying).
- Crossed on the other hand doesn't change the properties of tiles, instead it transports Link *physically?* across worlds upon transitioning. This also means that Link can be transformed into a bunny moving from tile to tile.
tldr: Tile Flip moves the tiles, Crossed moves Link
So, let's run an example of 2 tiles, Link's House and the screen to the right of it. Transitioning right from Link's House: In vanilla, you get the Stone Bridge screen and Link stays his normal self and is just normal LW behavior. Now, let's assume Links House screen stays vanilla, but the tile to the right is getting Flipped or Crossed.
- In Tile Flip, you'd get the Hammer Bridge screen and Link would stay as Link and you'd be able to flute away from this screen if you had a flute.
- In Crossed, you'd get the same Hammer Bridge Screen, but this time Link would be transformed into a bunny, just like he'd normally be when on that tile.
- In Polar Crossed (when both Tile Flip and Crossed effects are applied together), you get the normal Stone Bridge screen, but Link is transformed to a bunny (because the Stone Bridge screen has moved to the DW AND Link is also moving across worlds).
As you can see, things get pretty complicated when mixing modes together. Doing this can definitely create a very unique and interesting experience, but one that is very hard to grasp. And then beyond that there is OW Layout Shuffle, which is where transition destinations are shuffled, so Link will get transported to a different tile entirely, but the same rules apply when you eventually find the Stone/Hammer Bridge screen, you just likely won't find that screen thru a transition on Link's House screen.
## Whirlpool Shuffle (--ow_whirlpool)
When enabled, the whirlpool connections are shuffled. If Crossed OW is enabled, the whirlpools can also be cross-world as well. When the number of crossed edges are limited thru Customizer, this doesn't count towards the limited number of crossed edge transitions.
## Flute Shuffle (--ow_fluteshuffle)
When enabled, new flute spots are generated and gives the player the option to cancel out of the flute menu by pressing X.
Note: Desert Teleporter Ledge is always guaranteed to be chosen. One of the three Mountain tiles are guaranteed if OW Layout Shuffle is set to Vanilla.
### Vanilla
Flute spots remain unchanged.
### Balanced
New flute spots are chosen at random, with restrictions that limit the promixity between other chosen flute spots.
This setting makes swords, shields, armor, and gloves non-progressive. All of the items of these types will be
randomly placed in chests, and the player could find them in any order and thus instantly receive high level equipment.
Downgrades are not possible; finding a lower level piece of equipment than what is already in the player's possession
will simply do nothing.
### Random
New flute spots are chosen at random with minimum bias.
This setting makes swords, shields, armor, and gloves randomly either progressive or not. Each category is independently
randomized.
## Follower Shuffle (--shuffle_followers)
## Item Distribution Algorithm
This shuffles the follower companions throughout the world. Here is a full list of followers in the game:
Determines how the items are shuffled.
- Princess Zelda
- Old Man
- Blind Maiden
- Frog/Blacksmith
- Locksmith (Guy who unlocks Purple Chest)
- Kiki the Monkey
- Purple Chest
- Super Bomb
### Balanced
This is a variation of VT26 that aims to strike a balance between the overworld heavy VT25 and the dungeon heavy VT26 algorithm.
It does this by reshuffling the remaining locations after placing dungeon items.
When followers are shuffled, you must still fulfill the original requirements of the follower location. For example, if the Super Bomb Shop now contains the Frog, you must have crystals 5 and 6 and 100 rupees to unlock the Frog. It is also important to note that Purple Chest still needs to be delivered to the usual spot. Also, since it isn't useful normally, many people might not know that the Locksmith (the Purple Chest unlocking guy) can follow you, you must remove his sign to get him to follow.
### VT26
Items and locations are shuffled like in VT25, and dungeon items are now placed using the same algorithm. When Ganon is not
shuffled it includes a slight deliberate bias against having too many desireable items in Ganon's Tower to help counterbalance
the sheer number of chests in that single location.
Most of the limitations of followers in the vanilla game have been lifted for this shuffle to work. For instance, you are able to mirror, flute, die, collect a crystal, and save/quit in most situations and still retain the follower. You are also able to enter caves/dungeons and complete dig game while you have a follower.
### VT25
Items and locations are shuffled and placed from the top of the lists. The only thing preventing an item from being placed into a spot
is if is absolutely impossible to be there given the previous made placement choices. Leads to very uniform but guaranteed solvable distributions.
In the scenario where you are forced down a narrow path and a follower is in your way and you already have a different follower. Running into that follower will switch the followers rather than overwriting, this gives you the opportunity to proceed with either one of the followers. Note that if you leave the screen, you lose this option to switch and you will need to go back to the original place you found the first follower.
### VT22
The ordinary VT v8.22 algorithm. Fixes issues in placement in VT21 by discarding all previously skipped and unfilled locations
after 2/3 of the progression items were placed to prevent stale late game locations from soaking up the same items all the time.
Optionally, thru the customizer, you can add a follower to your starting inventory, and you will be given that follower and it will stay with you until you complete their quest.
### VT21
The ordinary VT v8.21 algorithm. Unbiased placement of items into unlocked locations, placing items that unlock new locations first.
May lead to distributions that seem a bit wonky (high likelyhood of ice rod in Turtle Rock, for instance)
## Bonk Drop Shuffle (--bonk_drops)
### Flood
Pushes out items starting from Link's House and is slightly biased to placing progression items with less restrictions. Use for relatively simple distributions.
This adds 42 new item locations to the game. These bonk locations are limited to the ones that drop a static item in the vanilla game.
### Freshness
Alternative approach to VT22 to improve on VT21 flaws. Locations that are skipped because they are currently unreachable increase in
staleness, decreasing the likelihood of receiving a progress item.
- Bonk Locations consist of some trees, rocks, and statues
- 33 Trees
- 8 of the tree locations require Agahnim to be defeated to access the item
- 6 Rocks
- 1 of the rocks drops 2 items
- 2 Statues
- 1 of them is the Cold Fairy Statue next to Ice Rod Cave
- Bonk locations can be collected by bonking into them with the Pegasus Boots or using the Quake Medallion
- One of the bonk locations are guaranteed to have a full magic decanter
- Some of the drops can be farmed repeatedly, but only increments the collection rate once
- All of the bonk trees have been given an alternate color (and all non-bonk trees are reverted to normal tree color)
- Some screens are coded to change the "alternate tree color", some of them are strange (just how the vanilla game does it)
- Rocks and statues are unable to be made to have a different color
## Entrance Shuffle Algorithm
Here is a map that shows all the [Bonk Locations](https://zelda.codemann8.com/images/shared/bonkdrops.png). FYI, the numbers indicate how many bonk items there. The stars with a green square are all Bonk Locations that are unlocked after you kill Aga 1.
Determines how locations are shuffled. In all modes other than Insanity and the similar legacy versions, holes shuffle as a pair with the connecting cave and the front
two sections of Skull Woods remain confined to the general Skull Woods area. Link's house is never shuffled as a design decision.
As far as map trackers, Bonk Locations are supported on `CodeTracker` when the Bonk Drops option is enabled.
### Vanilla
#### Items Added To Pool:
- 15 Fairies
- 8 Apples
- 6 Bee Traps
- 3 Red Rupees
- 3 Blue Rupees
- 2 Single Bombs
- 2 Small Hearts
- 1 Large Magic Decanter
- 1 8x Bomb Pack
- 1 Good Bee
Places entrances in the same locations they were in the original The Legend of Zelda: A Link to the Past.
## Nearby Dungeon Items
### Simple
This is a new option in addition to the traditional wild vs non-wild (keysanity/non-keysanity) options for all the dungeon item types (maps, compasses, small keys, big keys, prizes). This new option shuffles dungeon items into locations somewhere either within the dungeon that it is assigned to or within the surrounding district of that dungeon.
Shuffles dungeon entrances between each other and keeps all 4-entrance dungeons confined to one location such that dungeons will one to one swap with each other.
Other than on Light World Death Mountain, interiors are shuffled but still connect the same points on the overworld. On Death Mountain, entrances are connected more freely.
## Prize Shuffle (--prizeshuffle)
### Restricted
A new option has been added to shuffle the 10 dungeon prizes in ways that they haven't been shuffled before. This means that dungeon prizes can be found in other item locations, such as chests or free-standing item locations. This also means that bosses are able to drop a 2nd item in place of the shuffled prize.
Uses dungeon shuffling from Simple but freely connects remaining entrances. Caves and dungeons with multiple entrances will be confined to one world.
### On Boss
### Full
This is the normal prize behavior that has been a part of rando up until now. The dungeon prizes are shuffled amongst themselves in their own pool and aren't considered part of the main collection total.
Mixes cave and dungeon entrances freely. Caves and dungeons with multiple entrances will be confined to one world.
### In Dungeon
### Crossed
This option shuffles the prize into a location somewhere within the dungeon that it is assigned to.
Mixes cave and dungeon entrances freely, but now connector caves and dungeons can link Light World and Dark World.
### Nearby
### Insanity
This option shuffles the prize into a location somewhere either within the dungeon that it is assigned to or within the surrounding district of that dungeon.
Decouples entrances and exits from each other and shuffles them freely. Caves that were single entrance in vanilla still can only exit to the same location from which they were entered.
### Randomized
### Legacy Variants
This option freely shuffles the prizes throughout the world. While the dungeon prizes can end up anywhere, they still are assigned to a specific dungeon. When you defeat the boss of a certain dungeon, checking the map on the overworld will reveal the location WHERE you can find the prize, an example shown [here](https://zelda.codemann8.com/images/shared/prizemap-all.gif). Finding the map will still reveal WHAT the prize is. If you defeated a boss but haven't collected the map for that dungeon, the prize will be indicated by a red X, example shown [here](https://zelda.codemann8.com/images/shared/prizemap-boss.gif). If you collected a map but haven't defeated the boss yet, the icon indicator on the map will be shown on the top edge (for LW dungeons) or the bottom edge (for DW dungeons), but it will show you WHAT the prize is for that dungeon, an example of that is shown [here](https://zelda.codemann8.com/images/shared/prizemap-map.gif).
Similar to the base shuffles, but the distinction between single entrance and multi-entrance caves from older versions of the randomizer is maintained.
Madness_Legacy is the more similar to the modern Insanity. Insanity_Legacy has fake worlds and guaranteed Moon Pearl and Magic Mirror for a very different experience.
- It is important to note that the overworld map check has changed: the numbered icons that are displayed are NO LONGER indicating the crystal number like they have in the past. They are now indicating the dungeon that it belongs to; a blue E/D/T indicates the 3 LW dungeons (EP, DP, and ToH) and a red 1-7 indicate the 7 DW dungeons
### Dungeon Variants
## New Goal Options (--goal)
The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla.
### Trinity
## Heartbeep Sound Rate
This goal gives you the choice between 3 goals, only one of which the player needs to complete: Fast Ganon (no Aga2), pulling Pedestal, or turn in TF pieces to Murahdahla. By default, you need to find 8 of 10 total TF pieces but this can be changed with a Custom Item Pool. It is recommended to set GT Entry to 7 crystals and Ganon to 4 or 5 crystals or Random crystals, although the player can flexibly change these settings as they see fit.
Select frequency of beeps when on low health. Can completely disable them.
## New Entrance Shuffle Options (--shuffle)
## Heart Color
### Lite
Select the color of Link's hearts.
This mode is intended to be a beginner-friendly introduction to playing ER, unlike that of Simple ER. It focuses on reducing Low% world traversal in late-game dungeons while reducing the number of entrances needing to be checked.
## Menu Speed
This mode groups entrances into types and shuffles them freely within those groups.
- Dungeons and Connectors (Multi-Entrance Caves)
- Item Locations (Single-Entrance Caves with an item, includes Potion Shop and Red Bomb Shop)
- Includes Shops only if Shopsanity is enabled
- Includes caves with pots only if Pottery settings have them shuffled)
- Dropdowns and their associated exits (Skull Woods dropdowns are handled the same as in Crossed)
- Non-item locations (junk locations) all remain vanilla
A setting that lets the player set the rate at which the menu opens and closes.
Lite ER shuffles all connectors same-world, to limit bunny traversal. And to prevent Low% enemy and boss combat, some dungeons are confined to specific worlds.
## Create Spoiler Log
The following dungeons are guaranteed to be in the Light World:
- Hyrule Castle
- Eastern Palace
- Desert Palace
- Tower of Hera
- Agahnim's Tower
Output a Spoiler File.
The following are guaranteed to be in the Dark World:
- Ice Palace
- Misery Mire
- Turtle Rock
- Ganon's Tower
## Do not Create Patched Rom
Both Lite and Lean ER modes are supported in `CodeTracker`, showing only the entrances that are shuffled.
If set, will not produce a patched rom as output. Useful in conjunction with the spoiler log option to batch
generate spoilers for statistical analysis.
### Lean
## Enable L/R button quickswapping
This mode is intended to be a more refined and more competitive format to Crossed ER. It focuses on reducing the number of entrances needing to be checked, while giving the player unique routing options based on the entrance pools defined below, as opposed to mindlessly checking all the remaining entrances. The Dungeons/Connectors can connect cross-world.
Use to enable quick item swap with L/R buttons. Press L and R together to switch the state of items like the Mushroom/Powder pair.
This mode groups entrances into types and shuffles them freely within those groups.
- Dungeons and Connectors (Multi-Entrance Caves)
- Item Locations (Single-Entrance Caves with an item, includes Potion Shop and Red Bomb Shop)
- Includes Shops only if Shopsanity is enabled
- Includes caves with pots only if Pottery settings have them shuffled)
- Dropdowns and their associated exits (Skull Woods dropdowns are handled the same as in Crossed)
- Non-item locations (junk locations) all remain vanilla
## Keysanity
Both Lite and Lean ER modes are supported in `CodeTracker`, showing only the entrances that are shuffled.
This setting allows dungeon specific items (Small Key, Big Key, Map, Compass) to be distributed anywhere in the world and not just
in their native dungeon. Small Keys dropped by enemies or found in pots are not affected. The chest in southeast Skull Woods that
is traditionally a guaranteed Small Key still is. These items will be distributed according to the v26/balanced algorithm, but
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
### Swapped
## Retro
This mostly follows the rules of Crossed ER, but instead of entrances being randomly placed individually, random pairs of two entrances are chosen to swap with each other. This creates a unique experience where checking one entrance gives you information about another entrance, which can influence your future routing.
This setting turns all Small Keys into universal Small Keys that can be used in any dungeon and are distributed across the world.
The Bow now consumed rupees to shoot; the cost is 10 rupees per Wood Arrow and 50 per Silver Arrow. Shooting Wood Arrows requires
the purchase of an arrow item from shops, and to account for this and the dynamic use of keys, both Wood Arrows and Small Keys will
be added to several shops around the world. Four "take any" caves are added that allow the player to choose between an extra Heart
Container and a Bottle being filled with Blue Potion, and one of the four swords from the item pool is placed into a special cave as
well. The five caves that are removed for these will be randomly selected single entrance caves that did not contain any items or any shops.
In further concert with the Bow changes, all arrows under pots, in chests, and elsewhere in the seed will be replaced with rupees.
If there is an odd amount of entrances being shuffled in the pool, the last remaining entrance will be vanilla. All other entrances are guaranteed to be non-vanilla.
## Place Dungeon Items
Here is a [Swapped ER Reference Sheet](http://zelda.codemann8.com/images/shared/swapped-reference-sheet.png) that shows some of the caves/houses that are less familiar to most people.
If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world.
This may lead to different amount of itempool items being placed in a dungeon than you are used to.
### District
## Include Ganon's Tower and Pyramid Hole in Shuffle pool
This is an entrance shuffle that only shuffles entrances within their respective `Districts` (See Below). Also, dropdowns and the entrance that a dropdown normally exits are NOT kept together; this also means that the Skull Woods entrances are no longer guaranteed to lead to Skull Woods. Also, since there is no district that can span multiple worlds, this is NOT a cross-world entrance mode.
If set, Ganon's Tower is included in the dungeon shuffle pool and the Pyramid Hole/Exit pair is included in the Holes shuffle pool. Ganon can not be defeated until the primary goal is fulfilled.
This setting removes any bias against Ganon's Tower that some algorithms may have.
Districts are a concept originally conceived by Aerinon in the Door Randomizer, where parts of the OW are split into areas and given a name. Here is a [District Map](https://zelda.codemann8.com/images/shared/ow-districts-reference-sheet.png) showing how they are split up.
## Include Helpful Hints
If set, the 15 telepathic tiles and 5 storytellers scattered about Hyrule will give helpful hints about various items and entrances. An exact breakdown of the hint
distribution is provided as an included text file.
## Use Custom Item Pool
If set, the item pool normally associated with your difficulty setting is replaced by the item pool specified in the custom tab. This feature is only supported when the randomizer is run
via the GUI; attempting to set this via the command line does nothing.
## Seed
Can be used to set a seed number to generate. Using the same seed with same settings on the same version of the entrance randomizer will always yield an identical output.
## Count
Use to batch generate multiple seeds with same settings. If a seed number is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with the same seed number given will produce the same 10 (different) roms each time).
# Command Line Options
```
-h, --help
-h, --help
```
Show the help message and exit.
```
--ow_layout <mode>
--create_spoiler
```
For specifying the overworld layout shuffle you want as above. (default: vanilla)
Output a Spoiler File (default: False)
```
--ow_unparallel
--logic [{noglitches,minorglitches,nologic}]
```
With OW Layout Shuffle, this no longer forces both worlds to have a matching layout.
Select the game logic (default: noglitches)
```
--ow_terrain
--mode [{standard,open,swordless,inverted}]
```
With OW Layout Shuffle, this allows land and water edges to be connected.
Select the game mode. (default: open)
```
--ow_crossed <mode>
--goal [{ganon,pedestal,dungeons,triforcehunt,crystals}]
```
For specifying the type of cross-world connections you want on the overworld
Select the game completion goal. (default: ganon)
```
--ow_keepsimilar
--difficulty [{normal,hard,expert}]
```
This keeps similar edge transitions paired together with other pairs of transitions
Select the game difficulty. Affects available itempool. (default: normal)
```
--ow_mixed
--item_functionality [{normal,hard,expert}]
```
This gives each OW tile a random chance to be flipped to the opposite world
Select limits on item functionality to increase difficulty. (default: normal)
```
--ow_fluteshuffle <mode>
--timer [{none,display,timed,timed-ohko,ohko,timed-countdown}]
```
For randomizing the flute spots around the overworld
Select the timer setting. (default: none)
```
--ow_no_fog
--progressive [{on,off,random}]
```
With OW Grid Layout Shuffle or Mixed, this disables the fog that prevents you from seeing unvisited screens on the overworld map.
Select the setting for progressive equipment. (default: on)
```
--shuffle_followers
--algorithm [{freshness,flood,vt21,vt22,vt25,vt26,balanced}]
```
This shuffles the follower companion locations, ie. Purple Chest, Old Man, etc.
Select item distribution algorithm. (default: balanced)
```
--bonk_drops
--shuffle [{default,simple,restricted,full,crossed,insanity,restricted_legacy,full_legacy,madness_legacy,insanity_legacy,dungeonsfull,dungeonssimple}]
```
This extends the item pool to bonk locations and makes them additional item locations
Select entrance shuffle algorithm. (default: full)
```
--prizeshuffle <mode>
--rom ROM
```
This allows prizes (crystals/pendants) to shuffle outside their usual boss location.
Path to a Japanese 1.0 A Link to the Past Rom. (default: Zelda no Densetsu - Kamigami no Triforce (Japan).sfc)
```
--loglevel [{error,info,warning,debug}]
```
Select level of logging for output. (default: info)
```
--seed SEED
```
Define seed number to generate. (default: None)
```
--count COUNT
```
Set the count option (default: None)
```
--quickswap
```
Use to enable quick item swap with L/R buttons. (default: False)
```
--fastmenu [{normal,instant,double,triple,quadruple,half}]
```
Alters the rate at which the menu opens and closes. (default: normal)
```
--disablemusic
```
Disables game music, resulting in the game sound being just the SFX. (default: False)
```
--keysanity
```
Enable Keysanity (default: False)
```
--retro
```
Enable Retro mode (default: False)
```
--nodungeonitems
```
If set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world.
This may lead to different amount of itempool items being placed in a dungeon than you are used to. (default: False)
```
--heartbeep [{normal,half,quarter,off}]
```
Select frequency of beeps when on low health. (default: normal)
```
--heartcolor [{red,blue,green,yellow,random}]
```
Select the color of Link\'s heart meter. (default: red)
```
--sprite SPRITE
```
Use to select a different sprite sheet to use for Link. Path to a binary file of length 0x7000 containing the sprite data stored at address 0x80000 in the rom. (default: None)
```
--accessibility [{items,locations,none}]
```
Sets the item/location accessibility rules. (default: items)
```
--hints
```
Enables helpful hints from storytellers and telepathic tiles (default: False)
```
--no-shuffleganon
```
Disables the "Include Ganon's Tower and Pyramid Hole in Shuffle pool" option. (default: Enabled)
```
--suppress_rom
```
Enables the "Do not Create Patched Rom" option. (default: False)
```
--gui
```
Open the graphical user interface. Preloads selections with set command line parameters.
-159
View File
@@ -1,159 +0,0 @@
# New Features
FastROM changes have been included now.
## Enemizer Features
Please see this document for extensive details. [Enemizer in DR](https://docs.google.com/document/d/1iwY7Gy50DR3SsdXVaLFIbx4xRBqo9a-e1_jAl5LMCX8/edit?usp=sharing)
Key points:
* Enemizer no longer uses a third party program. It is now built-in.
* New option under Enemy Drops: Underworld. Any underworld enemy can drop items.
* New option under Enemizer tab: Enemy Logic
Please read the entire document above for extensive details about enemizer and enemy drop shuffle systems.
Enemizer main changes:
* Several sprites added to the pool. Most notable is how enemies behave on shallow water. They work now.
* Clearing rooms, spawnable chests, and enemy keys drops can now have enemies with specific logic in the room. This logic is controlled by the new Enemy Logic option
* New system for banning enemies that cause issue is place. If you see an enemy in a place that would cause issue, please report it and it can be banned to never happen again. Initial bans can be found [in the code](source/enemizer/enemy_deny.yaml) for the curious
* Thieves are always unkillable, but banned from the entire underworld. We can selectively ban them from problematic places in the overworld, and if someone wants to figure out where they could be safe in the underworld, I'll allow them there once the major problems have been banned.
* THe old "random" and "legacy" options have been discarded for enemy shuffle. Tile room patterns are currently shuffled with enemies.
Underworld drops:
* A flashing blue square added to help locate enemies that have remaining drops on the supertile. (Dungeons and caves without a compass get this for free.)
* Flying enemies, spawned enemies, and enemies with special death routines will not drop items.
* Pikits do not drop their item if they have eaten a shield.
* Hovers in swamp waterway do no drop items due to a layer issue that's not been solved.
* Enemies that are over pits require boomerang or hookshot to collect the item
* Enemies behind rails require the boomerang (hookshot can sequence break in certain cases)
* Enemies that spawn on walls do not drop items. (Keese normally don't, but in enemizer these can be valid drops otherwise. The document has a visual guide.)
(Older notes below)
One major change with this update is that big key doors and certain trap doors are no longer guaranteed to be vanilla in Dungeon Door Shuffle modes even if you choose not to shuffle those types. A newer algorithm for putting dungeons together has been written and it will remove big key doors and trap doors when necessary to ensure progress can be made.
Please note that retro features are now independently customizable as referenced below. Selecting Retro mode or World State: Retro will change Bow Mode to Retro (Progressive). Take Anys to Random, and Small Keys to Universal.
## Flute Mode
Normal mode for flute means you need to activate it at the village statue after finding it like usual.
Activated flute mode mean you can use it immediately upon finding it. the flute SFX plays to let you know this is the case.
## Bow Mode
Four options here:
* Progressive. Standard progressive bows.
* Silvers separate. One bow in the pool and silvers are a separate item.
* Retro (progressive). Arrows cost rupees. You need to purchase the single arrow item at a shop and there are two progressive bows places.
* Retro + Silvers. Arrows cost rupees. You need to purchase the single arrow item or find the silvers, there is only one bow, and silvers are a separate item (but count for the quiver if found).
## Dungeon Shuffle Features
### Small Keys
There are three options now available:
* In Dungeon: The small key will be in their own dungeon
* Randomized: Small keys can be shuffled outside their own dungeon
* Universal: Retro keys without the other options
### Dungeon Door Shuffle
New mode: Partitioned. Partly between basic and crossed, dungeons are shuffled in 3 pools:
* Light World dungeons, Hyrule Castle and Aga Tower are mixed together
* Palace of Darkness, Swamp Palace, Skull Woods, and Thieves Town are mixed together
* The other dark world dungeons including Ganons Tower are mixed together
### Door Types to Shuffle
Four options here, and all of them only take effect if Dungeon Door Shuffle is not Vanilla:
* Small Key Doors, Bomb Doors, Dash Doors: This is what was normally shuffled previously
* Adds Big Keys Doors: Big key doors are now shuffled in addition to those above, and Big Key doors are enabled to be on in both vertical directions thanks to a graphic that ended up on the cutting room floor. This does change
* Adds Trap Doors: All trap doors that are permanently shut in vanilla are shuffled.
* Increases all Door Types: This is a chaos mode where each door type per dungeon is randomized between 1 less and 4 more.
Note: Boss Trap doors are removed currently and not added into the trap door pool as extra trap doors. This may not be a permanent change
### Decouple Doors
This is similar to insanity mode in ER where door entrances and exits are not paired anymore. Tends to remove more logic from dungeons as many rooms will not be required to traverse to explore. Hope you like transitions.
## Customizer
Please see [Customizer documentation](docs/Customizer.md) on how to create custom seeds.
## New Goals
### Ganonhunt
Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
### Completionist
All dungeons not enough for you? You have to obtain every item in the game too. This option turns on the collection rate counter and forces accessibility to be 100% locations. Finish by defeating Ganon.
## Standard Generation Change
Hyrule Castle in standard mode is generated a little differently now. The throne room is guaranteed to be in Hyrule Castle and the Sanctuary is guaranteed to be beyond that. Additionally, the Mirror Scroll will bring you back to Zelda's Cell or the Throne Room depending on what save point you last obtained, this is to make it consistent with where you end up if you die. If you are lucky enough to find the Mirror, it behaves differently and brings you the last entrance used - giving you more options for exploration in Hyrule Castle.
## ER Features
### New Experimental Algorithm
To accommodate future flexibility the ER algorithm was rewritten for easy of use. This allows future modes to be added more easily. This new algorithm is only used when the experimental flag is turned on.
### Lite/Lean ER (Experimental required)
Designed by Codemann, these are available now (only with experimental turned on - they otherwise fail)
#### Lite
- Dungeon and multi-entrance caves can only lead to dungeon and multi-entrance caves
- Dropdowns can only lead to dropdowns, with them staying coupled to their appropriate exits
- Cave entrances that normally lead to items can only lead to caves that have items (this includes Potion Shop and Big Bomb Shop)
- All remaining entrances remain vanilla
- Multi-entrance caves are connected same-world only
- LW is guaranteed to have HC/EP/DP/ToH/AT and DW: IP/MM/TR/GT
- Shop locations are included in the Item Cave pool if Shopsanity is enabled
- Houses with pots are included in the Item Cave pool if Pottery is enabled
#### Lean
- Same grouping/pooling mechanism as in Lite ER
- Both dungeons and connectors can be cross-world connections
- No dungeon guarantees like in Lite ER
### Back of Tavern Shuffle (Experimental required)
Thanks goes to Catobat which now allows the back of tavern to be shuffled anywhere and any valid cave can be at the back of tavern with this option checked. Available in experimental only for now as it requires the new algorithm to be shuffled properly.
#### Take Any Caves
These are now independent of retro mode and have three options: None, Random, and Fixed. None disables the caves. Random works as take-any caves did before. Fixed means that the take any caves replace specific fairy caves in the pool and will be at those entrances unless ER is turned on (then they can be shuffled wherever). The fixed entrances are:
* Desert Healer Fairy
* Swamp Healer Fairy (aka Light Hype Cave)
* Dark Death Mountain Healer Fairy
* Dark Lake Hylia Ledge Healer Fairy (aka Shopping Mall Bomb)
* Bonk Fairy (Dark)
# Patch Notes
* 1.5.0
* Logic: Fixed vanilla key logic for GT basement
* Logic (Playthrough): Fixed an issue where enemy kill rules were not applied during playthrough calculation. (Thanks Catobat for the catch)
* Enemy Drop: Added "spies" and shadows for hidden enemies when enemy drop shuffled is enabled
* Pottery: Pots will uncolor when the item inside is collected next time the room is loaded
* Keysanity/Keydrop Menu for DR:
* Map key information is now controlled by the Dungeon Chest Counts setting. If set to always on, this information will be available right away in the menu and will be on the HUD even when the map is not obtained.
* The key counter on the HUD for the current dungeon now accounts for keys from enemies or pots that are from vanilla key locations.
* The first number on the HUD represents all keys collected either in that dungeon or elsewhere.
* The second number on the HUD is the total keys that can be collected either in that dungeon or elsewhere.
* The key counter on inside the Menu is unchanged. (At the bottom near A button items)
* The first number in the Menu is the current number of keys in your inventory
* The second number is how many keys left to find in chests (not those from pots/enemies unless those item pools are enabled)
* Customizer: free_lamp_cone option added. The logic will account for this, and place the lamp without regard to dark rooms.
* Customizer: force_enemy option added that makes all enemies the specified type if possible. There are known gfx glitches in the overworld.
* Optimization: Improved generation performance (Thanks Catobat!)
-1
View File
@@ -22,7 +22,6 @@ def _wrap(name):
# These are for intellisense purposes only, and will be overwritten below
choice = _prng_inst.choice
choices = _prng_inst.choices
gauss = _prng_inst.gauss
getrandbits = _prng_inst.getrandbits
randint = _prng_inst.randint
+561 -1688
View File
File diff suppressed because it is too large Load Diff
+959 -2575
View File
File diff suppressed because it is too large Load Diff
-528
View File
@@ -1,528 +0,0 @@
from enum import Enum, unique
from Tables import door_pair_offset_table
def create_rooms(world, player):
world.rooms += [
Room(player, 0x01, 0x51168).door(Position.WestN2, DoorKind.Warp).door(Position.EastN2, DoorKind.Warp),
Room(player, 0x02, 0x50b97).door(Position.South2, DoorKind.TrapTriggerableLow).door(Position.InteriorV2, DoorKind.NormalLow2).door(Position.South2, DoorKind.ToggleFlag),
# Room(player, 0x03, 0x509cf).door(Position.SouthW, DoorKind.CaveEntrance),
Room(player, 0x04, 0xfe25c).door(Position.NorthW, DoorKind.StairKey2).door(Position.InteriorW, DoorKind.Dashable).door(Position.InteriorS, DoorKind.Dashable).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.Normal),
Room(player, 0x06, 0xfa192).door(Position.SouthW, DoorKind.Trap),
Room(player, 0x07, None),
# Room(player, 0x08, 0x5064f).door(Position.InteriorS2, DoorKind.CaveEntranceLow08).door(Position.SouthE, DoorKind.CaveEntrance).door(Position.SouthW2, DoorKind.NormalLow2).door(Position.SouthW2, DoorKind.ToggleFlag),
Room(player, 0x09, None),
Room(player, 0x0a, 0xfa734).door(Position.North, DoorKind.StairKey),
Room(player, 0x0b, 0xfabf0).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorN, DoorKind.SmallKey),
Room(player, 0x0c, 0xfef53).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0x0d, 0xf918b).door(Position.SouthW, DoorKind.Trap),
Room(player, 0x0e, 0xfc279).door(Position.InteriorW, DoorKind.StairKey2).door(Position.InteriorS, DoorKind.Trap).door(Position.SouthE, DoorKind.DungeonEntrance),
# Room(player, 0x10, 0x50596).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x11, 0x50c52).door(Position.InteriorN, DoorKind.Dashable).door(Position.InteriorS, DoorKind.Dashable).door(Position.SouthE, DoorKind.SmallKey),
Room(player, 0x12, 0x50a9b).door(Position.North2, DoorKind.NormalLow).door(Position.North2, DoorKind.ToggleFlag).door(Position.South2, DoorKind.NormalLow).door(Position.South2, DoorKind.IncognitoEntrance),
Room(player, 0x13, 0xfe29d).door(Position.EastS, DoorKind.SmallKey).door(Position.EastN, DoorKind.Normal),
Room(player, 0x14, 0xfe464).door(Position.SouthE, DoorKind.SmallKey).door(Position.WestS, DoorKind.SmallKey).door(Position.NorthW, DoorKind.Normal).door(Position.WestN, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.EastN, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x15, 0xfe63e).door(Position.WestS, DoorKind.Trap).door(Position.WestN, DoorKind.Normal),
Room(player, 0x16, 0xfa150).door(Position.InteriorV, DoorKind.Bombable).door(Position.InteriorW, DoorKind.SmallKey).door(Position.InteriorE, DoorKind.Normal).door(Position.NorthW, DoorKind.Normal),
Room(player, 0x17, None),
# Room(player, 0x18, 0x506e5).door(Position.NorthW2, DoorKind.NormalLow).door(Position.NorthW2, DoorKind.ToggleFlag),
Room(player, 0x19, 0xfacc6).door(Position.East, DoorKind.Bombable).door(Position.EastN, DoorKind.SmallKey),
Room(player, 0x1a, 0xfa670).door(Position.InteriorE, DoorKind.SmallKey).door(Position.WestN, DoorKind.SmallKey).door(Position.West, DoorKind.Bombable).door(Position.SouthW, DoorKind.SmallKey).door(Position.InteriorN, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x1b, 0xfab31).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.Normal),
Room(player, 0x1c, 0xff784).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap).door(Position.InteriorW, DoorKind.Dashable),
Room(player, 0x1d, 0xfff19).door(Position.NorthW, DoorKind.BigKey),
Room(player, 0x1e, 0xfc35e).door(Position.EastS, DoorKind.Trap).door(Position.InteriorS, DoorKind.Trap).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x1f, 0xfc3af).door(Position.WestS, DoorKind.Trap).door(Position.InteriorS, DoorKind.Trap2),
Room(player, 0x20, 0xf918b).door(Position.SouthW, DoorKind.Trap),
Room(player, 0x21, 0x50d2e).door(Position.NorthE, DoorKind.SmallKey).door(Position.InteriorV, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x22, 0x50dd1).door(Position.South, DoorKind.SmallKey).door(Position.WestS, DoorKind.Normal),
Room(player, 0x23, 0xfed30).door(Position.SouthE, DoorKind.BombableEntrance).door(Position.EastS, DoorKind.Normal),
Room(player, 0x24, 0xfe6ee).door(Position.NorthE, DoorKind.BigKey).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorW, DoorKind.Trap2).door(Position.InteriorE, DoorKind.Trap2).door(Position.SouthE, DoorKind.DungeonEntrance).door(Position.NorthW, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x26, 0xf9cbb).door(Position.South, DoorKind.SmallKey).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.InteriorN, DoorKind.Normal),
Room(player, 0x27, None),
Room(player, 0x28, 0xf92a8).door(Position.NorthW, DoorKind.StairKey2).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0x2a, 0xfa594).door(Position.NorthE, DoorKind.Trap).door(Position.NorthW, DoorKind.SmallKey).door(Position.EastS, DoorKind.Bombable).door(Position.East2, DoorKind.NormalLow).door(Position.SouthW, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x2b, 0xfaaa7).door(Position.InteriorS, DoorKind.Bombable).door(Position.WestS, DoorKind.Bombable).door(Position.NorthW, DoorKind.Trap).door(Position.West2, DoorKind.NormalLow),
# Room(player, 0x2c, 0x508cf).door(Position.InteriorW, DoorKind.Bombable).door(Position.InteriorE, DoorKind.Bombable).door(Position.InteriorS, DoorKind.Bombable).door(Position.SouthE, DoorKind.Bombable).door(Position.SouthW, DoorKind.CaveEntrance),
Room(player, 0x2e, 0xfc3d8).door(Position.NorthE, DoorKind.Normal),
# Room(player, 0x2f, 0x507d1).door(Position.InteriorW, DoorKind.Bombable).door(Position.SouthE, DoorKind.CaveEntrance),
Room(player, 0x30, 0xf8de3).door(Position.NorthW, DoorKind.Hidden).door(Position.InteriorW, DoorKind.Trap2),
Room(player, 0x31, 0xfcf4f).door(Position.InteriorW, DoorKind.BigKey).door(Position.InteriorS, DoorKind.TrapTriggerable),
Room(player, 0x32, 0x50e4b).door(Position.North, DoorKind.SmallKey),
Room(player, 0x33, 0xf8792).door(Position.SouthW, DoorKind.Trap),
Room(player, 0x34, 0xf993c).door(Position.EastN, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x35, 0xf97f1).door(Position.EastN, DoorKind.SmallKey).door(Position.WestN, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.InteriorE, DoorKind.Normal)
.door(Position.EastS, DoorKind.Normal).door(Position.InteriorV2, DoorKind.NormalLow),
Room(player, 0x36, 0xf9685).door(Position.EastN, DoorKind.Bombable).door(Position.North, DoorKind.SmallKey).door(Position.WestN, DoorKind.SmallKey).door(Position.WestS, DoorKind.Normal)
.door(Position.EastS, DoorKind.Normal).door(Position.South2, DoorKind.NormalLow),
Room(player, 0x37, 0xf9492).door(Position.WestN, DoorKind.Bombable).door(Position.EastN, DoorKind.Bombable).door(Position.InteriorW, DoorKind.SmallKey).door(Position.EastS, DoorKind.SmallKey).door(Position.WestS, DoorKind.Normal).door(Position.InteriorV2, DoorKind.NormalLow),
Room(player, 0x38, 0xf935b).door(Position.WestN, DoorKind.Bombable).door(Position.WestS, DoorKind.SmallKey),
Room(player, 0x39, 0xfc180).door(Position.SouthW, DoorKind.Trap).door(Position.InteriorS, DoorKind.SmallKey),
Room(player, 0x3a, 0xfa3f5).door(Position.South, DoorKind.SmallKey).door(Position.NorthW, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal),
Room(player, 0x3b, 0xfa9de).door(Position.SouthW, DoorKind.Normal),
# Room(player, 0x3c, 0x509a3).door(Position.NorthE, DoorKind.Bombable).door(Position.SouthE, DoorKind.CaveEntrance),
Room(player, 0x3d, 0xffd37).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.InteriorN, DoorKind.SmallKey).door(Position.SouthW, DoorKind.SmallKey).door(Position.InteriorW, DoorKind.Bombable),
Room(player, 0x3e, 0xfc486).door(Position.InteriorE, DoorKind.Trap).door(Position.SouthW, DoorKind.SmallKey),
Room(player, 0x3f, 0xfc51b).door(Position.InteriorS, DoorKind.Trap),
Room(player, 0x40, 0xf8eea).door(Position.InteriorS2, DoorKind.NormalLow2),
Room(player, 0x41, 0x50f15).door(Position.South, DoorKind.Trap),
Room(player, 0x42, None),
Room(player, 0x43, 0xf87f8).door(Position.NorthW, DoorKind.BigKey).door(Position.InteriorE, DoorKind.SmallKey).door(Position.SouthE, DoorKind.SmallKey),
Room(player, 0x44, 0xfdbcd).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorS, DoorKind.SmallKey).door(Position.EastN, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x45, 0xfdcae).door(Position.WestN, DoorKind.Trap).door(Position.InteriorW, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x46, 0xf9bbb).door(Position.North2, DoorKind.NormalLow).door(Position.InteriorW2, DoorKind.NormalLow).door(Position.InteriorE2, DoorKind.NormalLow),
Room(player, 0x49, 0xfc12c).door(Position.NorthW, DoorKind.Hidden).door(Position.InteriorN, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.SmallKey).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x4a, 0xfa267).door(Position.InteriorW, DoorKind.Trap).door(Position.InteriorE, DoorKind.Trap).door(Position.North, DoorKind.SmallKey).door(Position.InteriorV, DoorKind.Normal).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0x4b, 0xfa8c9).door(Position.NorthW, DoorKind.Trap).door(Position.InteriorW, DoorKind.Dashable).door(Position.InteriorE, DoorKind.Dashable),
Room(player, 0x4c, 0xffece).door(Position.EastS, DoorKind.Trap),
Room(player, 0x4d, 0xffe5a).door(Position.NorthW, DoorKind.SmallKey).door(Position.WestS, DoorKind.Normal),
Room(player, 0x4e, 0xfc5ba).door(Position.InteriorN, DoorKind.Trap).door(Position.NorthW, DoorKind.SmallKey),
Room(player, 0x4f, 0xfca89).door(Position.WestS, DoorKind.SmallKey),
Room(player, 0x50, 0x510dc).door(Position.EastN2, DoorKind.Warp).door(Position.SouthE2, DoorKind.NormalLow2),
Room(player, 0x51, 0x51029).door(Position.North, DoorKind.Normal).door(Position.North, DoorKind.DungeonChanger),
Room(player, 0x52, 0x51230).door(Position.WestN2, DoorKind.Warp).door(Position.SouthW2, DoorKind.NormalLow2).door(Position.South, DoorKind.Normal),
Room(player, 0x53, 0xf88ad).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap2).door(Position.NorthE, DoorKind.SmallKey),
Room(player, 0x54, None),
# Room(player, 0x55, 0x50166).door(Position.InteriorW2, DoorKind.NormalLow).door(Position.SouthW, DoorKind.Normal).door(Position.SouthW, DoorKind.IncognitoEntrance),
Room(player, 0x56, 0xfbb4e).door(Position.InteriorW, DoorKind.SmallKey).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x57, 0xfbbd2).door(Position.InteriorN, DoorKind.Bombable).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.EastS, DoorKind.SmallKey).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.WestS, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x58, 0xfbcf6).door(Position.NorthW, DoorKind.BlastWall).door(Position.WestS, DoorKind.SmallKey).door(Position.SouthE, DoorKind.SmallKey).door(Position.InteriorN, DoorKind.Bombable).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x59, 0xfbff7).door(Position.NorthW, DoorKind.SmallKey).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.InteriorN2, DoorKind.NormalLow2).door(Position.InteriorS2, DoorKind.NormalLow2),
Room(player, 0x5a, 0xfa7e5).door(Position.SouthE, DoorKind.Trap),
Room(player, 0x5b, 0xff8cc).door(Position.SouthE, DoorKind.SmallKey).door(Position.EastN, DoorKind.Trap),
Room(player, 0x5c, 0xff976).door(Position.InteriorE, DoorKind.Bombable).door(Position.WestN, DoorKind.Normal),
Room(player, 0x5d, 0xff9e1).door(Position.InteriorW, DoorKind.Trap).door(Position.SouthW, DoorKind.Trap).door(Position.InteriorN, DoorKind.Trap),
Room(player, 0x5e, 0xfc6b8).door(Position.EastS, DoorKind.SmallKey).door(Position.InteriorE, DoorKind.Trap2).door(Position.SouthE, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x5f, 0xfc6fa).door(Position.WestS, DoorKind.SmallKey),
Room(player, 0x60, 0x51309).door(Position.NorthE2, DoorKind.NormalLow2).door(Position.East2, DoorKind.NormalLow2).door(Position.East2, DoorKind.ToggleFlag).door(Position.EastN, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal).door(Position.SouthE, DoorKind.IncognitoEntrance),
Room(player, 0x61, 0x51454).door(Position.West2, DoorKind.NormalLow).door(Position.West2, DoorKind.ToggleFlag).door(Position.East2, DoorKind.NormalLow).door(Position.East2, DoorKind.ToggleFlag).door(Position.South2, DoorKind.NormalLow).door(Position.South2, DoorKind.IncognitoEntrance).door(Position.WestN, DoorKind.Normal),
Room(player, 0x62, 0x51577).door(Position.West2, DoorKind.NormalLow2).door(Position.West2, DoorKind.ToggleFlag).door(Position.NorthW2, DoorKind.NormalLow2).door(Position.North, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthW, DoorKind.IncognitoEntrance),
Room(player, 0x63, 0xf88ed).door(Position.NorthW, DoorKind.StairKey).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.DungeonEntrance), # looked like a huge typo - I had to guess on StairKey
Room(player, 0x64, 0xfda53).door(Position.InteriorS, DoorKind.Trap2),
Room(player, 0x65, 0xfdac5).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x66, 0xfa01b).door(Position.InteriorE2, DoorKind.Waterfall).door(Position.SouthW2, DoorKind.NormalLow2).door(Position.SouthW2, DoorKind.ToggleFlag).door(Position.InteriorW2, DoorKind.NormalLow2),
Room(player, 0x67, 0xfbe17).door(Position.NorthE, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x68, 0xfbf02).door(Position.WestS, DoorKind.Trap).door(Position.NorthE, DoorKind.SmallKey),
Room(player, 0x6a, 0xfa7c7).door(Position.NorthE, DoorKind.BigKey),
Room(player, 0x6b, 0xff821).door(Position.NorthE, DoorKind.BigKey).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap).door(Position.InteriorW, DoorKind.Trap),
Room(player, 0x6c, 0xffaa0).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorW, DoorKind.Trap).door(Position.EastS, DoorKind.Normal),
Room(player, 0x6d, 0xffa4e).door(Position.NorthW, DoorKind.Trap).door(Position.InteriorW, DoorKind.Trap).door(Position.WestS, DoorKind.Trap),
Room(player, 0x6e, 0xfc74b).door(Position.NorthE, DoorKind.Trap),
Room(player, 0x70, None),
Room(player, 0x71, 0x52341).door(Position.InteriorW, DoorKind.SmallKey).door(Position.SouthW2, DoorKind.TrapTriggerableLow).door(Position.InteriorS2, DoorKind.TrapTriggerableLow),
Room(player, 0x72, 0x51fda).door(Position.InteriorV, DoorKind.SmallKey),
Room(player, 0x73, 0xf8972).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorE, DoorKind.Normal),
Room(player, 0x74, 0xf8a66).door(Position.InteriorE, DoorKind.Normal).door(Position.InteriorW, DoorKind.Normal),
Room(player, 0x75, 0xf8ab9).door(Position.InteriorW, DoorKind.Trap2).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x76, 0xf9e35).door(Position.InteriorN2, DoorKind.NormalLow).door(Position.InteriorS2, DoorKind.NormalLow).door(Position.NorthW2, DoorKind.NormalLow).door(Position.NorthW2, DoorKind.ToggleFlag),
Room(player, 0x77, 0xfd0e6).door(Position.NorthW2, DoorKind.StairKeyLow).door(Position.South2, DoorKind.DungeonEntranceLow),
Room(player, 0x7b, 0xff02b).door(Position.SouthW, DoorKind.Trap).door(Position.EastN, DoorKind.SmallKey).door(Position.EastS, DoorKind.Normal),
Room(player, 0x7c, 0xff0ef).door(Position.NorthE, DoorKind.BlastWall).door(Position.EastS, DoorKind.Bombable).door(Position.WestN, DoorKind.SmallKey).door(Position.WestS, DoorKind.Normal),
Room(player, 0x7d, 0xff20c).door(Position.SouthE, DoorKind.Trap).door(Position.WestS, DoorKind.Bombable).door(Position.InteriorW, DoorKind.SmallKey),
Room(player, 0x7e, 0xfc7c6).door(Position.SouthE, DoorKind.SmallKey).door(Position.InteriorS, DoorKind.TrapTriggerable).door(Position.EastN, DoorKind.Normal),
Room(player, 0x7f, 0xfc827).door(Position.WestN, DoorKind.Trap).door(Position.InteriorW, DoorKind.Normal),
Room(player, 0x80, None),
Room(player, 0x81, 0x5224b).door(Position.NorthW2, DoorKind.NormalLow2),
Room(player, 0x82, None),
Room(player, 0x83, 0xf8bba).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x84, 0xf8cb7).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0x85, 0xf8d7d).door(Position.NorthE, DoorKind.Trap).door(Position.InteriorN, DoorKind.SmallKey).door(Position.SouthE, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x87, 0xfd1b7).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorE, DoorKind.Normal),
Room(player, 0x89, None),
Room(player, 0x8b, 0xff33f).door(Position.InteriorN, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.SmallKey).door(Position.EastN, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.NorthW, DoorKind.Normal),
Room(player, 0x8c, 0xff3ef).door(Position.EastN, DoorKind.Trap).door(Position.InteriorW, DoorKind.Trap2).door(Position.InteriorN, DoorKind.SmallKey).door(Position.WestN, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x8d, 0xff4e0).door(Position.SouthE, DoorKind.Trap).door(Position.InteriorN, DoorKind.SmallKey).door(Position.WestN, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x8e, 0xfc84d).door(Position.NorthE, DoorKind.SmallKey),
Room(player, 0x90, 0xfbab2).door(Position.SouthW, DoorKind.Trap),
Room(player, 0x91, 0xfb9e6).door(Position.EastS, DoorKind.Normal),
Room(player, 0x92, 0xfb97b).door(Position.InteriorN, DoorKind.Bombable).door(Position.InteriorW, DoorKind.Bombable).door(Position.WestS, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x93, 0xfb8e1).door(Position.InteriorW, DoorKind.Trap2).door(Position.InteriorE, DoorKind.SmallKey).door(Position.WestS, DoorKind.Normal),
Room(player, 0x95, 0xffc04).door(Position.SouthE, DoorKind.Normal).door(Position.EastN, DoorKind.Normal),
Room(player, 0x96, 0xffc78).door(Position.InteriorS, DoorKind.Trap2).door(Position.WestN, DoorKind.Normal),
Room(player, 0x97, 0xfb30a).door(Position.InteriorS, DoorKind.Normal).door(Position.InteriorW, DoorKind.Normal),
Room(player, 0x98, 0xfaf5b).door(Position.SouthW, DoorKind.DungeonEntrance),
Room(player, 0x99, 0x5172a).door(Position.InteriorW, DoorKind.StairKey).door(Position.South, DoorKind.SmallKey).door(Position.InteriorE, DoorKind.Normal),
Room(player, 0x9b, 0xff5a2).door(Position.InteriorN, DoorKind.SmallKey).door(Position.NorthW, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x9c, 0xff6c9).door(Position.EastS, DoorKind.Trap).door(Position.NorthW, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal).door(Position.WestS, DoorKind.Normal),
Room(player, 0x9d, 0xff741).door(Position.NorthE, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.InteriorN, DoorKind.Normal),
Room(player, 0x9e, 0xfc8c8).door(Position.NorthE, DoorKind.StairKey2).door(Position.InteriorE, DoorKind.BigKey).door(Position.InteriorS, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x9f, 0xfc937).door(Position.WestS, DoorKind.Trap).door(Position.SouthW, DoorKind.Trap),
Room(player, 0xa0, 0xfba9a).door(Position.NorthW, DoorKind.BigKey),
Room(player, 0xa1, 0xfb83d).door(Position.SouthE, DoorKind.SmallKey).door(Position.East, DoorKind.Normal),
Room(player, 0xa2, 0xfb759).door(Position.South, DoorKind.SmallKey).door(Position.West, DoorKind.Normal).door(Position.East, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0xa3, 0xfb667).door(Position.West, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal),
Room(player, 0xa4, 0xfe741).door(Position.SouthW, DoorKind.Trap),
Room(player, 0xa5, 0xffb7f).door(Position.NorthE, DoorKind.Trap).door(Position.InteriorE, DoorKind.Trap2).door(Position.InteriorW, DoorKind.Trap2),
Room(player, 0xa6, None),
Room(player, 0xa8, 0x51887).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthE, DoorKind.SmallKey).door(Position.InteriorN2, DoorKind.NormalLow2).door(Position.EastN2, DoorKind.NormalLow2).door(Position.East, DoorKind.Normal),
Room(player, 0xa9, 0x519c9).door(Position.West, DoorKind.Trap).door(Position.East, DoorKind.Trap).door(Position.North, DoorKind.BigKey).door(Position.WestN2, DoorKind.NormalLow2).door(Position.EastN2, DoorKind.NormalLow2).door(Position.South, DoorKind.Normal),
Room(player, 0xaa, 0x51b29).door(Position.InteriorE, DoorKind.Trap2).door(Position.WestN2, DoorKind.NormalLow2).door(Position.InteriorN, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal).door(Position.West, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal),
Room(player, 0xab, 0xfd9a9).door(Position.InteriorW, DoorKind.StairKey).door(Position.SouthW, DoorKind.Normal),
Room(player, 0xac, 0xfd9d8).door(Position.SouthE, DoorKind.Trap),
Room(player, 0xae, 0xfc975).door(Position.EastN, DoorKind.Normal),
Room(player, 0xaf, 0xfc9e1).door(Position.NorthW, DoorKind.Normal).door(Position.WestN, DoorKind.Normal),
Room(player, 0xb0, 0xf8f6b).door(Position.InteriorW, DoorKind.Trap).door(Position.InteriorN, DoorKind.Trap).door(Position.InteriorS, DoorKind.SmallKey),
Room(player, 0xb1, 0xfb3b7).door(Position.InteriorW, DoorKind.BigKey).door(Position.NorthE, DoorKind.SmallKey).door(Position.SouthE, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0xb2, 0xfb4ad).door(Position.North, DoorKind.BigKey).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.EastN2, DoorKind.NormalLow2).door(Position.NorthE, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0xb3, 0xfb5e4).door(Position.InteriorW, DoorKind.SmallKey).door(Position.WestN2, DoorKind.NormalLow2).door(Position.NorthW, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal),
Room(player, 0xb4, 0xfe807).door(Position.NorthW, DoorKind.BigKey),
Room(player, 0xb5, 0xfeb07).door(Position.SouthW, DoorKind.Trap),
Room(player, 0xb6, 0xfdd50).door(Position.NorthW, DoorKind.StairKey2).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.SmallKey).door(Position.InteriorW, DoorKind.SmallKey).door(Position.SouthE, DoorKind.Normal),
Room(player, 0xb7, 0xfddcd).door(Position.SouthW, DoorKind.Normal),
Room(player, 0xb8, 0x51b75).door(Position.NorthE, DoorKind.BigKey).door(Position.EastN, DoorKind.Normal),
Room(player, 0xb9, 0x51d09).door(Position.EastN, DoorKind.SmallKey).door(Position.North, DoorKind.Normal).door(Position.South, DoorKind.Normal).door(Position.WestN, DoorKind.Normal),
Room(player, 0xba, 0x51d57).door(Position.WestN, DoorKind.SmallKey).door(Position.NorthW, DoorKind.Trap).door(Position.InteriorN, DoorKind.Trap2),
Room(player, 0xbb, 0xfd86b).door(Position.NorthW, DoorKind.Normal).door(Position.InteriorN, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal).door(Position.InteriorE, DoorKind.Normal).door(Position.EastN, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0xbc, 0xfd974).door(Position.InteriorS, DoorKind.SmallKey).door(Position.SouthE, DoorKind.SmallKey).door(Position.InteriorN, DoorKind.Trap).door(Position.SouthW, DoorKind.Bombable).door(Position.WestN, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.InteriorW, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal),
Room(player, 0xbe, 0xfca28).door(Position.SouthE, DoorKind.Trap).door(Position.EastS, DoorKind.SmallKey).door(Position.InteriorE, DoorKind.Normal),
Room(player, 0xbf, 0xfca89).door(Position.WestS, DoorKind.SmallKey),
Room(player, 0xc0, 0xf9026).door(Position.InteriorN, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap2).door(Position.NorthE, DoorKind.StairKey),
Room(player, 0xc1, 0xfb176).door(Position.InteriorS, DoorKind.SmallKey).door(Position.EastS, DoorKind.SmallKey).door(Position.InteriorN, DoorKind.TrapTriggerable).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.Normal).door(Position.EastN, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0xc2, 0xfb0e7).door(Position.EastN, DoorKind.SmallKey).door(Position.WestS, DoorKind.SmallKey).door(Position.NorthW, DoorKind.Normal).door(Position.WestN, DoorKind.Normal).door(Position.East, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal).door(Position.EastS, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal),
Room(player, 0xc3, 0xfb56c).door(Position.WestN, DoorKind.SmallKey).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorH, DoorKind.Trap2).door(Position.InteriorS, DoorKind.TrapTriggerable).door(Position.NorthW, DoorKind.Normal).door(Position.West, DoorKind.Normal).door(Position.WestS, DoorKind.Normal),
Room(player, 0xc4, 0xfec3f).door(Position.EastS, DoorKind.SmallKey),
Room(player, 0xc5, 0xfece1).door(Position.WestS, DoorKind.SmallKey).door(Position.NorthW, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal),
Room(player, 0xc6, 0xfdf5c).door(Position.NorthW, DoorKind.SmallKey).door(Position.NorthE, DoorKind.Normal).door(Position.EastN, DoorKind.Normal).door(Position.EastS, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0xc7, 0xfe0a1).door(Position.NorthW, DoorKind.Trap).door(Position.WestN, DoorKind.Normal).door(Position.WestS, DoorKind.Normal),
Room(player, 0xc8, 0x51596).door(Position.SouthE, DoorKind.Trap),
Room(player, 0xc9, 0x51e5a).door(Position.InteriorV, DoorKind.Trap).door(Position.North, DoorKind.Trap).door(Position.InteriorW, DoorKind.Normal).door(Position.InteriorE, DoorKind.Normal).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0xcb, 0xfd630).door(Position.East, DoorKind.Dashable),
Room(player, 0xcc, 0xfd783).door(Position.NorthE, DoorKind.BigKey).door(Position.NorthW, DoorKind.Bombable).door(Position.West, DoorKind.Dashable),
Room(player, 0xce, 0xfcadd).door(Position.NorthE, DoorKind.Trap),
Room(player, 0xd0, 0xf90de).door(Position.InteriorS, DoorKind.SmallKey).door(Position.InteriorN, DoorKind.Trap2),
Room(player, 0xd1, 0xfb259).door(Position.InteriorS, DoorKind.Trap2).door(Position.NorthW, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal).door(Position.InteriorE, DoorKind.Normal),
Room(player, 0xd2, 0xfafd6).door(Position.NorthE, DoorKind.Trap),
Room(player, 0xd5, 0xfee40).door(Position.SouthW, DoorKind.BombableEntrance).door(Position.NorthW, DoorKind.Normal),
Room(player, 0xd6, 0xfe1cb).door(Position.NorthW, DoorKind.UnknownD6).door(Position.SouthE, DoorKind.DungeonEntrance).door(Position.NorthE, DoorKind.Normal),
Room(player, 0xd8, 0x515ed).door(Position.NorthE, DoorKind.Trap).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.EastS, DoorKind.Normal),
Room(player, 0xd9, 0x5166f).door(Position.WestS, DoorKind.Trap).door(Position.InteriorS, DoorKind.Trap).door(Position.EastS, DoorKind.Trap),
Room(player, 0xda, 0x5169d).door(Position.WestS, DoorKind.Trap),
Room(player, 0xdb, 0xfd370).door(Position.East, DoorKind.Trap).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0xdc, 0xfd4d1).door(Position.West, DoorKind.Normal),
# Room(player, 0xdf, 0x52db4).door(Position.South, DoorKind.CaveEntrance),
Room(player, 0xe0, 0xf9149).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorW, DoorKind.Trap2).door(Position.NorthE, DoorKind.StairKey).door(Position.SouthW, DoorKind.DungeonEntrance),
# Room(player, 0xe1, 0x5023c).door(Position.InteriorH2, DoorKind.NormalLow2).door(Position.SouthW, DoorKind.CaveEntrance),
# Room(player, 0xe2, 0x50464).door(Position.InteriorH, DoorKind.Normal).door(Position.SouthE, DoorKind.CaveEntrance),
# Room(player, 0xe3, 0x5032b).door(Position.InteriorS2, DoorKind.TrapLowE3).door(Position.InteriorE2, DoorKind.NormalLow2).door(Position.SouthW, DoorKind.CaveEntrance),
# Room(player, 0xe4, 0x534b1).door(Position.SouthW2, DoorKind.CaveEntranceLow).door(Position.InteriorN, DoorKind.Normal).door(Position.East, DoorKind.Normal),
# Room(player, 0xe5, 0x535ba).door(Position.West, DoorKind.Normal).door(Position.South, DoorKind.CaveEntrance),
# Room(player, 0xe6, 0x532ee).door(Position.SouthW2, DoorKind.CaveEntranceLow).door(Position.EastN2, DoorKind.NormalLow),
# Room(player, 0xe7, 0x533ce).door(Position.SouthE2, DoorKind.CaveEntranceLow).door(Position.WestN2, DoorKind.NormalLow),
# Room(player, 0xe8, 0x529d3).door(Position.SouthE, DoorKind.CaveEntrance),
# Room(player, 0xea, 0x531f5).door(Position.SouthW, DoorKind.CaveEntrance),
# Room(player, 0xeb, 0x52e1a).door(Position.SouthE, DoorKind.CaveEntrance),
# Room(player, 0xed, 0x52bec).door(Position.SouthE, DoorKind.CaveEntrance),
# Room(player, 0xee, 0x52f76).door(Position.SouthE, DoorKind.CaveEntrance),
# Room(player, 0xef, 0x52d37).door(Position.InteriorE, DoorKind.Trap2).door(Position.South, DoorKind.CaveEntrance),
# Room(player, 0xf0, 0x5258a).door(Position.SouthW, DoorKind.CaveEntrance).door(Position.East2, DoorKind.NormalLow),
# Room(player, 0xf1, 0x52703).door(Position.SouthE2, DoorKind.CaveEntranceLow).door(Position.West2, DoorKind.NormalLow),
# Room(player, 0xf2, 0x5274a).door(Position.EastS, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal).door(Position.SouthE, DoorKind.IncognitoEntrance),
# Room(player, 0xf3, 0x52799).door(Position.WestS, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthW, DoorKind.IncognitoEntrance),
# Room(player, 0xf4, 0x527d3).door(Position.EastS, DoorKind.Dashable).door(Position.SouthE, DoorKind.Normal).door(Position.SouthE, DoorKind.IncognitoEntrance),
# Room(player, 0xf5, 0x52813).door(Position.WestS, DoorKind.Dashable).door(Position.SouthW, DoorKind.Normal).door(Position.SouthW, DoorKind.IncognitoEntrance),
# Room(player, 0xf8, 0x528fe).door(Position.South, DoorKind.CaveEntrance),
# Room(player, 0xf9, 0x5305a).door(Position.SouthW, DoorKind.CaveEntrance),
# Room(player, 0xfa, 0x53165).door(Position.SouthW2, DoorKind.EntranceLow),
# Room(player, 0xfb, 0x52ea4).door(Position.South, DoorKind.CaveEntrance),
# Room(player, 0xfd, 0x52ab1).door(Position.South2, DoorKind.CaveEntranceLow),
# Room(player, 0xfe, 0x52ff1).door(Position.SouthE2, DoorKind.CaveEntranceLow),
# Room(player, 0xff, 0x52c9a).door(Position.InteriorW, DoorKind.Bombable).door(Position.InteriorE, DoorKind.Bombable).door(Position.SouthE, DoorKind.CaveEntrance),
]
# fix some wonky things
# should I put back the dungeon changer for certain logic - like no logic? maybe in basic
if world.doorShuffle[player] != 'vanilla':
world.get_room(0x51, player).delete(1) # remove the dungeon changer
world.get_room(0x60, player).swap(2, 4) # puts the exit at pos 2 - enables pos 3
world.get_room(0x61, player).swap(1, 6) # puts the WN door at pos 1 - enables it
world.get_room(0x61, player).swap(5, 6) # puts the Incognito Entrance at the end, so it can be deleted
world.get_room(0x62, player).swap(1, 4) # puts the exit at pos 1 - enables pos 3
world.get_room(0x77, player).swap(0, 1) # fixes Hera Lobby Key Stairs - entrance now at pos 0
if world.enemy_shuffle[player] != 'none':
world.get_room(0xc0, player).change(0, DoorKind.Normal) # fix this kill room if enemizer is on
world.get_room(0x0e, player).change(1, DoorKind.TrapTriggerable) # fix this kill room if enemizer is on
def reset_rooms(world, player):
world.rooms = [x for x in world.rooms if x.player != player]
world._room_cache.clear()
create_rooms(world, player)
class Room(object):
def __init__(self, player, index, address):
self.player = player
self.index = index
self.doorListAddress = address
self.doorList = []
self.modified = False
self.palette = None
def position(self, door):
return self.doorList[door.doorListPos][0]
def kind(self, door):
return self.doorList[door.doorListPos][1]
def door(self, pos, kind):
self.doorList.append((pos, kind))
return self
def change(self, list_idx, kind):
prev = self.doorList[list_idx]
self.doorList[list_idx] = (prev[0], kind)
self.modified = True
def mirror(self, list_idx):
prev = self.doorList[list_idx]
mirror_door = None
for door in self.doorList:
if door != prev:
mirror_door = door
break
self.doorList[list_idx] = (mirror_door[0], mirror_door[1])
self.modified = True
def swap(self, idx1, idx2):
item1 = self.doorList[idx1]
item2 = self.doorList[idx2]
self.doorList[idx1] = item2
self.doorList[idx2] = item1
self.modified = True
def delete(self, list_idx):
self.doorList[list_idx] = (Position.FF, DoorKind.FF)
self.modified = True
def address(self):
return self.doorListAddress
def rom_data(self):
byte_array = []
for pos, kind in self.doorList:
byte_array.append(pos.value)
byte_array.append(kind.value)
return byte_array
def next_free(self, pos=4):
for i, door in enumerate(self.doorList):
if i >= pos:
return None
position, kind = door
if kind not in [DoorKind.SmallKey, DoorKind.Dashable, DoorKind.Bombable, DoorKind.TrapTriggerable,
DoorKind.Trap, DoorKind.Trap2, DoorKind.TrapTriggerableLow, DoorKind.TrapLowE3,
DoorKind.BigKey, DoorKind.StairKey, DoorKind.StairKey2, DoorKind.StairKeyLow,
DoorKind.BlastWall, DoorKind.BombableEntrance]:
return i
return None
def __str__(self):
return str(self.__unicode__())
def __unicode__(self):
return '%s' % self.index
class PairedDoor(object):
def __init__(self, door_a, door_b, original=False):
self.door_a = door_a
self.door_b = door_b
self.pair = True
self.original = original
def address_a(self, world, player):
d = world.check_for_door(self.door_a, player)
return 0x13C000 + (door_pair_offset_table[d.roomIndex]+d.doorListPos)*2
def address_b(self, world, player):
d = world.check_for_door(self.door_b, player)
return 0x13C000 + (door_pair_offset_table[d.roomIndex]+d.doorListPos)*2
def rom_data_a(self, world, player):
if not self.pair:
return [0x00, 0x00]
d = world.check_for_door(self.door_b, player)
return [d.roomIndex, pos_map[d.doorListPos]]
def rom_data_b(self, world, player):
if not self.pair:
return [0x00, 0x00]
d = world.check_for_door(self.door_a, player)
return [d.roomIndex, pos_map[d.doorListPos]]
pos_map = {
0: 0x80, 1: 0x40, 2: 0x20, 3: 0x10
# indices 4-7 not supported yet
}
@unique
class DoorKind(Enum):
Normal = 0x00
NormalLow = 0x02
EntranceLow = 0x04
Waterfall = 0x08
DungeonEntrance = 0x0A
DungeonEntranceLow = 0x0C
CaveEntrance = 0x0E
CaveEntranceLow = 0x10
IncognitoEntrance = 0x12
DungeonChanger = 0x14
ToggleFlag = 0x16
Trap = 0x18 # both sides trapped
UnknownD6 = 0x1A
SmallKey = 0x1C
BigKey = 0x1E
StairKey = 0x20
StairKey2 = 0x22
HauntedStairKey = 0x24 # not a real door, can see it in dark rooms when facing left
StairKeyLow = 0x26
Dashable = 0x28
BombableEntrance = 0x2A
Bombable = 0x2E
BlastWall = 0x30
Hidden = 0x32
TrapTriggerable = 0x36 # right side trap or bottom side trap (door directions: West, North)
Trap2 = 0x38 # left side trap or top side trap (door directions: East, South)
NormalLow2 = 0x40
TrapTriggerableLow = 0x44
Warp = 0x46
CaveEntranceLow08 = 0x48
TrapLowE3 = 0x4A # Maybe this is a toggle flag too?
FF = 0xFF
@unique
class Position(Enum):
NorthW = 0x00
North = 0x10
NorthE = 0x20
NorthW2 = 0x30
North2 = 0x40
NorthE2 = 0x50
InteriorW = 0x60
InteriorV = 0x70
InteriorE = 0x80
InteriorW2 = 0x90
InteriorV2 = 0xA0
InteriorE2 = 0xB0
SouthW = 0x61
South = 0x71
SouthE = 0x81
SouthW2 = 0x91
South2 = 0xA1
SouthE2 = 0xB1
WestN = 0x02
West = 0x12
WestS = 0x22
WestN2 = 0x32
West2 = 0x42
# WestS2 = 0x52
InteriorN = 0x62
InteriorH = 0x72
InteriorS = 0x82
InteriorN2 = 0x92
InteriorH2 = 0xA2
InteriorS2 = 0xB2
EastN = 0x63
East = 0x73
EastS = 0x83
EastN2 = 0x93
East2 = 0xA3
# EastS2 = 0xB3
FF = 0xFF
class TestWorld(object):
def __init__(self):
self.rooms = []
# python3 -c "from RoomData import offset_utility; offset_utility()"
# This utility was used to calculate the distance offsets
def offset_utility():
world = TestWorld()
create_rooms(world, 1)
map = {}
cntr = 1
for room in world.rooms:
map[room.index] = cntr
cntr = cntr + len(room.doorList)
string = ''
for i in range(225):
if i % 16 == 0:
string = string + 'dw '
if i not in map:
string = string + '$0000,'
else:
string = string + hex(map[i]) + ','
print(string)
# python3 -c "from RoomData import key_door_template_generator; key_door_template_generator()"
# This utility was used to help initialize the pairing data
def key_door_template_generator():
world = TestWorld()
create_rooms(world, 1)
map = {}
cntr = 1
for room in world.rooms:
string = 'dw '
for door in room.doorList:
if door[1] in [DoorKind.SmallKey, DoorKind.BigKey, DoorKind.SmallKey, DoorKind.Dashable, DoorKind.Bombable]:
string = string + '$xxxx,'
else:
string = string + '$0000,'
print(string[0:-1])
# python3 -c "from RoomData import door_address_list; door_address_list('/home/randall/kwyn/orig/z3.sfc')"
# python3 -c "from RoomData import door_address_list; door_address_list('path/to/rom.sfc')"
def door_address_list(rom):
with open(rom, 'rb') as stream:
rom_data = bytearray(stream.read())
room_index = 0
while room_index < 256:
offset = room_index * 3
address = rom_data[0x0F8000 + offset]
address = address + 0x100 * rom_data[0x0F8000 + offset + 1]
byte3 = rom_data[0x0F8000 + offset + 2]
address = address + (byte3 << 16)
if byte3 == 0x03:
address = address - 0x020000
elif byte3 == 0x0A:
address = address - 0x058000
elif byte3 == 0x1f:
address = address - 0x100000
else:
print('Byte3 ' + hex(byte3))
print('Address ' + hex(address))
raise Exception('Bad address?')
terminated = False
while not terminated:
marker = rom_data[address] + (rom_data[address+1] << 8)
# if marker == 0xFFFF:
# print('Room '+ hex(room_index)+ ' terminated at '+ hex(address))
# terminated = True
if marker == 0xFFF0:
print(hex(room_index) + ': ' + hex(address+2))
# print('Room ' + hex(room_index) + ' address: ' + hex(address+2))
terminated = True
else:
address = address + 3
room_index = room_index + 1
+851 -2201
View File
File diff suppressed because it is too large Load Diff
-286
View File
@@ -1,286 +0,0 @@
## Door Tables ##
normal_offset_table = {
0x01: 0x01, 0x02: 0x02, 0x04: 0x03, 0x06: 0x04, 0x0d: 0x05, 0x11: 0x06, 0x12: 0x07, 0x13: 0x08,
0x14: 0x09, 0x15: 0x0A, 0x16: 0x0B, 0x19: 0x0C, 0x1a: 0x0D, 0x1b: 0x0E, 0x1d: 0x0F, 0x1e: 0x10,
0x1f: 0x11, 0x20: 0x12, 0x21: 0x13, 0x22: 0x14, 0x23: 0x15, 0x24: 0x16, 0x26: 0x17, 0x2a: 0x18,
0x2b: 0x19, 0x2e: 0x1A, 0x30: 0x1B, 0x32: 0x1C, 0x33: 0x1D, 0x34: 0x1E, 0x35: 0x1F, 0x36: 0x20,
0x37: 0x21, 0x38: 0x22, 0x39: 0x23, 0x3a: 0x24, 0x3b: 0x25, 0x3d: 0x26, 0x3e: 0x27, 0x41: 0x28,
0x43: 0x29, 0x44: 0x2A, 0x45: 0x2B, 0x46: 0x2C, 0x49: 0x2D, 0x4a: 0x2E, 0x4b: 0x2F, 0x4c: 0x30,
0x4d: 0x31, 0x4e: 0x32, 0x50: 0x33, 0x51: 0x34, 0x52: 0x35, 0x53: 0x36, 0x56: 0x37, 0x57: 0x38,
0x58: 0x39, 0x59: 0x3A, 0x5a: 0x3B, 0x5b: 0x3C, 0x5c: 0x3D, 0x5d: 0x3E, 0x5e: 0x3F, 0x5f: 0x40,
0x60: 0x41, 0x61: 0x42, 0x62: 0x43, 0x66: 0x44, 0x67: 0x45, 0x68: 0x46, 0x6a: 0x47, 0x6b: 0x48,
0x6c: 0x49, 0x6d: 0x4A, 0x6e: 0x4B, 0x71: 0x4C, 0x75: 0x4D, 0x76: 0x4E, 0x7b: 0x4F, 0x7c: 0x50,
0x7d: 0x51, 0x7e: 0x52, 0x7f: 0x53, 0x81: 0x54, 0x85: 0x55, 0x8b: 0x56, 0x8c: 0x57, 0x8d: 0x58,
0x8e: 0x59, 0x90: 0x5A, 0x91: 0x5B, 0x92: 0x5C, 0x93: 0x5D, 0x95: 0x5E, 0x96: 0x5F, 0x99: 0x60,
0x9b: 0x61, 0x9c: 0x62, 0x9d: 0x63, 0x9e: 0x64, 0x9f: 0x65, 0xa0: 0x66, 0xa1: 0x67, 0xa2: 0x68,
0xa3: 0x69, 0xa4: 0x6A, 0xa5: 0x6B, 0xa8: 0x6C, 0xa9: 0x6D, 0xaa: 0x6E, 0xab: 0x6F, 0xac: 0x70,
0xae: 0x71, 0xaf: 0x72, 0xb1: 0x73, 0xb2: 0x74, 0xb3: 0x75, 0xb4: 0x76, 0xb5: 0x77, 0xb6: 0x78,
0xb7: 0x79, 0xb8: 0x7A, 0xb9: 0x7B, 0xba: 0x7C, 0xbb: 0x7D, 0xbc: 0x7E, 0xbe: 0x7F, 0xbf: 0x80,
0xc1: 0x81, 0xc2: 0x82, 0xc3: 0x83, 0xc4: 0x84, 0xc5: 0x85, 0xc6: 0x86, 0xc7: 0x87, 0xc8: 0x88,
0xc9: 0x89, 0xcb: 0x8A, 0xcc: 0x8B, 0xce: 0x8C, 0xd1: 0x8D, 0xd2: 0x8E, 0xd5: 0x8F, 0xd6: 0x90,
0xd8: 0x91, 0xd9: 0x92, 0xda: 0x93, 0xdb: 0x94, 0xdc: 0x95,
0x40: 0x96, 0x42: 0x97, # newcomers for str stairs
# newcomers for entrances
0x28: 0x98, 0x0e: 0x99, 0x0c: 0x9a, 0x98: 0x9b, 0x83: 0x9c, 0x84: 0x9d, 0x77: 0x9e, 0xe0: 0x9f,
0x63: 0xa0
}
spiral_offset_table = {
0x01: 0x01, 0x02: 0x02, 0x04: 0x03, 0x07: 0x04, 0x09: 0x05, 0x0a: 0x07, 0x0c: 0x08, 0x0e: 0x0b,
0x11: 0x0c, 0x15: 0x0d, 0x16: 0x0e, 0x17: 0x0f, 0x1a: 0x11, 0x1c: 0x13, 0x1d: 0x14, 0x1e: 0x15,
0x26: 0x16, 0x27: 0x19, 0x28: 0x1b, 0x31: 0x1c, 0x34: 0x1f, 0x38: 0x20, 0x3a: 0x21, 0x3f: 0x22,
0x40: 0x23, 0x41: 0x24, 0x42: 0x25, 0x45: 0x26, 0x4a: 0x27, 0x4c: 0x29, 0x4d: 0x2a, 0x4e: 0x2b,
0x53: 0x2c, 0x54: 0x2d, 0x5c: 0x2e, 0x5d: 0x2f, 0x5f: 0x30, 0x63: 0x35, 0x64: 0x36, 0x66: 0x37,
0x6a: 0x38, 0x6b: 0x3a, 0x6c: 0x3b, 0x6e: 0x3c, 0x70: 0x3d, 0x71: 0x40, 0x72: 0x41, 0x76: 0x42,
0x77: 0x45, 0x7f: 0x49, 0x80: 0x4a, 0x87: 0x4b, 0x8c: 0x4f, 0x8e: 0x53, 0x91: 0x54, 0x93: 0x55,
0x97: 0x56, 0x98: 0x57, 0x99: 0x58, 0x9e: 0x59, 0xa0: 0x5a, 0xa2: 0x5b, 0xa5: 0x5c, 0xa6: 0x5d,
0xab: 0x5e, 0xae: 0x5f, 0xb0: 0x60, 0xb5: 0x63, 0xb6: 0x64, 0xbc: 0x65, 0xbe: 0x66, 0xc0: 0x67,
0xd0: 0x6a, 0xd1: 0x6d, 0xd2: 0x6e, 0xda: 0x6f, 0xe0: 0x70
}
door_pair_offset_table = {
0x01: 0x0001, 0x02: 0x0003, 0x04: 0x0006, 0x06: 0x000b, 0x0a: 0x000c, 0x0b: 0x000d, 0x0c: 0x0010, 0x0d: 0x0011,
0x0e: 0x0012, 0x11: 0x0015, 0x12: 0x0018, 0x13: 0x001c, 0x14: 0x001e, 0x15: 0x0025, 0x16: 0x0027, 0x19: 0x002b,
0x1a: 0x002d, 0x1b: 0x0033, 0x1c: 0x0035, 0x1d: 0x0038, 0x1e: 0x0039, 0x1f: 0x003d, 0x20: 0x003f, 0x21: 0x0040,
0x22: 0x0043, 0x23: 0x0045, 0x24: 0x0047, 0x26: 0x004f, 0x28: 0x0053, 0x2a: 0x0055, 0x2b: 0x005b, 0x2e: 0x005f,
0x30: 0x0060, 0x31: 0x0062, 0x32: 0x0064, 0x33: 0x0065, 0x34: 0x0066, 0x35: 0x0068, 0x36: 0x006e, 0x37: 0x0074,
0x38: 0x007a, 0x39: 0x007c, 0x3a: 0x007e, 0x3b: 0x0081, 0x3d: 0x0082, 0x3e: 0x0086, 0x3f: 0x0088, 0x40: 0x0089,
0x41: 0x008a, 0x43: 0x008b, 0x44: 0x008e, 0x45: 0x0092, 0x46: 0x0096, 0x49: 0x0099, 0x4a: 0x009d, 0x4b: 0x00a2,
0x4c: 0x00a5, 0x4d: 0x00a6, 0x4e: 0x00a8, 0x4f: 0x00aa, 0x50: 0x00ab, 0x51: 0x00ad, 0x52: 0x00af, 0x53: 0x00b2,
0x56: 0x00b5, 0x57: 0x00b9, 0x58: 0x00bf, 0x59: 0x00c5, 0x5a: 0x00c9, 0x5b: 0x00ca, 0x5c: 0x00cc, 0x5d: 0x00ce,
0x5e: 0x00d1, 0x5f: 0x00d5, 0x60: 0x00d6, 0x61: 0x00dc, 0x62: 0x00e3, 0x63: 0x00e9, 0x64: 0x00ec, 0x65: 0x00ed,
0x66: 0x00ee, 0x67: 0x00f2, 0x68: 0x00f5, 0x6a: 0x00f7, 0x6b: 0x00f8, 0x6c: 0x00fc, 0x6d: 0x00ff, 0x6e: 0x0102,
0x71: 0x0103, 0x72: 0x0106, 0x73: 0x0107, 0x74: 0x010a, 0x75: 0x010c, 0x76: 0x010e, 0x77: 0x0112, 0x7b: 0x0114,
0x7c: 0x0117, 0x7d: 0x011b, 0x7e: 0x011e, 0x7f: 0x0121, 0x81: 0x0123, 0x83: 0x0124, 0x84: 0x0127, 0x85: 0x0128,
0x87: 0x012c, 0x8b: 0x012e, 0x8c: 0x0133, 0x8d: 0x0139, 0x8e: 0x013e, 0x90: 0x013f, 0x91: 0x0140, 0x92: 0x0141,
0x93: 0x0146, 0x95: 0x0149, 0x96: 0x014b, 0x97: 0x014d, 0x98: 0x014f, 0x99: 0x0150, 0x9b: 0x0153, 0x9c: 0x0156,
0x9d: 0x015a, 0x9e: 0x015d, 0x9f: 0x0161, 0xa0: 0x0163, 0xa1: 0x0164, 0xa2: 0x0166, 0xa3: 0x016a, 0xa4: 0x016c,
0xa5: 0x016d, 0xa8: 0x0170, 0xa9: 0x0176, 0xaa: 0x017c, 0xab: 0x0182, 0xac: 0x0184, 0xae: 0x0185, 0xaf: 0x0186,
0xb0: 0x0188, 0xb1: 0x018b, 0xb2: 0x018f, 0xb3: 0x0197, 0xb4: 0x019c, 0xb5: 0x019d, 0xb6: 0x019e, 0xb7: 0x01a3,
0xb8: 0x01a4, 0xb9: 0x01a6, 0xba: 0x01aa, 0xbb: 0x01ad, 0xbc: 0x01b3, 0xbe: 0x01bb, 0xbf: 0x01be, 0xc0: 0x01bf,
0xc1: 0x01c2, 0xc2: 0x01ca, 0xc3: 0x01d2, 0xc4: 0x01d9, 0xc5: 0x01da, 0xc6: 0x01dd, 0xc7: 0x01e3, 0xc8: 0x01e6,
0xc9: 0x01e7, 0xcb: 0x01ec, 0xcc: 0x01ed, 0xce: 0x01f0, 0xd0: 0x01f1, 0xd1: 0x01f3, 0xd2: 0x01f7, 0xd5: 0x01f8,
0xd6: 0x01fa, 0xd8: 0x01fd, 0xd9: 0x0200, 0xda: 0x0203, 0xdb: 0x0204, 0xdc: 0x0206, 0xe0: 0x0207
}
# Note: 0-7 correspond to 1,2,3,4,5,6,a,14 respectively, see doortables.asm : MultDivInfo
multiply_lookup = {
0x08: {0x8: 0, 0x10: 1, 0x18: 2, 0x20: 3, 0x30: 5, 0x50: 6, 0xa0: 7},
0x10: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 1, 0x30: 2, 0x50: 3, 0xa0: 6},
0x18: {0x8: 0, 0x10: 1, 0x18: 0, 0x20: 3, 0x30: 1, 0x50: 6, 0xa0: 7},
0x20: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 0, 0x30: 2, 0x50: 4, 0xa0: 4},
0x30: {0x8: 0, 0x10: 0, 0x18: 0, 0x20: 1, 0x30: 0, 0x50: 4, 0xa0: 6},
0x50: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 1, 0x30: 2, 0x50: 0, 0xa0: 1},
0xa0: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 0, 0x30: 2, 0x50: 0, 0xa0: 0},
}
divisor_lookup = {
0x08: {0x8: 0, 0x10: 0, 0x18: 0, 0x20: 0, 0x30: 0, 0x50: 0, 0xa0: 0},
0x10: {0x8: 1, 0x10: 0, 0x18: 1, 0x20: 0, 0x30: 0, 0x50: 0, 0xa0: 0},
0x18: {0x8: 2, 0x10: 2, 0x18: 0, 0x20: 2, 0x30: 0, 0x50: 2, 0xa0: 2},
0x20: {0x8: 3, 0x10: 1, 0x18: 3, 0x20: 0, 0x30: 1, 0x50: 1, 0xa0: 0},
0x30: {0x8: 5, 0x10: 2, 0x18: 1, 0x20: 2, 0x30: 0, 0x50: 2, 0xa0: 2},
0x50: {0x8: 6, 0x10: 3, 0x18: 6, 0x20: 4, 0x30: 4, 0x50: 0, 0xa0: 0},
0xa0: {0x8: 7, 0x10: 6, 0x18: 7, 0x20: 4, 0x30: 6, 0x50: 1, 0xa0: 0},
}
# I don't need this right now, but I don't want to throw it away either
# Note: this value + 0x028000 is where the header is for the room (PC not SNES addressing)
# room_header_lookup = {
# 0x0: 0x462, 0x1: 0x46c, 0x2: 0x47a, 0x3: 0x5dd, 0x4: 0x485, 0x5: 0x490, 0x6: 0x490, 0x7: 0x497,
# 0x8: 0x4a2, 0x9: 0x4a9, 0xa: 0x4b5, 0xb: 0x4c0, 0xc: 0x4cb, 0xd: 0x4d8, 0xe: 0x4df, 0xf: 0x4ea,
# 0x10: 0x4ea, 0x11: 0x4f1, 0x12: 0x4fc, 0x13: 0x503, 0x14: 0x511, 0x15: 0x518, 0x16: 0x523, 0x17: 0x52e,
# 0x18: 0xc73, 0x19: 0x53a, 0x1a: 0x541, 0x1b: 0x54d, 0x1c: 0x558, 0x1d: 0x563, 0x1e: 0x56e, 0x1f: 0x579,
# 0x20: 0x584, 0x21: 0x58b, 0x22: 0x58b, 0x23: 0x503, 0x24: 0x592, 0x25: 0x599, 0x26: 0x599, 0x27: 0x5a6,
# 0x28: 0x5b2, 0x29: 0x5bd, 0x2a: 0x5c4, 0x2b: 0x5cb, 0x2c: 0xc73, 0x2d: 0x5d6, 0x2e: 0x5d6, 0x2f: 0x5dd,
# 0x30: 0x5e4, 0x31: 0x5ef, 0x32: 0x5fb, 0x33: 0x606, 0x34: 0x60d, 0x35: 0x618, 0x36: 0x61f, 0x37: 0x618,
# 0x38: 0x626, 0x39: 0x631, 0x3a: 0x63b, 0x3b: 0x646, 0x3c: 0x651, 0x3d: 0x658, 0x3e: 0x663, 0x3f: 0x66e,
# 0x40: 0x67a, 0x41: 0x686, 0x42: 0x691, 0x43: 0x69d, 0x44: 0x6a4, 0x45: 0x6ab, 0x46: 0x6b6, 0x47: 0x6bd,
# 0x48: 0x6bd, 0x49: 0x6bd, 0x4a: 0x6c4, 0x4b: 0x6d0, 0x4c: 0x6da, 0x4d: 0x6e5, 0x4e: 0x6f0, 0x4f: 0x6fb,
# 0x50: 0x705, 0x51: 0x713, 0x52: 0x71e, 0x53: 0x72c, 0x54: 0x737, 0x55: 0x742, 0x56: 0x749, 0x57: 0x750,
# 0x58: 0x757, 0x59: 0x75e, 0x5a: 0x765, 0x5b: 0x76c, 0x5c: 0x773, 0x5d: 0x77e, 0x5e: 0x789, 0x5f: 0x794,
# 0x60: 0x7a0, 0x61: 0x7a7, 0x62: 0x7a0, 0x63: 0x7b2, 0x64: 0x7bd, 0x65: 0x7c8, 0x66: 0x7d2, 0x67: 0x7dd,
# 0x68: 0x7e4, 0x69: 0x7eb, 0x6a: 0x7eb, 0x6b: 0x7f7, 0x6c: 0x802, 0x6d: 0x80d, 0x6e: 0x814, 0x6f: 0x81f,
# 0x70: 0x81f, 0x71: 0x82b, 0x72: 0x836, 0x73: 0x841, 0x74: 0x848, 0x75: 0x84f, 0x76: 0x856, 0x77: 0x863,
# 0x78: 0x870, 0x79: 0x870, 0x7a: 0x870, 0x7b: 0x870, 0x7c: 0x87a, 0x7d: 0x881, 0x7e: 0x88b, 0x7f: 0x896,
# 0x80: 0x8a1, 0x81: 0x8ac, 0x82: 0x8ac, 0x83: 0x8b3, 0x84: 0x8ba, 0x85: 0x8c1, 0x86: 0x8c8, 0x87: 0x8c8,
# 0x88: 0x8d4, 0x89: 0x8d4, 0x8a: 0x8de, 0x8b: 0x8de, 0x8c: 0x8e5, 0x8d: 0x8f2, 0x8e: 0x8f9, 0x8f: 0x904,
# 0x90: 0x904, 0x91: 0x90b, 0x92: 0x916, 0x93: 0x91d, 0x94: 0x928, 0x95: 0x928, 0x96: 0x92f, 0x97: 0x93a,
# 0x98: 0x945, 0x99: 0x950, 0x9a: 0x95b, 0x9b: 0x95b, 0x9c: 0x965, 0x9d: 0x96c, 0x9e: 0x976, 0x9f: 0x981,
# 0xa0: 0x988, 0xa1: 0x993, 0xa2: 0x99a, 0xa3: 0x993, 0xa4: 0x9a5, 0xa5: 0x9ac, 0xa6: 0x9b7, 0xa7: 0x9c2,
# 0xa8: 0x9cc, 0xa9: 0x9d3, 0xaa: 0x9dd, 0xab: 0x9e4, 0xac: 0x9ef, 0xad: 0x9f6, 0xae: 0x9f6, 0xaf: 0xa01,
# 0xb0: 0xa08, 0xb1: 0xa14, 0xb2: 0xa1e, 0xb3: 0xa25, 0xb4: 0xa2c, 0xb5: 0xa37, 0xb6: 0xa42, 0xb7: 0x50a,
# 0xb8: 0xa4d, 0xb9: 0xa54, 0xba: 0xa5b, 0xbb: 0xa62, 0xbc: 0xa69, 0xbd: 0xa74, 0xbe: 0xa74, 0xbf: 0xa7f,
# 0xc0: 0xa86, 0xc1: 0xa92, 0xc2: 0xa99, 0xc3: 0xaa0, 0xc4: 0xaa7, 0xc5: 0xab2, 0xc6: 0x50a, 0xc7: 0xab9,
# 0xc8: 0xac0, 0xc9: 0xac7, 0xca: 0xace, 0xcb: 0xace, 0xcc: 0xace, 0xcd: 0xad5, 0xce: 0xad5, 0xcf: 0xadf,
# 0xd0: 0xadf, 0xd1: 0xaeb, 0xd2: 0xaf6, 0xd3: 0xb01, 0xd4: 0xb01, 0xd5: 0xab2, 0xd6: 0x50a, 0xd7: 0xb01,
# 0xd8: 0xb01, 0xd9: 0xb08, 0xda: 0xb0f, 0xdb: 0xace, 0xdc: 0xace, 0xdd: 0xb1a, 0xde: 0xb1a, 0xdf: 0xb21,
# 0xe0: 0xb2c, 0xe1: 0xb37, 0xe2: 0xb3e, 0xe3: 0xb45, 0xe4: 0xb4c, 0xe5: 0xb4c, 0xe6: 0xb53, 0xe7: 0xb53,
# 0xe8: 0xb5a, 0xe9: 0xb68, 0xea: 0xb68, 0xeb: 0xb73, 0xec: 0xb7e, 0xed: 0xb7e, 0xee: 0xb8a, 0xef: 0xb94,
# 0xf0: 0xb53, 0xf1: 0xb53, 0xf2: 0xba0, 0xf3: 0xba0, 0xf4: 0xba5, 0xf5: 0xba5, 0xf6: 0xbac, 0xf7: 0xbac,
# 0xf8: 0xbac, 0xf9: 0xbba, 0xfa: 0xbc1, 0xfb: 0xbcc, 0xfc: 0xbd7, 0xfd: 0xbd7, 0xfe: 0xbba, 0xff: 0xbe3
# }
# item name: (spriteID, pool count, replacement item)
bonk_prize_lookup = {
'Chicken': (0x0b, 0, None),
'Bee Trap': (0x79, 6, None),
'Apples': (0xac, 8, None),
'Good Bee': (0xb2, 1, None),
'Small Heart': (0xd8, 2, None),
'Rupee (1)': (0xd9, 0, None),
'Rupees (5)': (0xda, 3, None), # TODO: add in murahdahla tree rupee
'Rupees (20)': (0xdb, 3, None),
'Single Bomb': (0xdc, 2, None),
'Bombs (3)': (None, 0, 'Bombs (4)'),
'Bombs (4)': (0xdd, 0, 'Bombs (3)'),
'Bombs (8)': (0xde, 1, 'Bombs (10)'),
'Bombs (10)': (None, 0, 'Bombs (8)'),
'Small Magic': (0xdf, 0, None),
'Big Magic': (0xe0, 1, None),
'Arrows (5)': (0xe1, 0, None),
'Arrows (10)': (0xe2, 0, None),
'Fairy': (0xe3, 15, None)
}
# item name: (custom gfx, address offset, palette, palette address, size)
# decompressed gfx loaded at $7F8000
# custom gfx loaded at $228000
item_gfx_table = {
'Green Rupees (20)': (False, 0x0000, 0x04, 0x0000, 2),
'Pegasus Boots': (False, 0x0040, 0x01, 0x0000, 2),
'Psuedoboots': (False, 0x0040, 0x02, 0x0000, 2),
'Blue Pendant': (False, 0x0080, 0x02, 0x0000, 2),
'Red Pendant': (False, 0x0080, 0x01, 0x0000, 2),
'Warp Tile': (False, 0x00C0, 0x04, 0x0000, 2),
'Open Chest': (False, 0x0100, 0x02, 0x0000, 2),
'Chicken': (False, 0x0140, 0x04, 0x0000, 2),
'Duck': (False, 0x0180, 0x01, 0x0000, 2),
'Chest': (False, 0x0400, 0x02, 0x0000, 2),
'Frog': (False, 0x0440, 0x04, 0x0000, 2),
'Kiki (Head)': (False, 0x0480, 0x04, 0x0000, 2),
'Purple Chest': (False, 0x0500, 0x04, 0x0000, 2),
'Super Bomb': (False, 0x0540, 0x04, 0x0000, 2),
'Blacksmith': (False, 0x0580, 0x04, 0x0000, 2),
'Bug Net': (False, 0x0860, 0x01, 0x0000, 2),
'Crystal': (False, 0x08A0, 0x86, 0xB240, 2),
'Silver Arrows': (False, 0x08E0, 0x01, 0x0000, 2),
'Progressive Bow': (False, 0x0920, 0x02, 0x0000, 2),
'Bottle (Fairy)': (False, 0x0960, 0x02, 0x0000, 2),
'Bottle (Bee)': (False, 0x09A0, 0x02, 0x0000, 2),
'Piece of Heart': (False, 0x0C00, 0x01, 0x0000, 2),
'Ocarina': (False, 0x0C40, 0x02, 0x0000, 2),
'Mirror Shield': (False, 0x0C80, 0x80, 0xB230, 2),
'Rupees (100)': (False, 0x0D20, 0x04, 0x0000, 2),
'Rupees (50)': (False, 0x0D60, 0x04, 0x0000, 2),
'Rupees (300)': (False, 0x0DA0, 0x04, 0x0000, 2),
'Flippers': (False, 0x1000, 0x02, 0x0000, 2),
'Magic Mirror': (False, 0x1040, 0x02, 0x0000, 2),
'Bomb': (False, 0x1080, 0x02, 0x0000, 2),
'Lamp': (False, 0x10C0, 0x01, 0x0000, 2),
'Psuedolamp': (False, 0x10C0, 0x02, 0x0000, 2),
'Magic Cape': (False, 0x1100, 0x01, 0x0000, 2),
'Compass': (False, 0x1140, 0x02, 0x0000, 2),
'Moon Pearl': (False, 0x1180, 0x01, 0x0000, 2),
'Ether': (False, 0x1400, 0x04, 0x0000, 2),
'Bombos': (False, 0x1440, 0x04, 0x0000, 2),
'Quake': (False, 0x1480, 0x04, 0x0000, 2),
'Bottle': (False, 0x14C0, 0x01, 0x0000, 2),
'Bottle (Red Potion)': (False, 0x1500, 0x01, 0x0000, 2),
'Bottle (Green Potion)': (False, 0x1500, 0x04, 0x0000, 2),
'Bottle (Blue Potion)': (False, 0x1500, 0x02, 0x0000, 2),
'Mushroom': (False, 0x1540, 0x04, 0x0000, 2),
'Map': (False, 0x1580, 0x04, 0x0000, 2),
'Big Key': (False, 0x15C0, 0x04, 0x0000, 2),
'Green Potion': (False, 0x1800, 0x04, 0x0000, 2),
'Blue Potion': (False, 0x1800, 0x02, 0x0000, 2),
'Red Potion': (False, 0x1800, 0x01, 0x0000, 2),
'Bombs (3)': (False, 0x1840, 0x02, 0x0000, 2),
'Arrows (10)': (False, 0x1880, 0x02, 0x0000, 2),
'Heart Container': (False, 0x18C0, 0x01, 0x0000, 2),
'Heart Container (Green)': (False, 0x18C0, 0x04, 0x0000, 2),
'Heart Container (Blue)': (False, 0x18C0, 0x02, 0x0000, 2),
'Green Mail': (False, 0x1900, 0x04, 0x0000, 2),
'Blue Mail': (False, 0x1900, 0x02, 0x0000, 2),
'Red Mail': (False, 0x1900, 0x01, 0x0000, 2),
'Fire Shield': (False, 0x1940, 0x80, 0xB220, 2),
'Blue Shield': (False, 0x19C0, 0x02, 0x0000, 2),
'Magic Powder': (False, 0x1CC0, 0x02, 0x0000, 2),
'Bombs (10)': (False, 0x1D00, 0x02, 0x0000, 2),
'Power Glove': (False, 0x1D40, 0x01, 0x0000, 2),
'Titans Mitt': (False, 0x1D40, 0x04, 0x0000, 2),
'Book of Mudora': (False, 0x1D80, 0x04, 0x0000, 2),
'Maiden (Head)': (False, 0x2000, 0x04, 0x0000, 2),
'Zelda (Head)': (False, 0x2080, 0x04, 0x0000, 2),
'Old Man (Head)': (False, 0x2140, 0x04, 0x0000, 2),
'Locksmith (Head)': (False, 0x2180, 0x04, 0x0000, 2),
'Fire': (False, 0x25C0, 0x04, 0x0000, 2),
'Apples': (False, 0x30A0, 0x04, 0x0000, 2),
'Fairy': (False, 0x3140, 0x01, 0x0000, 2),
'Whirlpool': (False, 0x31C0, 0x02, 0x0000, 2),
'Nothing': (True, 0x0020, 0x01, 0x0000, 2),
'Triforce': (True, 0x0060, 0x04, 0x0000, 2),
'Fighter Sword': (True, 0x00A0, 0x02, 0x0000, 2),
'Master Sword': (True, 0x00E0, 0x02, 0x0000, 2),
'Tempered Sword': (True, 0x0120, 0x01, 0x0000, 2),
'Golden Sword': (True, 0x0160, 0x04, 0x0000, 2),
'Half Magic': (True, 0x01A0, 0x04, 0x0000, 2),
'Quarter Magic': (True, 0x01E0, 0x04, 0x0000, 2),
'Bomb Upgrade (+5)': (True, 0x0420, 0x04, 0x0000, 2),
'Bomb Upgrade (+10)': (True, 0x0460, 0x04, 0x0000, 2),
'Bomb Upgrade (50)': (True, 0x04A0, 0x04, 0x0000, 2),
'Bombbag': (True, 0x04E0, 0x02, 0x0000, 2),
'Arrow Upgrade (+5)': (True, 0x0520, 0x02, 0x0000, 2),
'Arrow Upgrade (+10)': (True, 0x0560, 0x02, 0x0000, 2),
'Arrow Upgrade (70)': (True, 0x05A0, 0x02, 0x0000, 2),
'Silver Arrows (Ag)': (True, 0x05E0, 0x01, 0x0000, 2),
'Green Pendant': (True, 0x0820, 0x04, 0x0000, 2),
'Sword and Shield': (True, 0x0860, 0x02, 0xD244, 2),
'Green Potion (G)': (True, 0x08A0, 0x04, 0x0000, 2),
'Blue Potion (B)': (True, 0x08E0, 0x02, 0x0000, 2),
'Red Potion (R)': (True, 0x0920, 0x01, 0x0000, 2),
'Bee Trap': (True, 0x0960, 0x02, 0x0000, 2),
'Red Crystal': (True, 0x0C60, 0x01, 0x0000, 2),
'Green Clock': (True, 0x0DE0, 0x04, 0x0000, 2),
'Blue Clock': (True, 0x0DE0, 0x02, 0x0000, 2),
'Red Clock': (True, 0x0DE0, 0x01, 0x0000, 2),
'Egg': (True, 0x1020, 0x02, 0x0000, 2),
'Master Key': (True, 0x1060, 0x02, 0x0000, 2),
'Lumberjack (Head)': (True, 0x11A0, 0x02, 0x0000, 2),
'Power Star': (True, 0x11E0, 0x04, 0x0000, 2),
# Thin GFX
'Rupee (1)': (False, 0x0800, 0x04, 0x0000, 0),
'Rupees (5)': (False, 0x0800, 0x02, 0x0000, 0),
'Rupees (20)': (False, 0x0800, 0x01, 0x0000, 0),
'Blue Shield (Thin)': (False, 0x09E0, 0x02, 0x0000, 0),
'Heart': (False, 0x0CC0, 0x01, 0x0000, 0),
'Heart (Green)': (False, 0x0CC0, 0x04, 0x0000, 0),
'Heart (Blue)': (False, 0x0CC0, 0x02, 0x0000, 0),
'Small Magic': (False, 0x0CE0, 0x04, 0x0000, 0),
'Arrow': (False, 0x0D00, 0x02, 0x0000, 0),
'Nothing (Thin)': (False, 0x0DE0, 0x01, 0x0000, 0),
'Master Sword (Thin)': (False, 0x11C0, 0x05, 0x0000, 0),
'Tempered Sword (Thin)': (False, 0x11C0, 0x01, 0x0000, 0),
'Golden Sword (Thin)': (False, 0x11C0, 0x04, 0x0000, 0),
'Shovel': (False, 0x11E0, 0x01, 0x0000, 0),
'Bow': (False, 0x1C00, 0x01, 0x0000, 0),
'Fighter Sword (Thin)': (False, 0x1C20, 0x02, 0x0000, 0),
'Somaria': (False, 0x1C40, 0x01, 0x0000, 0),
'Byrna': (False, 0x1C40, 0x02, 0x0000, 0),
'Hookshot': (False, 0x1C60, 0x01, 0x0000, 0),
'Fire Rod': (False, 0x1C80, 0x01, 0x0000, 0),
'Ice Rod': (False, 0x1C80, 0x02, 0x0000, 0),
'Hammer': (False, 0x1CA0, 0x01, 0x0000, 0),
'Small Key': (False, 0x1DC0, 0x04, 0x0000, 0),
'Boomerang': (False, 0x1DE0, 0x02, 0x0000, 0),
'Red Boomerang': (False, 0x1DE0, 0x01, 0x0000, 0),
'Full Magic': (True, 0x01A0, 0x04, 0x0000, 0),
}
-142
View File
@@ -1,142 +0,0 @@
import argparse
import concurrent.futures
import multiprocessing
import subprocess
import sys
from collections import OrderedDict
cpu_threads = multiprocessing.cpu_count()
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
def main(args=None):
successes = []
errors = []
task_mapping = []
tests = OrderedDict()
successes.append(f"Testing {args.dr} DR with {args.count} Tests" + (f" (intensity={args.tense})" if args.dr in ['basic', 'crossed'] else ""))
print(successes[0])
max_attempts = args.count
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
dead_or_alive = 0
alive = 0
def test(testname: str, command: str):
tests[testname] = [command]
for mode in [['Open', ''],
['Std ', ' --mode standard'],
['Inv ', ' --mode inverted']]:
basecommand = f"python DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --spoiler none"
def gen_seed():
taskcommand = basecommand + " " + command + mode[1]
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
for x in range(1, max_attempts + 1):
task = pool.submit(gen_seed)
task.success = False
task.name = testname
task.mode = mode[0]
task.cmd = basecommand + " " + command + mode[1]
task_mapping.append(task)
test("Vanilla ", "--shuffle vanilla")
test("Retro ", "--retro --shuffle vanilla")
test("Keysanity ", "--shuffle vanilla --keydropshuffle --keysanity")
test("Shopsanity", "--shuffle vanilla --shopsanity")
test("Simple ", "--shuffle simple")
test("Full ", "--shuffle full")
test("Lite ", "--shuffle lite")
test("Lean ", "--shuffle lean")
test("District ", "--shuffle district")
test("Swapped ", "--shuffle swapped")
test("Crossed ", "--shuffle crossed")
test("Insanity ", "--shuffle insanity")
test("OWG ", "--logic owglitches")
test("OW Full ", "--ow_shuffle full")
from tqdm import tqdm
with tqdm(concurrent.futures.as_completed(task_mapping),
total=len(task_mapping), unit="seed(s)",
desc=f"Success rate: 0.00%") as progressbar:
for task in progressbar:
dead_or_alive += 1
try:
result = task.result()
if result.returncode:
errors.append([task.name + task.mode, task.cmd, result.stderr])
else:
alive += 1
task.success = True
except Exception as e:
raise e
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}{task.mode}")
def get_results(testname: str):
result = ""
for mode in ['Open', 'Std ', 'Inv ']:
dead_or_alive = [task.success for task in task_mapping if task.name == testname and task.mode == mode]
alive = [x for x in dead_or_alive if x]
success = f"{testname}{mode} Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
successes.append(success)
print(success)
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
return result.strip()
results = []
for t in tests.keys():
results.append(get_results(t))
for result in results:
print(result)
successes.append(result)
return successes, errors
if __name__ == "__main__":
successes = []
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0))
parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1))
parser.add_argument('--help', default=False, action='store_true')
args = parser.parse_args()
if args.help:
parser.print_help()
exit(0)
cpu_threads = args.cpu_threads
for dr in [['vanilla', args.count if args.count else 2, 1],
['basic', args.count if args.count else 5, 3],
['crossed', args.count if args.count else 10, 3]]:
for tense in range(1, dr[2] + 1):
args = argparse.Namespace()
args.dr = dr[0]
args.tense = tense
args.count = dr[1]
s, errors = main(args=args)
if successes:
successes += [""] * 2
successes += s
print()
if errors:
with open(f"{dr[0]}{(f'-{tense}' if dr[0] in ['basic', 'crossed'] else '')}-errors.txt", 'w') as stream:
for error in errors:
stream.write(error[0] + "\n")
stream.write(error[1] + "\n")
stream.write(error[2] + "\n\n")
with open("success.txt", "w") as stream:
stream.write(str.join("\n", successes))
input("Press enter to continue")
-144
View File
@@ -1,144 +0,0 @@
import subprocess
import sys
import multiprocessing
import concurrent.futures
import argparse
import sqlite3
from collections import OrderedDict
from datetime import datetime
cpu_threads = multiprocessing.cpu_count()
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
def main(args=None):
successes = []
errors = []
task_mapping = []
tests = OrderedDict()
max_attempts = 1 #args and args.count if args.count is not None else 1
print(f"Testing Random OWR with {max_attempts} Tests")
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
dead_or_alive = 0
alive = 0
basecommand = f'py Mystery.py --suppress_rom --suppress_meta --spoiler full --outputpath L:/_Work/Zelda/ROMs/Bug/Automate/{datetime.now().strftime("%y%m%d")} --weights L:/_Work/Zelda/ROMs/Bug/Automate/_test.yml'
def gen_seed():
return subprocess.run(basecommand, capture_output=True, shell=True, text=True)
for x in range(1, max_attempts + 1):
task = pool.submit(gen_seed)
task.success = False
task.name = "OWR Random Test"
task.mode = ""
task.cmd = basecommand
task_mapping.append(task)
from tqdm import tqdm
with tqdm(concurrent.futures.as_completed(task_mapping),
total=len(task_mapping), unit="seed(s)",
desc=f"Success rate: 0.00%") as progressbar:
#with open("L:/_Work/Zelda/ROMs/Bug/Automate/_error_.txt", "a") as ff:
# ff.write("seed")
for task in progressbar:
dead_or_alive += 1
try:
result = task.result()
if result.returncode:
errors.append([datetime.now(), result.stderr])
#print(result.stderr)
#print(result.stdout)
else:
alive += 1
successes.append([datetime.now(), result.stderr])
task.success = True
except Exception as e:
with open("L:/_Work/Zelda/ROMs/Bug/Automate" + datetime.now().strftime("%y%m%d") + "/_error_.txt", "a") as ff:
exc_type, exc_obj, exc_tb = sys.exc_info()
ff.write(exc_type)
ff.write(exc_tb.tb_lineno)
raise e
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}{task.mode}")
def get_results(testname: str):
result = ""
dead_or_alive = [task.success for task in task_mapping]
alive = [x for x in dead_or_alive if x]
success = f"Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
#successes.append(success)
#print(success)
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
return result.strip()
results = []
results.append(get_results(""))
#for result in results:
#print(result)
#successes.append(result)
return successes, errors
if __name__ == "__main__":
successes = []
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--count', default=1, type=lambda value: max(int(value), 1))
parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1))
parser.add_argument('--help', default=False, action='store_true')
args = parser.parse_args()
if args.help:
parser.print_help()
exit(0)
cpu_threads = args.cpu_threads
args = argparse.Namespace()
successes, errors = main(args=args)
# if successes:
# successes += [""] * 2
# successes += s
# print()
if errors:
with open(f"L:/_Work/Zelda/ROMs/Bug/Automate/{datetime.now().strftime('%y%m%d')}/_error_{datetime.now().strftime('%y%m%d')}.txt", 'a') as stream:
for error in errors:
stream.write("Run at: " + error[0].strftime("%Y-%m-%d %H:%M:%S") + "\n")
stream.write(error[1] + "\n\n")
stream.write("----------------------------------------------------\n")
if successes:
with open(f"L:/_Work/Zelda/ROMs/Bug/Automate/{datetime.now().strftime('%y%m%d')}/_success_{datetime.now().strftime('%y%m%d')}.txt", 'a') as stream:
for success in successes:
stream.write("Run at: " + success[0].strftime("%Y-%m-%d %H:%M:%S") + "\n")
stream.write(success[1] + "\n\n")
stream.write("----------------------------------------------------\n")
# with open(f"L:\\_Work\\Zelda\\ROMs\\Bug\\Automate\\{datetime.now().strftime('%y%m%d')}\\_success.txt", "w") as stream:
# stream.write(str.join("\n", successes))
#conn = sqlite3.connect(f"L:/_Work/Zelda/ROMs/Bug/Automate/{datetime.now().strftime('%y%m%d')}/log.db")
# conn.execute('''CREATE TABLE SEEDGEN
# (SEED INT NOT NULL,
# MYSTERY CHAR(10),
# SETTINGSCODE CHAR(20) NOT NULL,
# VERSION CHAR(10) NOT NULL,
# TIMESTAMP INT NOT NULL,
# SUCCESS INT,
# LOG TEXT);''')
#input("Press enter to continue")
#conn.close()
exit(0)
-330
View File
@@ -1,330 +0,0 @@
import argparse
import concurrent.futures
import csv
import multiprocessing
import subprocess
import sys
from collections import OrderedDict
cpu_threads = multiprocessing.cpu_count()
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
ALL_SETTINGS = {
'mode': ['open', 'standard', 'inverted'],
'goal': ['ganon', 'pedestal', 'triforcehunt', 'trinity', 'crystals', 'dungeons'],
'swords': ['random', 'swordless', 'assured'],
'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','district','swapped','crossed','insanity'],
'shufflelinks': [True, False],
'shuffleganon': [True, False],
'door_shuffle': ['vanilla', 'basic', 'crossed'],
'intensity': ['1','2','3'],
'ow_shuffle': ['vanilla', 'parallel', 'full'],
'ow_fluteshuffle': ['vanilla', 'balanced', 'random'],
'ow_keepsimilar': [True, False],
'ow_mixed': [True, False],
'ow_crossed': [True, False],
'accessibility': [True, False],
'difficulty': [True, False],
'shufflepots': [True, False],
'keydropshuffle': [True, False],
'keysanity': [True, False],
'retro': [True, False],
'bombbag': [True, False],
'shopsanity': [True, False]
}
SETTINGS = {
'mode': ['standard', 'open', 'inverted'],
'goal': ['ganon'],
'swords': ['random'],
'shuffle': ['vanilla',
'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity'
],
'shufflelinks': [True, False],
'shuffleganon': [True, False],
'door_shuffle': ['vanilla', 'crossed'],
'intensity': ['2'],
'ow_shuffle': ['vanilla', 'parallel', 'full'],
'ow_fluteshuffle': ['balanced'],
'ow_keepsimilar': [True, False],
'ow_mixed': [True, False],
'ow_crossed': ['none', 'polar', 'grouped', 'unrestricted'],
'accessibility': [True],
'difficulty': [False],
'shufflepots': [False],
'keydropshuffle': [False],
'keysanity': [False],
'retro': [True, False],
'bombbag': [False],
'shopsanity': [True, False]
}
optionsList = []
for sett,options in SETTINGS.items():
for option in options:
if isinstance(option, str):
optionsList.append(f'{option}')
else:
optionsList.append('{}-{}'.format(sett,str(option)))
headerList = list(SETTINGS.keys())
def main(args=None):
successes = []
errors = []
task_mapping = []
tests = OrderedDict()
successes.append(f"Testing {args.ow} with {args.count} Tests" + (f" (intensity={args.tense})" if args.ow in ['basic', 'crossed'] else ""))
print(successes[0])
max_attempts = args.count
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
dead_or_alive = 0
alive = 0
def test(testname: list, command: str):
tests[' '.join(testname)] = [command]
basecommand = f"py DungeonRandomizer.py --shuffle {args.ow} --suppress_rom --suppress_spoiler"
def gen_seed():
taskcommand = basecommand + ' ' + command
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
for _ in range(1, max_attempts + 1):
task = pool.submit(gen_seed)
task.success = False
task.name = ' '.join(testname)
task.settings = testname
task.cmd = basecommand + ' ' + command
task_mapping.append(task)
for mode in SETTINGS['mode']:
for goal in SETTINGS['goal']:
for swords in SETTINGS['swords']:
#for shuffle in SETTINGS['shuffle']:
for ow_shuffle in SETTINGS['ow_shuffle']:
for shufflelinks in SETTINGS['shufflelinks']:
for shuffleganon in SETTINGS['shuffleganon']:
for door_shuffle in SETTINGS['door_shuffle']:
for intensity in SETTINGS['intensity']:
for ow_fluteshuffle in SETTINGS['ow_fluteshuffle']:
for ow_keepsimilar in SETTINGS['ow_keepsimilar']:
for ow_mixed in SETTINGS['ow_mixed']:
for ow_crossed in SETTINGS['ow_crossed']:
for difficulty in SETTINGS['difficulty']:
for shufflepots in SETTINGS['shufflepots']:
for accessibility in SETTINGS['accessibility']:
for keydropshuffle in SETTINGS['keydropshuffle']:
for keysanity in SETTINGS['keysanity']:
for retro in SETTINGS['retro']:
for bombbag in SETTINGS['bombbag']:
for shopsanity in SETTINGS['shopsanity']:
commands = ''
name = []
commands = commands + f' --mode {mode}'
name.append(mode)
commands = commands + f' --goal {goal}'
name.append(goal)
commands = commands + f' --swords {swords}'
name.append(swords)
# if shuffle != 'vanilla': #since this is the output is grouped on this, we only want one of these in the loop
# continue
# commands = commands + f' --shuffle {shuffle}'
# name.append(f'ER{shuffle}')
commands = commands + f' --ow_shuffle {ow_shuffle}'
name.append(ow_shuffle)
if shufflelinks:
commands = commands + f' --shufflelinks'
name.append('shufflelinks-True')
else:
name.append('shufflelinks-False')
if shuffleganon:
commands = commands + f' --shuffleganon'
name.append('shuffleganon-True')
else:
name.append('shuffleganon-False')
commands = commands + f' --door_shuffle {door_shuffle}'
name.append(door_shuffle)
if intensity == '3' and door_shuffle == 'vanilla':
continue
commands = commands + f' --intensity {intensity}'
name.append(intensity)
commands = commands + f' --ow_fluteshuffle {ow_fluteshuffle}'
name.append(ow_fluteshuffle)
if difficulty:
commands = commands + f' --difficulty expert'
commands = commands + f' --item_functionality expert'
name.append('difficulty-True')
else:
name.append('difficulty-False')
if ow_keepsimilar:
commands = commands + f' --ow_keepsimilar'
name.append('ow_keepsimilar-True')
else:
if ow_crossed in ['none', 'polar', 'grouped']:
continue
name.append('ow_keepsimilar-False')
if ow_mixed:
commands = commands + f' --ow_mixed'
name.append('ow_mixed-True')
else:
if ow_crossed == 'polar':
continue
name.append('ow_mixed-False')
commands = commands + f' --ow_crossed {ow_crossed}'
name.append(ow_crossed)
if shufflepots:
commands = commands + f' --shufflepots'
name.append('shufflepots-True')
else:
name.append('shufflepots-False')
if not accessibility:
commands = commands + f' --accessibility none'
name.append('accessibility-False')
else:
name.append('accessibility-True')
if keydropshuffle:
commands = commands + f' --keydropshuffle'
name.append('keydropshuffle-True')
else:
name.append('keydropshuffle-False')
if keysanity:
commands = commands + f' --keysanity'
name.append('keysanity-True')
else:
name.append('keysanity-False')
if retro:
commands = commands + f' --retro'
name.append('retro-True')
else:
name.append('retro-False')
if bombbag:
commands = commands + f' --bombbag'
name.append('bombbag-True')
else:
name.append('bombbag-False')
if shopsanity:
commands = commands + f' --shopsanity'
name.append('shopsanity-True')
else:
name.append('shopsanity-False')
test(name, commands)
# test("Vanilla ", "--futuro --shuffle vanilla")
# test("Basic ", "--futuro --retro --shuffle vanilla")
# test("Keysanity ", "--futuro --shuffle vanilla --keydropshuffle --keysanity")
# test("Simple ", "--futuro --shuffle simple")
# test("Crossed ", "--futuro --shuffle crossed")
# test("Insanity ", "--futuro --shuffle insanity")
# test("CrossKeys ", "--futuro --shuffle crossed --keydropshuffle --keysanity")
from tqdm import tqdm
with tqdm(concurrent.futures.as_completed(task_mapping),
total=len(task_mapping), unit="seed(s)",
desc=f"Success rate: 0.00%") as progressbar:
for task in progressbar:
dead_or_alive += 1
try:
result = task.result()
if result.returncode:
errors.append([task.name, task.cmd, result.stderr])
else:
alive += 1
task.success = True
except Exception as e:
raise e
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}")
def get_results(option: str):
result = ""
dead_or_alive = [task.success for task in task_mapping if option in task.settings]
if len(dead_or_alive):
alive = [x for x in dead_or_alive if x]
success = f"{option} Rate: {(len(alive) / len(dead_or_alive)) * 100:.1f}%"
successes.append(success)
print(success)
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
else:
success = f"{option} Rate: NULL%"
print(success)
result += f"NULL%\t"
return result.strip()
results = []
for option in optionsList:
results.append(get_results(option))
for result in results:
successes.append(result)
tabresultsfile = './output/' + args.ow + '.tsv'
with open(tabresultsfile, 'w+', newline='') as f:
writer = csv.writer(f, delimiter='\t')
header = headerList.copy()
header.append('Success')
writer.writerow(header)
for task in task_mapping:
settings = []
for option in headerList:
if option in task.settings:
settings.append(1)
elif str(option + '-True') in task.settings:
settings.append(1)
else:
settings.append(0)
if task.success:
settings.append(1)
else:
settings.append(0)
writer.writerow(settings)
return successes, errors
if __name__ == "__main__":
successes = []
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0))
parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1))
parser.add_argument('--help', default=False, action='store_true')
args = parser.parse_args()
if args.help:
parser.print_help()
exit(0)
cpu_threads = args.cpu_threads
# for ow in [['full', args.count if args.count else 2, 1]]:
# for ow in [['vanilla', args.count if args.count else 2, 1],
# ['parallel', args.count if args.count else 5, 1],
# ['full', args.count if args.count else 10, 1]]:
count = args.count if args.count else 1
for ow in SETTINGS['shuffle']:
for tense in range(1, 2): #ow[2] + 1): #unnecessary when DR is not the root setting
args = argparse.Namespace()
args.ow = ow #ow[0]
args.tense = tense
args.count = count #ow[1]
s, errors = main(args=args)
if successes:
successes += [""] * 2
successes += s
print()
if errors:
with open(f"./output/{ow[0]}{(f'-{tense}' if ow in ['parallel', 'full'] else '')}-errors.txt", 'w+') as stream:
for error in errors:
stream.write(error[0] + "\n")
stream.write(error[1] + "\n")
stream.write(error[2] + "\n\n")
with open("./output/success.txt", "w+") as stream:
stream.write(str.join("\n", successes))
input("Press enter to continue")
+64 -84
View File
@@ -1,10 +1,7 @@
# -*- coding: UTF-8 -*-
from collections import OrderedDict
import logging
import re
import warnings
from collections import OrderedDict
warnings.filterwarnings("ignore", category=SyntaxWarning)
text_addresses = {'Pedestal': (0x180300, 256),
'Triforce': (0x180400, 256),
@@ -99,9 +96,9 @@ Triforce_texts = [
"\n G G",
" All your base\n are belong\n to us.",
" You have ended\n the domination\n of Dr. Wily",
" Thanks for\n playing!!!",
" Thanks for\n playing!!!",
"\n You Win!",
" Thank you!\n Your quest\n is over.",
" Thank you!\n Your quest\n is over.",
" A winner\n is you!",
"\n WINNER!!",
"\n I'm sorry\n\nbut our princess is\n in another castle",
@@ -109,18 +106,18 @@ Triforce_texts = [
" Whelp…\n that just\n happened",
" Oh hey…\n it's you",
"\n Wheeeeee!!",
" Time for\n another one?",
" Time for\n another one?",
" And\n\n scene",
"\n GOT EM!!",
"\n THE VALUUUE!!!",
" Cool seed,\n\n right?",
"\n We did it!",
" Spam those\n emotes in\n wilds chat",
"\n O M G",
"\n O M G",
" Hello. Will you\n you be my friend?",
" Beetorp\n was\n here!",
" The Wind Fish\n will wake soon.\n Hoot!",
" Meow Meow Meow\n Meow Meow Meow\n Oh My God!",
" Meow Meow Meow\n Meow Meow Meow\n Oh my god!",
" Ahhhhhhhhh\n Ya ya yaaaah\n Ya ya yaaah",
" .done\n\n .comment lol",
" You get to\n drink from\n the firehose",
@@ -128,14 +125,13 @@ Triforce_texts = [
" You get one\n wish. Choose\n wisely, hero!",
" Can you please\n break us three\n up? Thanks.",
" Pick us up\n before we\n get dizzy!",
" Thank you,\n Mikey. You're\n 2 minutes late",
" Thank you,\n Mikey. Youre\n 2 minutes late",
" This was a\n 7000 series\n train.",
" I'd buy\n that for\n a rupee!",
" Did you like\n that bow\n placement?",
" I promise the\n next seed will\n be better.",
"\n Honk.",
" Breakfast\n is served!",
"\n send help",
]
BombShop2_texts = ['Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!']
Sahasrahla2_texts = ['You already got my item, idiot.', 'Why are you still talking to me?', 'This text won\'t change.', 'Have you met my brother, Hasarahshla?']
@@ -194,7 +190,7 @@ Blind_texts = [
"Gloves open\na handful\nof checks",
"Red mail?\nReturn to\nsender.",
"For sale:\nBaby boots,\nNever found",
"SRL or rtGG?\nI prefer the\nStepladder",
"SRL or rtGG?\nI prefer the\nLadder",
"Ladders are\nalways up\nto something",
"Zelda's\nfashion is\nvery chic",
"Zombie geese\nare waterfoul.\n",
@@ -236,37 +232,30 @@ Ganon_Phase_3_No_Silvers_texts = [
"Did you find\nthe arrows on\nThe Moon?",
"Did you find\nthe arrows\nIn dev null?",
"I have sold\nthe arrows for\na green big 20",
"Did you find\nthe arrows in\nCount Dracula?",
"Did you find\nThe arrows in\nCount Dracula?",
"Error 404\nSilver arrows\nnot found.",
"No arrows for\nyou today.\nSorry.",
"No arrows for\nYou today,\nSorry",
"No arrows?\nCheck your\njunk mail."
"Careful, all\nthat spinning\nmakes me dizzy",
"Did you find\nthe arrows in\nJabu's belly?",
"Silver is not\nan appropriate\narrow material",
"Did you find\nthe arrows in\nNarnia?",
"Are you ready\nTo spin\nTo win?",
"DID YOU FIND\nTHE ARROWS IN\nKEFKA'S TOWER",
"Did you find\nthe arrows in\nyour Recycle Bin?",
"Did you find\nthe arrows in\nRecycle Bin?",
"Silver Arrows?\n\nLUL",
"Imagine\nfinding the\narrows",
"Did you find\nsilvers in\nscenic Ohio?",
"Did you find\nthe arrows in\n*mumblemumble*",
"Did you find\nthe arrows in\nthe hourglass?",
"Did you find\nThe arrows in\n*mumblemumble*",
"\nSpin To Win!\n",
"did you find\nthe arrows in\nthe hourglass?",
"Silver Arrows\nare so v30",
"OH, NO, THEY\nACTUALLY SAID\nSILVER MARROW",
"SURELY THE\nLEFTMOST TILES\nWILL STAY UP",
"Did you find\nthe arrows in\nWorld 4-2?",
"SILLY HERO,\nSILVER IS FOR\nWEREWOLVES!",
"Did you find\nthe silvers in\nGanti's ears",
]
Ganon_Phase_3_No_Weakness_texts = [
"Error 404\nWeakness\nnot found.",
"No weakness\nfor you today.\nSorry",
"Careful, all\nthat spinning\nmakes me dizzy",
"Are you ready\nTo spin\nTo win?",
"\nSpin To Win!\n",
"SURELY THE\nLEFTMOST TILES\nWILL STAY UP",
"You Spin Me\nRight Round\nLike A Record",
"SILLY HERO,\nSILVER IS FOR\nWEREWOLVES!",
"Did you find\nthe silvers in\nganti's ears",
]
TavernMan_texts = [
@@ -350,8 +339,6 @@ junk_texts = [
KingsReturn_texts = [
'Who is this even',
'When did he get here',
'The Harem'
] * 2 + [
"the return of the king",
"fellowship of the ring",
@@ -389,10 +376,11 @@ DeathMountain_texts = [
]
LostWoods_texts = [
'thieves\' stump',
'He\'s got wood',
] * 2 + [
"the forest thief",
"dancing pickles",
"flying crows",
"flying vultures",
]
WishingWell_texts = [
"venus. queen of faeries",
@@ -404,7 +392,7 @@ WishingWell_texts = [
"Venus Love Chain",
"Venus Crescent Beam",
]
DesertPalace_texts = ['vultures rule the desert', 'literacy that moves']
DesertPalace_texts = ['vultures rule the desert', 'literacy moves']
MountainTower_texts = ['the bully makes a friend', 'up up and away']
LinksHouse_texts = ['your uncle recovers', 'Home Sweet Home', 'Only one bed']
Lumberjacks_texts = [
@@ -649,7 +637,7 @@ class MultiByteCoreTextMapper(object):
outbuf = bytearray()
lineindex = 0
is_intro = '{INTRO}' in text
first_line = True
first_line=True
while lines:
linespace = wrap
@@ -680,19 +668,20 @@ class MultiByteCoreTextMapper(object):
outbuf.extend(cls.special_commands[start_command])
word = word.replace(start_command, '')
match = re.search(r'(\{[A-Z0-9_:]+\})$', word)
match = re.search(r'(\{[A-Z0-9_:]+\})\.?$', word)
if match:
end_command = match.group(1)
word = word.replace(end_command, '')
period = word.endswith('.')
else:
end_command = None
end_command, period = None, False
# sanity check: if the word we have is more than 19 characters,
# we take as much as we can still fit and push the rest back for later
if cls.wordlen(word) > wrap:
(word_first, word_rest) = cls.splitword(word, linespace)
if end_command:
word_rest = word_rest + end_command
word_rest = (word_rest[:-1] + end_command + '.') if period else (word_rest + end_command)
words.insert(0, word_rest)
lines.insert(0, ' '.join(words))
@@ -705,13 +694,16 @@ class MultiByteCoreTextMapper(object):
if cls.wordlen(word) < linespace:
pending_space = True
linespace -= cls.wordlen(word) + 1 if pending_space else 0
outbuf.extend(RawMBTextMapper.convert(word))
word_to_map = word[:-1] if period else word
outbuf.extend(RawMBTextMapper.convert(word_to_map))
if end_command:
outbuf.extend(cls.special_commands[end_command])
if period:
outbuf.extend(RawMBTextMapper.convert('.'))
else:
# ran out of space, push word and lines back and continue with next line
if end_command:
word = word + end_command
word = (word[:-1] + end_command + '.') if period else (word + end_command)
words.insert(0, word)
lines.insert(0, ' '.join(words))
break
@@ -848,9 +840,9 @@ class RawMBTextMapper(CharTextMapper):
'': 0xCE,
':': 0xEA,
'@': [0x6A], # Links name (only works if compressed)
'>': [0x00, 0x9B, 0x00, 0x9C], # Link's face
'>': [0x00, 0x9B, 0x00, 0x9C], # Link's face
"'": 0x9D,
'': 0xD8,
'': 0x9D,
'¼': [0x00, 0xE5, 0x00, 0xE7], # ¼ heart
'½': [0x00, 0xE6, 0x00, 0xE7], # ½ heart
'¾': [0x00, 0xE8, 0x00, 0xE9], # ¾ heart
@@ -1316,7 +1308,6 @@ class GoldCreditMapper(CharTextMapper):
class GreenCreditMapper(CharTextMapper):
char_map = {' ': 0x9F,
'.': 0x52,
'·': 0x52}
alpha_offset = -0x29
alpha_lower_offset = -0x29
@@ -1442,7 +1433,6 @@ class TextTable(object):
'item_get_pendant_power',
'item_get_pendant_wisdom',
'item_get_mushroom',
'item_get_book',
'item_get_moonpearl',
'item_get_compass',
'item_get_map', #60
@@ -1459,7 +1449,6 @@ class TextTable(object):
'item_get_bottle',
'item_get_big_key',
'item_get_titans_mitts',
'item_get_magic_mirror',
'item_get_fake_mastersword',
'post_item_get_mastersword',
'item_get_red_potion',
@@ -1658,7 +1647,7 @@ class TextTable(object):
text['sign_east_of_desert'] = CompressedTextMapper.convert("← Desert\n\n It's hot.")
text['sign_east_of_sanctuary'] = CompressedTextMapper.convert("↑→ Potions!\n\nWish waterfall")
text['sign_east_of_castle'] = CompressedTextMapper.convert("→ East Palace\n\n← Castle")
text['sign_north_of_lake'] = CompressedTextMapper.convert("\n Lake Hiriah")
text['sign_north_of_lake'] = CompressedTextMapper.convert("\n Lake Hylia")
text['sign_desert_thief'] = CompressedTextMapper.convert("Don't talk to me or touch my sign!")
text['sign_lumberjacks_house'] = CompressedTextMapper.convert("Lumberjacks, Inc.\nYou see 'em, we saw 'em.")
text['sign_north_kakariko'] = CompressedTextMapper.convert("↓ Kakariko\n Village")
@@ -1682,7 +1671,7 @@ class TextTable(object):
text['item_get_pendant_power'] = CompressedTextMapper.convert("We have the Pendant of Power! How robust!")
text['item_get_pendant_wisdom'] = CompressedTextMapper.convert("We have the Pendant of Wisdom! How astute!")
text['item_get_mushroom'] = CompressedTextMapper.convert("Mushroom! Don't eat it. Find a witch.")
text['item_get_book'] = CompressedTextMapper.convert("Book! Are you well read?")
text['item_get_book'] = CompressedTextMapper.convert("This book has a new feature--it can change the state of colored pegs!")
text['item_get_moonpearl'] = CompressedTextMapper.convert("Moon Pearl! Rabbit Be Gone!")
text['item_get_compass'] = CompressedTextMapper.convert("A compass! I can now find the boss.")
# 60
@@ -1700,7 +1689,7 @@ class TextTable(object):
text['item_get_bottle'] = CompressedTextMapper.convert("Bottle! Store all manner of things")
text['item_get_big_key'] = CompressedTextMapper.convert("Yo! You got a Big Key!")
text['item_get_titans_mitts'] = CompressedTextMapper.convert("Mitts! Lift ALL the rocks!")
text['item_get_magic_mirror'] = CompressedTextMapper.convert("Mirror! Take some time to reflect on this moment!")
text['item_get_magic_mirror'] = CompressedTextMapper.convert("Your Mirror has been upgraded and now works in both worlds!")
text['item_get_fake_mastersword'] = CompressedTextMapper.convert("It's the Master Sword! …or not…\n\n FOOL!")
# 70
text['post_item_get_mastersword'] = CompressedTextMapper.convert("{NOBORDER}\n{SPEED6}\n@, you got the sword!\n{CHANGEMUSIC}\nNow let's go beat up Agahnim!")
@@ -1781,16 +1770,19 @@ class TextTable(object):
text['mastersword_pedestal_translated'] = CompressedTextMapper.convert("A test of strength: If you have 3 pendants, I'm yours.")
text['telepathic_tile_spectacle_rock'] = CompressedTextMapper.convert("{NOBORDER}\n{NOBORDER}\nUse the Mirror, or the Hookshot and Hammer, to get to Tower of Hera!")
text['telepathic_tile_swamp_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nDrain the floodgate to raise the water here!")
text['telepathic_tile_thieves_town_upstairs'] = CompressedTextMapper.convert("{NOBORDER}\nBlind hates bright light.")
text['telepathic_tile_thieves_town_upstairs'] = CompressedTextMapper.convert("{NOBORDER}\nBlind hate's bright light.")
text['telepathic_tile_misery_mire'] = CompressedTextMapper.convert("{NOBORDER}\nLighting 4 torches will open your way forward!")
text['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^")
text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.")
text['telepathic_tile_under_ganon'] = CompressedTextMapper.convert("Doors Async League winners\n{HARP}\n"
" ~~~2025~~~\nSchulzer\n\n"
" ~~~2024~~~\nhumbugh\n\n"
" ~~~2023~~~\nEriror\n\n"
" ~~~2022~~~\nAndy\n\n"
" ~~~2021~~~\nprdwong")
text['telepathic_tile_under_ganon'] = CompressedTextMapper.convert(
" ALTTPR League" \
" Winners\n{HARP}\n\n" \
" ~~~Season 5~~~\n OKDUDES: \n GanonsGoneWild\n Telethar\n Hitsuyan1337\n\n" \
" ~~~Season 4~~~\nThe Titan's Mitts:\n SailorNep\n Relkin\n Daaanty\n\n" \
" ~~~Season 3~~~\nPhenandra Drifters:\n Megawott\n Frostbite3030\n Jet082\n\n" \
" ~~~Season 2~~~\n Team すごい:\n IIYoshiII\n Hitsuyan1337\n Ramond\n\n" \
" ~~~Season 1~~~\n Team すごい:\n IIYoshiII\n Thalane\n Hitsuyan1337\n\n" \
)
text['telepathic_tile_palace_of_darkness'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a funny looking Enemizer")
# C0
text['telepathic_tile_desert_bonk_torch_room'] = CompressedTextMapper.convert("{NOBORDER}\nThings can be knocked down, if you fancy yourself a dashing dude.")
@@ -1800,14 +1792,15 @@ class TextTable(object):
text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n"
" ~~~2024~~~\nGammachuu\n\n"
" ~~~2023~~~\nGanonsGoneWild\n\n"
" ~~~2022~~~\nObscure\n\n"
" ~~~2021~~~\nDaaanty\n\n"
" ~~~2019~~~\nJet082\n\n"
" ~~~2018~~~\nAndy\n\n"
" ~~~2017~~~\nA: ajneb174\nS: ajneb174")
text['houlihan_room'] = CompressedTextMapper.convert(
" Crosskeys\n" \
" Tournament\n" \
" Winners\n{HARP}\n" \
" ~~~2022~~~\n Schulzer\n\n" \
" ~~~2021~~~\n Goomba\n\n" \
" ~~~2020~~~\n Linlinlin\n\n" \
" ~~~2019~~~\n Kohrek\n" \
)
text['caught_a_bee'] = CompressedTextMapper.convert("Caught a Bee\n ≥ Keep\n Release\n{CHOICE}")
text['caught_a_fairy'] = CompressedTextMapper.convert("Caught Fairy!\n ≥ Keep\n Release\n{CHOICE}")
text['no_empty_bottles'] = CompressedTextMapper.convert("Whoa, bucko!\nNo empty bottles.")
@@ -1892,18 +1885,13 @@ class TextTable(object):
text['magic_bat_wake'] = CompressedTextMapper.convert("You bum! I was sleeping! Where's my magic bolts?")
text['magic_bat_give_half_magic'] = CompressedTextMapper.convert("How you like me now?")
text['intro_main'] = CompressedTextMapper.convert(
"{INTRO}\n Episode III"
+ "{PAUSE3}\n A Link to the Past"
+ "{PAUSE3}\n Randomizer"
+ "{PAUSE3}\n\n\n"
+ "{PAUSE3}\nAfter mostly disregarding what happened in the first two games,"
+ "{PAUSE3}\nLink awakens to his uncle leaving the house."
+ "{PAUSE3}\nHe just runs out the door, into the rainy night."
+ "{PAUSE3} {CHANGEPIC}\nGanon has moved around all the items in Hyrule."
+ "{PAUSE7}\nYou will have to find all the items necessary to beat Ganon."
+ "{PAUSE7}\nThis is your chance to be a hero."
+ "{PAUSE3} {CHANGEPIC}\nYou must get the 7 crystals to beat Ganon."
+ "{PAUSE9} {CHANGEPIC}", False)
"{INTRO}\n Episode III\n{PAUSE3}\n A Link to\n the Past\n"
+ "{PAUSE3}\n Randomizer\n{PAUSE3}\nAfter mostly disregarding what happened in the first two games.\n"
+ "{PAUSE3}\nLink awakens to his uncle leaving the house.\n{PAUSE3}\nHe just runs out the door,\n"
+ "{PAUSE3}\ninto the rainy night.\n{PAUSE3}\n{CHANGEPIC}\nGanon has moved around all the items in Hyrule.\n"
+ "{PAUSE7}\nYou will have to find all the items necessary to beat Ganon.\n"
+ "{PAUSE7}\nThis is your chance to be a hero.\n{PAUSE3}\n{CHANGEPIC}\n"
+ "You must get the 7 crystals to beat Ganon.\n{PAUSE9}\n{CHANGEPIC}", False)
text['intro_throne_room'] = CompressedTextMapper.convert("{IBOX}\nLook at this Stalfos on the throne.", False)
text['intro_zelda_cell'] = CompressedTextMapper.convert("{IBOX}\nIt is your time to shine!", False)
text['intro_agahnim'] = CompressedTextMapper.convert("{IBOX}\nAlso, you need to defeat this guy!", False)
@@ -2015,7 +2003,7 @@ class TextTable(object):
text['thief_desert_rupee_cave'] = CompressedTextMapper.convert("So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees.")
text['thief_ice_rupee_cave'] = CompressedTextMapper.convert("I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am.")
text['telepathic_tile_south_east_darkworld_cave'] = CompressedTextMapper.convert("~~ dev cave ~~\n no farming\n required")
text['cukeman'] = CompressedTextMapper.convert("Trans rights!")
text['cukeman'] = CompressedTextMapper.convert("Did you hear that Veetorp beat ajneb174 in a 1 on 1 race at AGDQ?")
text['cukeman_2'] = CompressedTextMapper.convert("You found Shabadoo, huh?\nNiiiiice.")
text['potion_shop_no_cash'] = CompressedTextMapper.convert("Yo! I'm not running a charity here.")
text['kakariko_powdered_chicken'] = CompressedTextMapper.convert("Smallhacker…\n\n\nWas hiding, you found me!\n\n\nOkay, you can leave now.")
@@ -2040,14 +2028,7 @@ class TextTable(object):
text['ganon_fall_in_alt'] = CompressedTextMapper.convert("You think you are ready to face me?\n\nI will not die unless you complete your goals. Dingus!")
text['ganon_phase_3_alt'] = CompressedTextMapper.convert("Got wax in your ears? I cannot die!")
# 190
text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n"
"~~~HMG 2025~~~\nSkele\n"
"~~~No Logic 2024~~~\ntam\n\n"
"~~~HMG 2023~~~\ntam\n\n"
"~~~No Logic 2022~~~\nChexhuman\n\n"
"~~~HMG 2021~~~\nKrithel\n\n"
"~~~OWG 2019~~~\nGlan\n\n"
"~~~OWG 2018~~~\nChristosOwen\nthe numpty")
text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n~~~HMG 2021~~~\nKrithel\n\n~~~OWG 2019~~~\nGlan\n\n~~~OWG 2018~~~\nChristosOwen\nthe numpty")
text['fish_money'] = CompressedTextMapper.convert("It's a secret to everyone.")
text['sign_ganons_tower'] = CompressedTextMapper.convert("You need all 7 crystals to enter.")
text['sign_ganon'] = CompressedTextMapper.convert("You need all 7 crystals to beat Ganon.")
@@ -2055,7 +2036,6 @@ class TextTable(object):
text['ganon_phase_3_no_silvers_alt'] = CompressedTextMapper.convert("You can't best me without silver arrows!")
text['ganon_phase_3_no_silvers'] = CompressedTextMapper.convert("You can't best me without silver arrows!")
text['ganon_phase_3_silvers'] = CompressedTextMapper.convert("Oh no! Silver! My one true weakness!")
text['murahdahla'] = CompressedTextMapper.convert("Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n{PAUSE3}\n… … …\nWait! You can see me? I knew I should have\nhidden in a hollow tree.")
text['mastersword_pedestal_goal'] = CompressedTextMapper.convert("To claim thy reward, you must present all 3 Pendants of Virtue.")
text['murahdahla'] = CompressedTextMapper.convert("Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n{PAUSE3}\n… … …\nWait! you can see me? I knew I should have\nhidden in a hollow tree.")
text['end_pad_data'] = bytearray([0xfb])
text['terminator'] = bytearray([0xFF, 0xFF])
-372
View File
@@ -1,372 +0,0 @@
import functools
import Rules
from BaseClasses import Door, DoorType, Entrance
from DoorShuffle import connect_simple_door
kikiskip_spots = [
("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal")
]
mirehera_spots = [("Mire to Hera Clip", "Mire Torches Top", "Hera Portal")]
heraswamp_spots = [("Hera to Swamp Clip", "Mire Torches Top", "Swamp Portal")]
icepalace_spots = [("Ice Lobby Clip", "Ice Lobby", "Ice Bomb Drop - Top")]
thievesdesert_spots = [
("Thieves to Desert West Clip", "Thieves Attic", "Desert West Portal"),
("Thieves to Desert South Clip", "Thieves Attic", "Desert South Portal"),
("Thieves to Desert East Clip", "Thieves Attic", "Desert East Portal"),
]
specrock_spots = [
("Spec Rock Clip", "Spectacle Rock Cave (Peak)", "Spectacle Rock Cave (Top)")
]
paradox_spots = [
("Paradox Front Teleport", "Paradox Cave Front", "Paradox Cave Chest Area")
]
def create_hmg_entrances_regions(world, player):
for spots in [
kikiskip_spots,
mirehera_spots,
heraswamp_spots,
icepalace_spots,
thievesdesert_spots,
specrock_spots,
paradox_spots,
]:
for entrance, parent_region, _, *_ in spots:
parent = world.get_region(parent_region, player)
connection = Entrance(player, entrance, parent)
connection.spot_type = 'HMG'
if connection not in parent.exits:
parent.exits.append(connection)
ip_bomb_top_reg = world.get_region("Ice Bomb Drop - Top", player)
ip_clip_entrance = Entrance(player, "Ice Bomb Drop Clip", ip_bomb_top_reg)
ip_bomb_top_reg.exits.append(ip_clip_entrance)
def connect_hmg_entrances_regions(world, player):
for spots in [
kikiskip_spots,
mirehera_spots,
heraswamp_spots,
icepalace_spots,
thievesdesert_spots,
specrock_spots,
paradox_spots,
]:
for entrance, _, target_region, *_ in spots:
connection = world.get_entrance(entrance, player)
if world.fix_fake_world[player] and target_region.endswith(" Portal"):
target = world.get_portal(target_region[:-7], player).find_portal_entrance().parent_region
else:
target = world.get_region(target_region, player)
connection.connect(target)
# Add the new Ice path (back of bomb drop to front) to the world and model it properly
ip_clip_entrance = world.get_entrance('Ice Bomb Drop Clip', player)
clip_door = Door(player, "Ice Bomb Drop Clip", DoorType.Logical, ip_clip_entrance)
world.doors += [clip_door]
world.initialize_doors([clip_door])
connect_simple_door(world, "Ice Bomb Drop Clip", "Ice Bomb Drop", player)
# For some entrances, we need to fake having pearl, because we're in fake DW/LW.
# This creates a copy of the input state that has Moon Pearl.
def fake_pearl_state(state, player):
if state.has("Moon Pearl", player):
return state
fake_state = state.copy()
fake_state.prog_items["Moon Pearl", player] += 1
return fake_state
# Sets the rules on where we can actually go using this clip.
# Behavior differs based on what type of ER shuffle we're playing.
def dungeon_reentry_rules(
world,
player,
clip: Entrance,
dungeon_region: str,
):
fix_dungeon_exits = world.fix_palaceofdarkness_exit[player]
fix_fake_worlds = world.fix_fake_world[player]
all_clips = [
x[0]
for x in kikiskip_spots
+ mirehera_spots
+ heraswamp_spots
+ icepalace_spots
+ thievesdesert_spots
+ specrock_spots
+ paradox_spots
]
dungeon_entrance = [
r
for r in world.get_region(dungeon_region, player).entrances
if r.name not in all_clips
][0]
dungeon_exit = [
r
for r in world.get_region(dungeon_region, player).exits
if r.name not in all_clips
][0]
if not fix_dungeon_exits:
# vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix
# Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially.
# entrance doesn't exist until you fire rod it from the other side
if dungeon_entrance.name == "Skull Woods Final Section":
Rules.set_rule(clip, lambda state: False)
elif dungeon_entrance.name == "Misery Mire":
if world.swords[player] == "swordless":
Rules.add_rule(
clip, lambda state: state.has_misery_mire_medallion(player)
)
else:
Rules.add_rule(
clip,
lambda state: state.has_sword(player)
and state.has_misery_mire_medallion(player),
)
elif dungeon_entrance.name == "Agahnims Tower":
Rules.add_rule(
clip,
lambda state: state.has("Cape", player)
or state.has_beam_sword(player)
or state.has("Beat Agahnim 1", player),
)
# Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally.
Rules.add_rule(
world.get_entrance(dungeon_exit, player),
lambda state: dungeon_entrance.can_reach(state),
)
elif (
not fix_fake_worlds
): # full, dungeonsfull; fixed dungeon exits, but no fake worlds fix
# Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region.
Rules.add_rule(
clip,
lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player)),
)
# exiting restriction
Rules.add_rule(
world.get_entrance(dungeon_exit, player),
lambda state: dungeon_entrance.can_reach(state),
)
# Otherwise, the shuffle type is lean, lite, crossed, or insanity; all of these do not need additional rules on where we can go,
# since the clip links directly to the exterior region.
def underworld_glitches_rules(world, player):
def mire_clip(state):
torches = world.get_region("Mire Torches Top", player)
return (state.can_dash_clip(torches, player)
or (state.can_bomb_clip(torches, player) and state.has_fire_source(player))
) and state.can_reach(torches, player)
def hera_clip(state):
hera = world.get_region("Hera 4F", player)
return (state.can_bomb_clip(hera, player) or state.can_dash_clip(hera, player)) \
and state.has("Flippers", player) and state.can_reach(hera) and mire_clip(state)
# We use these plus functool.partial because lambdas don't work in loops properly.
def bomb_clip(state, region, player):
return state.can_bomb_clip(region, player) and state.can_reach(region, player)
def dash_clip(state, region, player):
return state.can_dash_clip(region, player) and state.can_reach(region, player)
# Bomb clips
for clip in (
kikiskip_spots
+ icepalace_spots
+ thievesdesert_spots
+ specrock_spots
):
region = world.get_region(clip[1], player)
Rules.set_rule(
world.get_entrance(clip[0], player),
functools.partial(bomb_clip, region=region, player=player),
)
# Dash clips
for clip in icepalace_spots:
region = world.get_region(clip[1], player)
Rules.add_rule(
world.get_entrance(clip[0], player),
functools.partial(dash_clip, region=region, player=player),
combine="or",
)
for spot in kikiskip_spots + thievesdesert_spots:
dungeon_reentry_rules(
world,
player,
world.get_entrance(spot[0], player),
spot[2],
)
for clip in mirehera_spots:
Rules.set_rule(
world.get_entrance(clip[0], player),
lambda state: mire_clip(state),
)
# Need to be able to escape by hitting the switch from the back
Rules.set_rule(
world.get_entrance("Ice Bomb Drop Clip", player),
lambda state: (
state.can_use_bombs(player) or state.has("Cane of Somaria", player)
),
)
# Allow mire big key to be used in Hera
Rules.add_rule(
world.get_entrance("Hera Startile Corner NW", player),
lambda state: state.has("Big Key (Misery Mire)", player) and mire_clip(state),
combine="or",
)
Rules.add_rule(
world.get_location("Tower of Hera - Big Chest", player),
lambda state: state.has("Big Key (Misery Mire)", player) and mire_clip(state),
combine="or",
)
# This uses the mire clip because it's always expected to come from mire
Rules.set_rule(
world.get_entrance("Hera to Swamp Clip", player),
lambda state: state.has("Flippers", player) and mire_clip(state),
)
Rules.add_rule(
world.get_location("Swamp Palace - Big Chest", player),
lambda state: (state.has("Big Key (Misery Mire)", player) or state.has("Big Key (Tower of Hera)", player)) \
and state.has("Flippers", player) and mire_clip(state),
combine="or",
)
# We need to set _all_ swamp doors to be openable with mire keys, otherwise the small key can't be behind them - 6 keys because of Pots
# Flippers required for all of these doors to prevent locks when flooding
for door in [
"Swamp Trench 1 Approach ES",
"Swamp Hammer Switch SW",
"Swamp Entrance Down Stairs",
"Swamp Pot Row WS",
"Swamp Trench 1 Key Ledge NW",
"Swamp Hub WN",
"Swamp Hub North Ledge N",
"Swamp Crystal Switch EN",
"Swamp Push Statue S",
"Swamp Waterway NW",
"Swamp T SW",
]:
Rules.add_rule(
world.get_entrance(door, player),
lambda state: state.has("Flippers", player)
and state.has("Small Key (Misery Mire)", player, count=6)
and mire_clip(state),
combine="or",
)
Rules.add_rule(
world.get_location("Trench 1 Switch", player),
lambda state: mire_clip(state) or hera_clip(state),
combine="or",
)
# Build the rule for SP moat.
# We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT.
# First we require a certain type of entrance shuffle, then build the rule from its pieces.
if not world.swamp_patch_required[player] and world.shuffle[player] in [
"vanilla",
"dungeonssimple",
"dungeonsfull",
]:
rule_map = {
"Mire Portal": (
lambda state: state.can_reach("Mire Torches Top", "Entrance", player)
),
"Hera Portal": (
lambda state: state.can_reach(
"Hera Startile Corner NW", "Entrance", player
)
),
}
inverted_dm = (world.mode[player] == "inverted") != world.is_tile_swapped(0x03, player)
def hera_rule(state):
return (state.has("Moon Pearl", player) or not inverted_dm) and rule_map.get(
world.get_entrance("Tower of Hera", player).connected_region.name,
lambda state: False,
)(state)
def gt_rule(state):
return (state.has("Moon Pearl", player) or inverted_dm) and rule_map.get(
world.get_entrance(("Ganons Tower"), player).connected_region.name,
lambda state: False,
)(state)
def mirrorless_moat_rule(state):
return (
state.can_reach("Old Man S&Q", "Entrance", player)
and state.has("Flippers", player)
and (hera_rule(state) or gt_rule(state))
and mire_clip(state)
)
Rules.add_rule(
world.get_entrance("Swamp Lobby Moat", player),
lambda state: mirrorless_moat_rule(state),
combine="or",
)
desert_exits = ["West", "South", "East"]
for desert_exit in desert_exits:
Rules.add_rule(
world.get_entrance(f"Thieves to Desert {desert_exit} Clip", player),
lambda state: state.can_dash_clip(
world.get_region("Thieves Attic", player), player
),
)
# Collecting left chests in Paradox Cave using a dash clip -> dash citrus, 1f right, teleport up
paradox_left_chests = [
"Paradox Cave Lower - Far Left",
"Paradox Cave Lower - Left",
"Paradox Cave Lower - Middle",
]
for location in paradox_left_chests:
Rules.add_rule(
world.get_location(location, player),
lambda state: state.can_dash_clip(
world.get_location(location, player).parent_region, player
),
"or",
)
# Collecting right chests in Paradox Cave using a dash clip on left side -> dash citrus, 1f right, teleport up, then hitting the switch
paradox_right_chests = [
"Paradox Cave Lower - Right",
"Paradox Cave Lower - Far Right",
]
for location in paradox_right_chests:
Rules.add_rule(
world.get_location(location, player),
lambda state: (
state.can_dash_clip(
world.get_location(location, player).parent_region, player
)
and state.can_hit_crystal(player)
),
"or",
)
+63 -743
View File
@@ -1,90 +1,82 @@
#!/usr/bin/env python3
import fileinput
import os
import re
import subprocess
import sys
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from collections import defaultdict
from hashlib import md5
from itertools import count
from math import factorial
from pathlib import Path
import yaml
def int16_as_bytes(value):
value = value & 0xFFFF
return [value & 0xFF, (value >> 8) & 0xFF]
def int24_as_bytes(value):
value = value & 0xFFFFFF
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF]
def int32_as_bytes(value):
value = value & 0xFFFFFFFF
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
def pc_to_snes(value):
return ((value << 1) & 0x7F0000) | (value & 0x7FFF) | 0x8000
return ((value<<1) & 0x7F0000)|(value & 0x7FFF)|0x8000
def snes_to_pc(value):
return ((value & 0x7F0000) >> 1) | (value & 0x7FFF)
def parse_player_names(names, players, teams):
names = [n for n in re.split(r'[, ]', names) if n]
ret = []
while names or len(ret) < teams:
team = [n[:16] for n in names[:players]]
while len(team) != players:
team.append(f"Player {len(team) + 1}")
ret.append(team)
names = names[players:]
return ret
return ((value & 0x7F0000)>>1)|(value & 0x7FFF)
def is_bundled():
return getattr(sys, 'frozen', False)
def count_set_bits(val):
if val == 0:
return 0
else:
return (val & 1) + count_set_bits(val >> 1)
def local_path(path):
# just do stuff here and bail
return os.path.join(".", path)
if local_path.cached_path is not None:
return os.path.join(local_path.cached_path, path)
if is_bundled():
# we are running in a bundle
local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member
local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member
else:
# we are running in a normal Python environment
local_path.cached_path = os.path.dirname(os.path.abspath(__file__))
return os.path.join(local_path.cached_path, path)
local_path.cached_path = None
def output_path(path):
if output_path.cached_path is None:
output_path.cached_path = '.'
return os.path.join(output_path.cached_path, path)
if output_path.cached_path is not None:
return os.path.join(output_path.cached_path, path)
if not is_bundled():
output_path.cached_path = '.'
return os.path.join(output_path.cached_path, path)
else:
# has been packaged, so cannot use CWD for output.
if sys.platform == 'win32':
#windows
import ctypes.wintypes
CSIDL_PERSONAL = 5 # My Documents
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf)
documents = buf.value
elif sys.platform == 'darwin':
from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
NSDocumentDirectory = 9
NSUserDomainMask = 1
# True for expanding the tilde into a fully qualified path
documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]
else:
raise NotImplementedError('Not supported yet')
output_path.cached_path = os.path.join(documents, 'ALttPEntranceRandomizer')
if not os.path.exists(output_path.cached_path):
os.mkdir(output_path.cached_path)
return os.path.join(output_path.cached_path, path)
output_path.cached_path = None
def open_file(filename):
if sys.platform == 'win32':
os.startfile(filename)
@@ -92,712 +84,40 @@ def open_file(filename):
open_command = 'open' if sys.platform == 'darwin' else 'xdg-open'
subprocess.call([open_command, filename])
def close_console():
if sys.platform == 'win32':
# windows
#windows
import ctypes.wintypes
try:
ctypes.windll.kernel32.FreeConsole()
except Exception:
pass
def get_new_romhash(new_rom='working.sfc'):
def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', new_rom='working.sfc'):
from collections import OrderedDict
import json
import hashlib
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
with open(new_rom, 'rb') as stream:
new_rom_data = bytearray(stream.read())
# extend to 2 mb
old_rom_data.extend(bytearray([0x00] * (2097152 - len(old_rom_data))))
basemd5 = md5()
out_data = OrderedDict()
for idx, old in enumerate(old_rom_data):
new = new_rom_data[idx]
if old != new:
out_data[idx] = [int(new)]
for offset in reversed(list(out_data.keys())):
if offset - 1 in out_data:
out_data[offset-1].extend(out_data.pop(offset))
with open('data/base2current.json', 'wt') as outfile:
json.dump([{key:value} for key, value in out_data.items()], outfile, separators=(",", ":"))
basemd5 = hashlib.md5()
basemd5.update(new_rom_data)
return basemd5.hexdigest()
def kth_combination(k, l, r):
if r == 0:
return []
elif len(l) == r:
return l
else:
i = ncr(len(l)-1, r-1)
if k < i:
return l[0:1] + kth_combination(k, l[1:], r-1)
else:
return kth_combination(k-i, l[1:], r)
def ncr(n, r):
if r == 0 or r >= n:
return 1
return factorial(n) // factorial(r) // factorial(n-r)
entrance_offsets = {
'Sanctuary': 0x2,
'HC West': 0x3,
'HC South': 0x4,
'HC East': 0x5,
'Eastern': 0x8,
'Desert West': 0x9,
'Desert South': 0xa,
'Desert East': 0xb,
'Desert Back': 0xc,
'TR Lazy Eyes': 0x15,
'TR Eye Bridge': 0x18,
'TR Chest': 0x19,
'Aga Tower': 0x24,
'Swamp': 0x25,
'Palace of Darkness': 0x26,
'Mire': 0x27,
'Skull 2 West': 0x28,
'Skull 2 East': 0x29,
'Skull 1': 0x2a,
'Skull 3': 0x2b,
'Ice': 0x2d,
'Hera': 0x33,
'Thieves': 0x34,
'TR Main': 0x35,
'GT': 0x37,
'Skull Pots': 0x76,
'Skull Left Drop': 0x77,
'Skull Pinball': 0x78,
'Skull Back Drop': 0x79,
'Sewer Drop': 0x81
}
entrance_data = {
'Room Ids': (0x14577, 2),
'Relative coords': (0x14681, 8),
'ScrollX': (0x14AA9, 2),
'ScrollY': (0x14BB3, 2),
'LinkX': (0x14CBD, 2),
'LinkY': (0x14DC7, 2),
'CameraX': (0x14ED1, 2),
'CameraY': (0x14FDB, 2),
'Blockset': (0x150e5, 1),
'FloorValues': (0x1516A, 1),
'Dungeon Value': (0x151EF, 1),
'Frame on Exit': (0x15274, 1),
'BG Setting': (0x152F9, 1),
'HV Scroll': (0x1537E, 1),
'Scroll Quad': (0x15403, 1),
'Exit Door': (0x15488, 2),
'Music': (0x15592, 1)
}
def read_layout_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'):
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
string = ''
for room in range(0, 0xff+1):
# print(ent)
pointer_start = 0xf8000+room*3
highbyte = old_rom_data[pointer_start+2]
midbyte = old_rom_data[pointer_start+1]
midbyte = midbyte - 0x80 if highbyte % 2 == 0 else midbyte
pointer = highbyte // 2 * 0x10000
pointer += midbyte * 0x100
pointer += old_rom_data[pointer_start]
layout_byte = old_rom_data[pointer+1]
layout = (layout_byte & 0x1c) >> 2
string += hex(room) + ':' + str(layout) + '\n'
print(string)
def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'):
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
for ent, offset in entrance_offsets.items():
# print(ent)
string = ent
for dp, data in entrance_data.items():
byte_array = []
address, size = data
for i in range(0, size):
byte_array.append(old_rom_data[address+(offset*size)+i])
some_bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
string += '\t'+some_bytes
# print("%s: %s" % (dp, bytes))
print(string)
def room_palette_data(old_rom):
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
offset = defaultdict(list)
for i in range(0, 256):
pointer_offset = 0x0271e2+i*2
header_offset = old_rom_data[pointer_offset + 1] << 8
header_offset += old_rom_data[pointer_offset]
header_offset -= 0x8000
header_offset += 0x020000
offset[header_offset].append(i)
# print(f'{hex(i)}: {hex(old_rom_data[header_offset+1])}')
for header_offset, rooms in offset.items():
print(f'{hex(header_offset)}: {[hex(x) for x in rooms]}')
# Palette notes:
# HC: 0
# Sewer/Dungeon: 1
# AT: 0xc near boss, 0x0 (other) 26 (f4, f1)
# Sanc: 0x1d
# Hera: 0x6
# Desert: 0x4 (boss and near boss), 0x9 (desert tiles 1 + desert main)
# Eastern: 0xb
# Pod: 0xf, x10 (boss)
# Swamp: 0x8 (boss), 0xa (other)
# Skull: 0xe (boss), 0xd (other)
# TT: 0x17, 0x23 (attic)
# Ice: 0x13, 0x14 (boss)
# Mire: 0x11 (other) , 0x12 (boss/preroom)
# TR: 0x18, 0x19 (boss+pre)
# GT: 0x28 (entrance + B1), 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?)
# Aga2: 0x1b, 0x1b (Pre aga2)
# Caves: 0x7, 0x20
# Uncle: 0x1
# Ganon: 0x21
# Houses: 0x2
def print_wiki_doors_by_region(d_regions, world, player):
for d, region_list in d_regions.items():
tile_map = {}
for region in region_list:
tile = None
r = world.get_region(region, player)
for ext in r.exits:
door = world.check_for_door(ext.name, player)
if door is not None and door.roomIndex != -1:
tile = door.roomIndex
break
if tile is not None:
if tile not in tile_map:
tile_map[tile] = []
tile_map[tile].append(r)
toprint = ""
toprint += ('<!-- ' + d + ' -->') + "\n"
toprint += ('== Room List ==') + "\n"
toprint += "\n"
toprint += ('{| class="wikitable"') + "\n"
toprint += ('|-') + "\n"
toprint += ('! Room !! Supertile !! Doors') + "\n"
for tile, region_list in tile_map.items():
tile_done = False
for region in region_list:
toprint += ('|-') + "\n"
toprint += ('| {{Dungeon Room|{{PAGENAME}}|' + region.name + '}}') + "\n"
if not tile_done:
listlen = len(region_list)
link = '| {{UnderworldMapLink|'+str(tile)+'}}'
toprint += (link if listlen < 2 else '| rowspan = '+str(listlen)+' '+link) + "\n"
tile_done = True
strs_to_print = []
for ext in region.exits:
strs_to_print.append('{{Dungeon Door|{{PAGENAME}}|' + ext.name + '}}')
toprint += ('| '+'<br />'.join(strs_to_print))
toprint += "\n"
toprint += ('|}') + "\n"
with open(os.path.join(".", "resources", "user", "regions-" + d + ".txt"), "w+") as f:
f.write(toprint)
def update_deprecated_args(args):
if args:
argVars = vars(args)
truthy = [1, True, "True", "true"]
if "multi" in argVars:
players = int(args.multi)
else:
players = 1
# Hints default to FALSE
# Don't do: Yes
# Do: No
if "no_hints" in argVars:
if args.no_hints in truthy:
if isinstance(argVars["hints"], dict):
tmp = {}
for idx in range(1, len(argVars["hints"]) + 1):
tmp[idx] = False
args.hints = tmp
else:
args.hints = False
# Spoiler defaults to TRUE
# Don't do: Yes
# Do: No
if "suppress_spoiler" in argVars:
args.spoiler = 'none'
# Don't do: No
# Do: Yes
if "create_spoiler" in argVars:
args.spoiler = 'full'
# ROM defaults to TRUE
# Don't do: Yes
# Do: No
if "suppress_rom" in argVars:
args.create_rom = not args.suppress_rom in truthy
# Don't do: No
# Do: Yes
if "create_rom" in argVars:
args.suppress_rom = not args.create_rom in truthy
# Shuffle Ganon defaults to TRUE
# Don't do: Yes
# Do: No
if "no_shuffleganon" in argVars:
if isinstance(args.shuffleganon, dict):
for player in range(1, players + 1):
args.shuffleganon[player] = not args.no_shuffleganon in truthy
else:
args.shuffleganon = not args.no_shuffleganon in truthy
# OW Parallel defaults to TRUE
# Don't do: Yes
# Do: No
if "ow_unparallel" in argVars:
if isinstance(args.ow_parallel, dict):
for player in range(1, players + 1):
args.ow_parallel[player] = not args.ow_unparallel in truthy
else:
args.ow_parallel = not args.ow_unparallel in truthy
# OW Fog defaults to TRUE
# Don't do: Yes
# Do: No
if "ow_no_fog" in argVars:
if isinstance(args.ow_fog, dict):
for player in range(1, players + 1):
args.ow_fog[player] = not args.ow_no_fog in truthy
else:
args.ow_fog = not args.ow_no_fog in truthy
# Playthrough defaults to TRUE
# Don't do: Yes
# Do: No
if "skip_playthrough" in argVars:
args.calc_playthrough = not args.skip_playthrough in truthy
# Don't do: No
# Do: Yes
if "calc_playthrough" in argVars:
args.skip_playthrough = not args.calc_playthrough in truthy
return args
def print_wiki_doors_by_room(d_regions, world, player):
for d, region_list in d_regions.items():
tile_map = {}
for region in region_list:
tile = None
r = world.get_region(region, player)
for ext in r.exits:
door = world.check_for_door(ext.name, player)
if door is not None and door.roomIndex != -1:
tile = door.roomIndex
break
if tile is not None:
if tile not in tile_map:
tile_map[tile] = []
tile_map[tile].append(r)
toprint = ""
toprint += ('<!-- ' + d + ' -->') + "\n"
for tile, region_list in tile_map.items():
for region in region_list:
toprint += ('<!-- ' + region.name + ' -->') + "\n"
toprint += ('{{Infobox dungeon room') + "\n"
toprint += ('| dungeon = {{ROOTPAGENAME}}') + "\n"
toprint += ('| supertile = ' + str(tile)) + "\n"
toprint += ('| tile = x') + "\n"
toprint += ('}}') + "\n"
toprint += ('') + "\n"
toprint += ('== Doors ==') + "\n"
toprint += ('{| class="wikitable"') + "\n"
toprint += ('|-') + "\n"
toprint += ('! Door !! Room Side !! Requirement') + "\n"
for ext in region.exits:
ext_part = ext.name.replace(region.name, '')
ext_part = ext_part.strip()
toprint += ('{{DungeonRoomDoorList/Row|{{ROOTPAGENAME}}|{{SUBPAGENAME}}|' + ext_part + '|Side|}}') + "\n"
toprint += ('|}') + "\n"
toprint += ('') + "\n"
with open(os.path.join(".", "resources", "user", "rooms-" + d + ".txt"), "w+") as f:
f.write(toprint)
def print_xml_doors(d_regions, world, player):
root = ET.Element('root')
for d, region_list in d_regions.items():
tile_map = {}
for region in region_list:
tile = None
r = world.get_region(region, player)
for ext in r.exits:
door = world.check_for_door(ext.name, player)
if door is not None and door.roomIndex != -1:
tile = door.roomIndex
break
if tile is not None:
if tile not in tile_map:
tile_map[tile] = []
tile_map[tile].append(r)
dungeon = ET.SubElement(root, 'dungeon', {'name': d})
for tile, r_list in tile_map.items():
supertile = ET.SubElement(dungeon, 'supertile', {'id': str(tile)})
for region in r_list:
room = ET.SubElement(supertile, 'room', {'name': region.name})
for ext in region.exits:
ET.SubElement(room, 'door', {'name': ext.name})
ET.dump(root)
def print_graph(world):
root = ET.Element('root')
for region in world.regions:
r = ET.SubElement(root, 'region', {'name': region.name})
for ext in region.exits:
attribs = {'name': ext.name}
if ext.connected_region:
attribs['connected_region'] = ext.connected_region.name
if ext.door and ext.door.dest:
attribs['dest'] = ext.door.dest.name
ET.SubElement(r, 'exit', attribs)
ET.dump(root)
def extract_data_from_us_rom(rom):
with open(rom, 'rb') as stream:
rom_data = bytearray(stream.read())
rooms = [0x1c, 0x1d, 0x4e]
# rooms = [0x9a, 0x69, 0x78, 0x79, 0x7a, 0x88, 0x8a, 0xad]
for room in rooms:
b2idx = room*2
b3idx = room*3
headerptr = 0x110000 + b2idx # zscream specific
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x108000 # zscream specific
header, objectdata, spritedata, secretdata = [], [], [], []
for i in range(0, 14):
header.append(rom_data[headerloc+i])
objectptr = 0xF8000 + b3idx
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
bank = rom_data[objectptr+2]
even = bank % 2 == 0
adjustment = ((bank // 2 if even else bank // 2 + 1) << 16) + (0x8000 if even else 0)
objectloc -= adjustment
stop, idx = False, 0
ffcnt = 0
mode = 0
# first two bytes
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
objectdata.append(b1)
objectdata.append(b2)
idx += 2
while ffcnt < 3:
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
b3 = rom_data[objectloc+idx+2]
objectdata.append(b1)
objectdata.append(b2)
if b1 == 0xff and b2 == 0xff:
ffcnt += 1
mode = 0
idx += 2
elif b1 == 0xf0 and b2 == 0xff:
mode = 1
idx += 2
elif not mode and ffcnt < 3:
objectdata.append(b3)
idx += 3
else:
idx += 2
spriteptr = 0x4d62e + b2idx
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
done, idx = False, 0
while not done:
b1 = rom_data[spriteloc+idx]
spritedata.append(b1)
if b1 == 0xff:
done = True
idx += 1
secretptr = 0xdb69 + b2idx
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
done, idx = False, 0
while not done:
b1 = rom_data[secretloc+idx]
b2 = rom_data[secretloc+idx+1]
b3 = rom_data[secretloc+idx+2]
secretdata.append(b1)
secretdata.append(b2)
if b1 == 0xff and b2 == 0xff:
done = True
else:
secretdata.append(b3)
idx += 3
print(f'Room {room:02x}')
print(f'db {",".join([f"${x:02x}" for x in header])}')
print(f'Obj Length: {len(objectdata)}')
print_data_block(objectdata)
print('Sprites')
print_data_block(spritedata)
print('Secrets')
print_data_block(secretdata)
blockdata, torchdata = [], []
blockloc = 0x271de
for i in range(0, 128):
idx = i*4
b1 = rom_data[blockloc+idx]
b2 = rom_data[blockloc+idx+1]
room_idx = b1 + b2*0x100
if room_idx in rooms:
blockdata.append(b1)
blockdata.append(b2)
blockdata.append(rom_data[blockloc+idx+2])
blockdata.append(rom_data[blockloc+idx+3])
torchloc = 0x2736A
nomatch = False
append = False
for i in range(0, 192):
idx = i*2
b1 = rom_data[torchloc+idx]
b2 = rom_data[torchloc+idx+1]
if nomatch:
if b1 == 0xff and b2 == 0xff:
nomatch = False
elif not append:
room_idx = b1 + b2*0x100
if room_idx in rooms:
append = True
else:
nomatch = True
if append:
torchdata.append(b1)
torchdata.append(b2)
if b1 == 0xff and b2 == 0xff:
append = False
print('Blocks')
print_data_block(blockdata)
print('Torches')
print_data_block(torchdata)
print()
def print_data_block(block):
for i in range(0, len(block)//16 + 1):
slice = block[i*16:i*16+16]
print(f'db {",".join([f"${x:02x}" for x in slice])}')
def extract_data_from_jp_rom(rom):
with open(rom, 'rb') as stream:
rom_data = bytearray(stream.read())
rooms = range(0, 0x128)
# rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d]
# rooms = [0x1a, 0x2a, 0xd1]
for room in rooms:
# print(f'Room {room:02x}')
b2idx = room*2
b3idx = room*3
headerptr = 0x271e2 + b2idx
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x18000
header, objectdata, spritedata, secretdata = [], [], [], []
for i in range(0, 14):
header.append(rom_data[headerloc+i])
objectptr = 0xF8000 + b3idx
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
bank = rom_data[objectptr+2]
even = bank % 2 == 0
adjustment = ((bank // 2 if even else bank // 2 + 1) << 16) + (0x8000 if even else 0)
objectloc -= adjustment
stop, idx = False, 0
ffcnt = 0
mode = 0
secret_cnt = 0
# first two bytes
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
objectdata.append(b1)
objectdata.append(b2)
idx += 2
while ffcnt < 3:
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
b3 = rom_data[objectloc+idx+2]
objectdata.append(b1)
objectdata.append(b2)
if b1 == 0xff and b2 == 0xff:
ffcnt += 1
mode = 0
idx += 2
elif b1 == 0xf0 and b2 == 0xff:
mode = 1
idx += 2
elif not mode and ffcnt < 3:
objectdata.append(b3)
if b3 == 0xFA and ((b2 & 0x3) << 2) | (b1 & 0x3) in [0xf, 0xc]:
# potcalc
vram = ((b1 & 0xFC) >> 1) | ((b2 & 0xFC) << 5)
low = vram & 0xFF
high = (vram & 0xFF00) >> 8
if ffcnt == 1:
high |= 0x20
print(f'db {", ".join([f"${low:02x}", f"${high:02x}"])}')
secret_cnt += 1
idx += 3
else:
idx += 2
if secret_cnt:
print(f'Room {room:02x} {secret_cnt}')
spriteptr = 0x4d62e + b2idx
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
secretptr = 0xdb67 + b2idx
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
done, idx = False, 0
while not done:
b1 = rom_data[spriteloc+idx]
spritedata.append(b1)
if b1 == 0xff:
done = True
idx += 1
done, idx = False, 0
while not done:
b1 = rom_data[secretloc+idx]
b2 = rom_data[secretloc+idx+1]
b3 = rom_data[secretloc+idx+2]
secretdata.append(b1)
secretdata.append(b2)
if b1 == 0xff and b2 == 0xff:
done = True
else:
secretdata.append(b3)
idx += 3
# print(f'Room {room:02x}')
# print(f'HeaderPtr {headerptr:06x}')
# print(f'HeaderLoc {headerloc:06x}')
# print(f'db {",".join([f"${x:02x}" for x in header])}')
# print(f'Obj Length: {len(objectdata)}')
# print(f'ObjectPtr {objectptr:06x}')
# print(f'ObjectLoc {objectloc:06x}')
# for i in range(0, len(objectdata)//16 + 1):
# slice = objectdata[i*16:i*16+16]
# print(f'db {",".join([f"${x:02x}" for x in slice])}')
# print(f'SpritePtr {spriteptr:06x}')
# print(f'SpriteLoc {spriteloc:06x}')
# print_data_block(spritedata)
# print(f'SecretPtr {secretptr:06x}')
# print(f'SecretLoc {secretloc:06x}')
# print_data_block(secretdata)
# print()
def count_set_bits(val):
if val == 0:
return 0
else:
return (val & 1) + count_set_bits(val >> 1)
def check_pots():
from PotShuffle import vanilla_pots
for supertile, pot_list in vanilla_pots.items():
for i,pot in enumerate(pot_list):
if pot.obj_ref:
r = pot.obj_ref
secret_vram = pot.x | (pot.y << 8)
tile_vram = ((r.data[1] & 0xFC) << 5) | ((r.data[0] & 0xFC) >> 1)
if secret_vram != tile_vram:
print(f'{pot.room}#{i+1} secret: {hex(secret_vram)} tile: {hex(tile_vram)}')
def stack_size3a(size=2):
# See reference: https://stackoverflow.com/questions/34115298/how-do-i-get-the-current-depth-of-the-python-interpreter-stack
"""Get stack size for caller's frame."""
frame = sys._getframe(size)
try:
for size in count(size, 8):
frame = frame.f_back.f_back.f_back.f_back.\
f_back.f_back.f_back.f_back
except AttributeError:
while frame:
frame = frame.f_back
size += 1
return size - 1
def find_and_replace():
for data_line in fileinput.input('scratch.txt', inplace=True):
if '=' in data_line:
one, two = data_line.split(' = ')
number = int(two.strip())
print(data_line.replace(two, hex(number)))
def load_yaml(path_list):
path = os.path.join(*path_list)
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
yaml_cache = {}
def load_cached_yaml(path_list):
path = os.path.join(*path_list)
if path in yaml_cache:
return yaml_cache[path]
else:
if os.path.exists(Path(path)):
with open(path, "r", encoding="utf-8") as f:
data = yaml.load(f, Loader=yaml.SafeLoader)
yaml_cache[path] = data
return data
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
data = yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
yaml_cache[path] = data
return data
class bidict(dict):
def __init__(self, *args, **kwargs):
super(bidict, self).__init__(*args, **kwargs)
self.inverse = {}
for key, value in self.items():
self.inverse.setdefault(value,[]).append(key)
def __setitem__(self, key, value):
if key in self:
self.inverse[self[key]].remove(key)
super(bidict, self).__setitem__(key, value)
self.inverse.setdefault(value,[]).append(key)
def __delitem__(self, key):
value = self[key]
self.inverse.setdefault(value,[]).remove(key)
if value in self.inverse and not self.inverse[value]:
del self.inverse[value]
super(bidict, self).__delitem__(key)
class HexInt(int): pass
def hex_representer(dumper, data):
return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}")
return "New Rom Hash: " + basemd5.hexdigest()
if __name__ == '__main__':
print("New Rom Hash:", get_new_romhash())
# read_entrance_data(old_rom=sys.argv[1])
# room_palette_data(old_rom=sys.argv[1])
# extract_data_from_us_rom(sys.argv[1])
# extract_data_from_jp_rom(sys.argv[1])
# check_pots()
# find_and_replace()
print(make_new_base2current(new_rom="../working.sfc"))
-3
View File
@@ -1,3 +0,0 @@
GKVersion = '1.0.0'
ORVersion = '0.7.0.2'
DRVersion = '1.5.2-u'
-1
View File
@@ -1 +0,0 @@
theme: jekyll-theme-slate
@@ -0,0 +1,5 @@
Mike Lenzen <m.lenzen@gmail.com> https://github.com/mlenzen
Caleb Levy <caleb.levy@berkeley.edu> https://github.com/caleblevy
Marein Könings <mail@marein.org> https://github.com/MareinK
Jad Kik <jadkik94@gmail.com> https://github.com/jadkik
Kuba Marek <blue.cube@seznam.cz> https://github.com/bluecube
+191
View File
@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+55
View File
@@ -0,0 +1,55 @@
"""collections_extended contains a few extra basic data structures."""
from ._compat import Collection
from .bags import bag, frozenbag
from .setlists import setlist, frozensetlist
from .bijection import bijection
from .range_map import RangeMap, MappedRange
__version__ = '1.0.2'
__all__ = (
'collection',
'setlist',
'frozensetlist',
'bag',
'frozenbag',
'bijection',
'RangeMap',
'MappedRange',
'Collection',
)
def collection(iterable=None, mutable=True, ordered=False, unique=False):
"""Return a Collection with the specified properties.
Args:
iterable (Iterable): collection to instantiate new collection from.
mutable (bool): Whether or not the new collection is mutable.
ordered (bool): Whether or not the new collection is ordered.
unique (bool): Whether or not the new collection contains only unique values.
"""
if iterable is None:
iterable = tuple()
if unique:
if ordered:
if mutable:
return setlist(iterable)
else:
return frozensetlist(iterable)
else:
if mutable:
return set(iterable)
else:
return frozenset(iterable)
else:
if ordered:
if mutable:
return list(iterable)
else:
return tuple(iterable)
else:
if mutable:
return bag(iterable)
else:
return frozenbag(iterable)
+53
View File
@@ -0,0 +1,53 @@
"""Python 2/3 compatibility helpers."""
import sys
is_py2 = sys.version_info[0] == 2
if is_py2:
def keys_set(d):
"""Return a set of passed dictionary's keys."""
return set(d.keys())
else:
keys_set = dict.keys
if sys.version_info < (3, 6):
from collections import Sized, Iterable, Container
def _check_methods(C, *methods):
mro = C.__mro__
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
class Collection(Sized, Iterable, Container):
"""Backport from Python3.6."""
__slots__ = tuple()
@classmethod
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
return NotImplemented
else:
from collections.abc import Collection
def handle_rich_comp_not_implemented():
"""Correctly handle unimplemented rich comparisons.
In Python 3, return NotImplemented.
In Python 2, raise a TypeError.
"""
if is_py2:
raise TypeError()
else:
return NotImplemented
+16
View File
@@ -0,0 +1,16 @@
"""util functions for collections_extended."""
def hash_iterable(it):
"""Perform a O(1) memory hash of an iterable of arbitrary length.
hash(tuple(it)) creates a temporary tuple containing all values from it
which could be a problem if it is large.
See discussion at:
https://groups.google.com/forum/#!msg/python-ideas/XcuC01a8SYs/e-doB9TbDwAJ
"""
hash_value = hash(type(it))
for value in it:
hash_value = hash((hash_value, value))
return hash_value
+527
View File
@@ -0,0 +1,527 @@
"""Bag class definitions."""
import heapq
from operator import itemgetter
from collections.abc import Set, MutableSet, Hashable
from . import _compat
class _basebag(Set):
"""Base class for bag classes.
Base class for bag and frozenbag. Is not mutable and not hashable, so there's
no reason to use this instead of either bag or frozenbag.
"""
# Basic object methods
def __init__(self, iterable=None):
"""Create a new basebag.
If iterable isn't given, is None or is empty then the bag starts empty.
Otherwise each element from iterable will be added to the bag
however many times it appears.
This runs in O(len(iterable))
"""
self._dict = dict()
self._size = 0
if iterable:
if isinstance(iterable, _basebag):
for elem, count in iterable._dict.items():
self._dict[elem] = count
self._size += count
else:
for value in iterable:
self._dict[value] = self._dict.get(value, 0) + 1
self._size += 1
def __repr__(self):
if self._size == 0:
return '{0}()'.format(self.__class__.__name__)
else:
repr_format = '{class_name}({values!r})'
return repr_format.format(
class_name=self.__class__.__name__,
values=tuple(self),
)
def __str__(self):
if self._size == 0:
return '{class_name}()'.format(class_name=self.__class__.__name__)
else:
format_single = '{elem!r}'
format_mult = '{elem!r}^{mult}'
strings = []
for elem, mult in self._dict.items():
if mult > 1:
strings.append(format_mult.format(elem=elem, mult=mult))
else:
strings.append(format_single.format(elem=elem))
return '{%s}' % ', '.join(strings)
# New public methods (not overriding/implementing anything)
def num_unique_elements(self):
"""Return the number of unique elements.
This runs in O(1) time
"""
return len(self._dict)
def unique_elements(self):
"""Return a view of unique elements in this bag.
In Python 3:
This runs in O(1) time and returns a view of the unique elements
In Python 2:
This runs in O(n) and returns set of the current elements.
"""
return _compat.keys_set(self._dict)
def count(self, value):
"""Return the number of value present in this bag.
If value is not in the bag no Error is raised, instead 0 is returned.
This runs in O(1) time
Args:
value: The element of self to get the count of
Returns:
int: The count of value in self
"""
return self._dict.get(value, 0)
def nlargest(self, n=None):
"""List the n most common elements and their counts.
List is from the most
common to the least. If n is None, the list all element counts.
Run time should be O(m log m) where m is len(self)
Args:
n (int): The number of elements to return
"""
if n is None:
return sorted(self._dict.items(), key=itemgetter(1), reverse=True)
else:
return heapq.nlargest(n, self._dict.items(), key=itemgetter(1))
@classmethod
def _from_iterable(cls, it):
return cls(it)
@classmethod
def from_mapping(cls, mapping):
"""Create a bag from a dict of elem->count.
Each key in the dict is added if the value is > 0.
"""
out = cls()
for elem, count in mapping.items():
if count > 0:
out._dict[elem] = count
out._size += count
return out
def copy(self):
"""Create a shallow copy of self.
This runs in O(len(self.num_unique_elements()))
"""
return self.from_mapping(self._dict)
# implementing Sized methods
def __len__(self):
"""Return the cardinality of the bag.
This runs in O(1)
"""
return self._size
# implementing Container methods
def __contains__(self, value):
"""Return the multiplicity of the element.
This runs in O(1)
"""
return self._dict.get(value, 0)
# implementing Iterable methods
def __iter__(self):
"""Iterate through all elements.
Multiple copies will be returned if they exist.
"""
for value, count in self._dict.items():
for i in range(count):
yield(value)
# Comparison methods
def _is_subset(self, other):
"""Check that every element in self has a count <= in other.
Args:
other (Set)
"""
if isinstance(other, _basebag):
for elem, count in self._dict.items():
if not count <= other._dict.get(elem, 0):
return False
else:
for elem in self:
if self._dict.get(elem, 0) > 1 or elem not in other:
return False
return True
def _is_superset(self, other):
"""Check that every element in self has a count >= in other.
Args:
other (Set)
"""
if isinstance(other, _basebag):
for elem, count in other._dict.items():
if not self._dict.get(elem, 0) >= count:
return False
else:
for elem in other:
if elem not in self:
return False
return True
def __le__(self, other):
if not isinstance(other, Set):
return _compat.handle_rich_comp_not_implemented()
return len(self) <= len(other) and self._is_subset(other)
def __lt__(self, other):
if not isinstance(other, Set):
return _compat.handle_rich_comp_not_implemented()
return len(self) < len(other) and self._is_subset(other)
def __gt__(self, other):
if not isinstance(other, Set):
return _compat.handle_rich_comp_not_implemented()
return len(self) > len(other) and self._is_superset(other)
def __ge__(self, other):
if not isinstance(other, Set):
return _compat.handle_rich_comp_not_implemented()
return len(self) >= len(other) and self._is_superset(other)
def __eq__(self, other):
if not isinstance(other, Set):
return False
if isinstance(other, _basebag):
return self._dict == other._dict
if not len(self) == len(other):
return False
for elem in other:
if self._dict.get(elem, 0) != 1:
return False
return True
def __ne__(self, other):
return not (self == other)
# Operations - &, |, +, -, ^, * and isdisjoint
def __and__(self, other):
"""Intersection is the minimum of corresponding counts.
This runs in O(l + n) where:
n is self.num_unique_elements()
if other is a bag:
l = 1
else:
l = len(other)
"""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
values = dict()
for elem in self._dict:
values[elem] = min(other._dict.get(elem, 0), self._dict.get(elem, 0))
return self.from_mapping(values)
def isdisjoint(self, other):
"""Return if this bag is disjoint with the passed collection.
This runs in O(len(other))
TODO move isdisjoint somewhere more appropriate
"""
for value in other:
if value in self:
return False
return True
def __or__(self, other):
"""Union is the maximum of all elements.
This runs in O(m + n) where:
n is self.num_unique_elements()
if other is a bag:
m = other.num_unique_elements()
else:
m = len(other)
"""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
values = dict()
for elem in self.unique_elements() | other.unique_elements():
values[elem] = max(self._dict.get(elem, 0), other._dict.get(elem, 0))
return self.from_mapping(values)
def __add__(self, other):
"""Return a new bag also containing all the elements of other.
self + other = self & other + self | other
This runs in O(m + n) where:
n is self.num_unique_elements()
m is len(other)
Args:
other (Iterable): elements to add to self
"""
out = self.copy()
for value in other:
out._dict[value] = out._dict.get(value, 0) + 1
out._size += 1
return out
def __sub__(self, other):
"""Difference between the sets.
For normal sets this is all x s.t. x in self and x not in other.
For bags this is count(x) = max(0, self.count(x)-other.count(x))
This runs in O(m + n) where:
n is self.num_unique_elements()
m is len(other)
Args:
other (Iterable): elements to remove
"""
out = self.copy()
for value in other:
old_count = out._dict.get(value, 0)
if old_count == 1:
del out._dict[value]
out._size -= 1
elif old_count > 1:
out._dict[value] = old_count - 1
out._size -= 1
return out
def __mul__(self, other):
"""Cartesian product of the two sets.
other can be any iterable.
Both self and other must contain elements that can be added together.
This should run in O(m*n+l) where:
m is the number of unique elements in self
n is the number of unique elements in other
if other is a bag:
l is 0
else:
l is the len(other)
The +l will only really matter when other is an iterable with MANY
repeated elements.
For example: {'a'^2} * 'bbbbbbbbbbbbbbbbbbbbbbbbbb'
The algorithm will be dominated by counting the 'b's
"""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
values = dict()
for elem, count in self._dict.items():
for other_elem, other_count in other._dict.items():
new_elem = elem + other_elem
new_count = count * other_count
values[new_elem] = new_count
return self.from_mapping(values)
def __xor__(self, other):
"""Symmetric difference between the sets.
other can be any iterable.
This runs in O(m + n) where:
m = len(self)
n = len(other)
"""
return (self - other) | (other - self)
class bag(_basebag, MutableSet):
"""bag is a mutable unhashable bag."""
def pop(self):
"""Remove and return an element of self."""
# TODO can this be done more efficiently (no need to create an iterator)?
it = iter(self)
try:
value = next(it)
except StopIteration:
raise KeyError
self.discard(value)
return value
def add(self, elem):
"""Add elem to self."""
self._dict[elem] = self._dict.get(elem, 0) + 1
self._size += 1
def discard(self, elem):
"""Remove elem from this bag, silent if it isn't present."""
try:
self.remove(elem)
except ValueError:
pass
def remove(self, elem):
"""Remove elem from this bag, raising a ValueError if it isn't present.
Args:
elem: object to remove from self
Raises:
ValueError: if the elem isn't present
"""
old_count = self._dict.get(elem, 0)
if old_count == 0:
raise ValueError
elif old_count == 1:
del self._dict[elem]
else:
self._dict[elem] -= 1
self._size -= 1
def discard_all(self, other):
"""Discard all of the elems from other."""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
for elem, other_count in other._dict.items():
old_count = self._dict.get(elem, 0)
new_count = old_count - other_count
if new_count >= 0:
if new_count == 0:
if elem in self:
del self._dict[elem]
else:
self._dict[elem] = new_count
self._size += new_count - old_count
def remove_all(self, other):
"""Remove all of the elems from other.
Raises a ValueError if the multiplicity of any elem in other is greater
than in self.
"""
if not self._is_superset(other):
raise ValueError
self.discard_all(other)
def clear(self):
"""Remove all elements from this bag."""
self._dict = dict()
self._size = 0
# In-place operations
def __ior__(self, other):
"""Set multiplicity of each element to the maximum of the two collections.
if isinstance(other, _basebag):
This runs in O(other.num_unique_elements())
else:
This runs in O(len(other))
"""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
for elem, other_count in other._dict.items():
old_count = self._dict.get(elem, 0)
new_count = max(other_count, old_count)
self._dict[elem] = new_count
self._size += new_count - old_count
return self
def __iand__(self, other):
"""Set multiplicity of each element to the minimum of the two collections.
if isinstance(other, _basebag):
This runs in O(other.num_unique_elements())
else:
This runs in O(len(other))
"""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
for elem, old_count in set(self._dict.items()):
other_count = other._dict.get(elem, 0)
new_count = min(other_count, old_count)
if new_count == 0:
del self._dict[elem]
else:
self._dict[elem] = new_count
self._size += new_count - old_count
return self
def __ixor__(self, other):
"""Set self to the symmetric difference between the sets.
if isinstance(other, _basebag):
This runs in O(other.num_unique_elements())
else:
This runs in O(len(other))
"""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
other_minus_self = other - self
self -= other
self |= other_minus_self
return self
def __isub__(self, other):
"""Discard the elements of other from self.
if isinstance(it, _basebag):
This runs in O(it.num_unique_elements())
else:
This runs in O(len(it))
"""
self.discard_all(other)
return self
def __iadd__(self, other):
"""Add all of the elements of other to self.
if isinstance(it, _basebag):
This runs in O(it.num_unique_elements())
else:
This runs in O(len(it))
"""
if not isinstance(other, _basebag):
other = self._from_iterable(other)
for elem, other_count in other._dict.items():
self._dict[elem] = self._dict.get(elem, 0) + other_count
self._size += other_count
return self
class frozenbag(_basebag, Hashable):
"""frozenbag is an immutable, hashable bab."""
def __hash__(self):
"""Compute the hash value of a frozenbag.
This was copied directly from _collections_abc.Set._hash in Python3 which
is identical to _abcoll.Set._hash
We can't call it directly because Python2 raises a TypeError.
"""
if not hasattr(self, '_hash_value'):
self._hash_value = self._hash()
return self._hash_value
+94
View File
@@ -0,0 +1,94 @@
"""Class definition for bijection."""
from collections.abc import MutableMapping, Mapping
class bijection(MutableMapping):
"""A one-to-one onto mapping, a dict with unique values."""
def __init__(self, iterable=None, **kwarg):
"""Create a bijection from an iterable.
Matches dict.__init__.
"""
self._data = {}
self.__inverse = self.__new__(bijection)
self.__inverse._data = {}
self.__inverse.__inverse = self
if iterable is not None:
if isinstance(iterable, Mapping):
for key, value in iterable.items():
self[key] = value
else:
for pair in iterable:
self[pair[0]] = pair[1]
for key, value in kwarg.items():
self[key] = value
def __repr__(self):
if len(self._data) == 0:
return '{0}()'.format(self.__class__.__name__)
else:
repr_format = '{class_name}({values!r})'
return repr_format.format(
class_name=self.__class__.__name__,
values=self._data,
)
@property
def inverse(self):
"""Return the inverse of this bijection."""
return self.__inverse
# Required for MutableMapping
def __len__(self):
return len(self._data)
# Required for MutableMapping
def __getitem__(self, key):
return self._data[key]
# Required for MutableMapping
def __setitem__(self, key, value):
if key in self:
del self.inverse._data[self[key]]
if value in self.inverse:
del self._data[self.inverse[value]]
self._data[key] = value
self.inverse._data[value] = key
# Required for MutableMapping
def __delitem__(self, key):
value = self._data.pop(key)
del self.inverse._data[value]
# Required for MutableMapping
def __iter__(self):
return iter(self._data)
def __contains__(self, key):
return key in self._data
def clear(self):
"""Remove everything from this bijection."""
self._data.clear()
self.inverse._data.clear()
def copy(self):
"""Return a copy of this bijection."""
return bijection(self)
def items(self):
"""See Mapping.items."""
return self._data.items()
def keys(self):
"""See Mapping.keys."""
return self._data.keys()
def values(self):
"""See Mapping.values."""
return self.inverse.keys()
def __eq__(self, other):
return isinstance(other, bijection) and self._data == other._data
+385
View File
@@ -0,0 +1,385 @@
"""RangeMap class definition."""
from bisect import bisect_left, bisect_right
from collections import namedtuple
from collections.abc import Mapping, MappingView, Set
# Used to mark unmapped ranges
_empty = object()
MappedRange = namedtuple('MappedRange', ('start', 'stop', 'value'))
class KeysView(MappingView, Set):
"""A view of the keys that mark the starts of subranges.
Since iterating over all the keys is impossible, the KeysView only
contains the keys that start each subrange.
"""
__slots__ = ()
@classmethod
def _from_iterable(self, it):
return set(it)
def __contains__(self, key):
loc = self._mapping._bisect_left(key)
return self._mapping._keys[loc] == key and \
self._mapping._values[loc] is not _empty
def __iter__(self):
for item in self._mapping.ranges():
yield item.start
class ItemsView(MappingView, Set):
"""A view of the items that mark the starts of subranges.
Since iterating over all the keys is impossible, the ItemsView only
contains the items that start each subrange.
"""
__slots__ = ()
@classmethod
def _from_iterable(self, it):
return set(it)
def __contains__(self, item):
key, value = item
loc = self._mapping._bisect_left(key)
return self._mapping._keys[loc] == key and \
self._mapping._values[loc] == value
def __iter__(self):
for mapped_range in self._mapping.ranges():
yield (mapped_range.start, mapped_range.value)
class ValuesView(MappingView):
"""A view on the values of a Mapping."""
__slots__ = ()
def __contains__(self, value):
return value in self._mapping._values
def __iter__(self):
for value in self._mapping._values:
if value is not _empty:
yield value
def _check_start_stop(start, stop):
"""Check that start and stop are valid - orderable and in the right order.
Raises:
ValueError: if stop <= start
TypeError: if unorderable
"""
if start is not None and stop is not None and stop <= start:
raise ValueError('stop must be > start')
def _check_key_slice(key):
if not isinstance(key, slice):
raise TypeError('Can only set and delete slices')
if key.step is not None:
raise ValueError('Cannot set or delete slices with steps')
class RangeMap(Mapping):
"""Map ranges of orderable elements to values."""
def __init__(self, iterable=None, **kwargs):
"""Create a RangeMap.
A mapping or other iterable can be passed to initialize the RangeMap.
If mapping is passed, it is interpreted as a mapping from range start
indices to values.
If an iterable is passed, each element will define a range in the
RangeMap and should be formatted (start, stop, value).
default_value is a an optional keyword argument that will initialize the
entire RangeMap to that value. Any missing ranges will be mapped to that
value. However, if ranges are subsequently deleted they will be removed
and *not* mapped to the default_value.
Args:
iterable: A Mapping or an Iterable to initialize from.
default_value: If passed, the return value for all keys less than the
least key in mapping or missing ranges in iterable. If no mapping
or iterable, the return value for all keys.
"""
default_value = kwargs.pop('default_value', _empty)
if kwargs:
raise TypeError('Unknown keyword arguments: %s' % ', '.join(kwargs.keys()))
self._keys = [None]
self._values = [default_value]
if iterable:
if isinstance(iterable, Mapping):
self._init_from_mapping(iterable)
else:
self._init_from_iterable(iterable)
@classmethod
def from_mapping(cls, mapping):
"""Create a RangeMap from a mapping of interval starts to values."""
obj = cls()
obj._init_from_mapping(mapping)
return obj
def _init_from_mapping(self, mapping):
for key, value in sorted(mapping.items()):
self.set(value, key)
@classmethod
def from_iterable(cls, iterable):
"""Create a RangeMap from an iterable of tuples defining each range.
Each element of the iterable is a tuple (start, stop, value).
"""
obj = cls()
obj._init_from_iterable(iterable)
return obj
def _init_from_iterable(self, iterable):
for start, stop, value in iterable:
self.set(value, start=start, stop=stop)
def __str__(self):
range_format = '({range.start}, {range.stop}): {range.value}'
values = ', '.join([range_format.format(range=r) for r in self.ranges()])
return 'RangeMap(%s)' % values
def __repr__(self):
range_format = '({range.start!r}, {range.stop!r}, {range.value!r})'
values = ', '.join([range_format.format(range=r) for r in self.ranges()])
return 'RangeMap([%s])' % values
def _bisect_left(self, key):
"""Return the index of the key or the last key < key."""
if key is None:
return 0
else:
return bisect_left(self._keys, key, lo=1)
def _bisect_right(self, key):
"""Return the index of the first key > key."""
if key is None:
return 1
else:
return bisect_right(self._keys, key, lo=1)
def ranges(self, start=None, stop=None):
"""Generate MappedRanges for all mapped ranges.
Yields:
MappedRange
"""
_check_start_stop(start, stop)
start_loc = self._bisect_right(start)
if stop is None:
stop_loc = len(self._keys)
else:
stop_loc = self._bisect_left(stop)
start_val = self._values[start_loc - 1]
candidate_keys = [start] + self._keys[start_loc:stop_loc] + [stop]
candidate_values = [start_val] + self._values[start_loc:stop_loc]
for i, value in enumerate(candidate_values):
if value is not _empty:
start_key = candidate_keys[i]
stop_key = candidate_keys[i + 1]
yield MappedRange(start_key, stop_key, value)
def __contains__(self, value):
try:
self.__getitem(value) is not _empty
except KeyError:
return False
else:
return True
def __iter__(self):
for key, value in zip(self._keys, self._values):
if value is not _empty:
yield key
def __bool__(self):
if len(self._keys) > 1:
return True
else:
return self._values[0] != _empty
__nonzero__ = __bool__
def __getitem(self, key):
"""Get the value for a key (not a slice)."""
loc = self._bisect_right(key) - 1
value = self._values[loc]
if value is _empty:
raise KeyError(key)
else:
return value
def get(self, key, restval=None):
"""Get the value of the range containing key, otherwise return restval."""
try:
return self.__getitem(key)
except KeyError:
return restval
def get_range(self, start=None, stop=None):
"""Return a RangeMap for the range start to stop.
Returns:
A RangeMap
"""
return self.from_iterable(self.ranges(start, stop))
def set(self, value, start=None, stop=None):
"""Set the range from start to stop to value."""
_check_start_stop(start, stop)
# start_index, stop_index will denote the sections we are replacing
start_index = self._bisect_left(start)
if start is not None: # start_index == 0
prev_value = self._values[start_index - 1]
if prev_value == value:
# We're setting a range where the left range has the same
# value, so create one big range
start_index -= 1
start = self._keys[start_index]
if stop is None:
new_keys = [start]
new_values = [value]
stop_index = len(self._keys)
else:
stop_index = self._bisect_right(stop)
stop_value = self._values[stop_index - 1]
stop_key = self._keys[stop_index - 1]
if stop_key == stop and stop_value == value:
new_keys = [start]
new_values = [value]
else:
new_keys = [start, stop]
new_values = [value, stop_value]
self._keys[start_index:stop_index] = new_keys
self._values[start_index:stop_index] = new_values
def delete(self, start=None, stop=None):
"""Delete the range from start to stop from self.
Raises:
KeyError: If part of the passed range isn't mapped.
"""
_check_start_stop(start, stop)
start_loc = self._bisect_right(start) - 1
if stop is None:
stop_loc = len(self._keys)
else:
stop_loc = self._bisect_left(stop)
for value in self._values[start_loc:stop_loc]:
if value is _empty:
raise KeyError((start, stop))
# this is inefficient, we've already found the sub ranges
self.set(_empty, start=start, stop=stop)
def empty(self, start=None, stop=None):
"""Empty the range from start to stop.
Like delete, but no Error is raised if the entire range isn't mapped.
"""
self.set(_empty, start=start, stop=stop)
def clear(self):
"""Remove all elements."""
self._keys = [None]
self._values = [_empty]
@property
def start(self):
"""Get the start key of the first range.
None if RangeMap is empty or unbounded to the left.
"""
if self._values[0] is _empty:
try:
return self._keys[1]
except IndexError:
# This is empty or everything is mapped to a single value
return None
else:
# This is unbounded to the left
return self._keys[0]
@property
def end(self):
"""Get the stop key of the last range.
None if RangeMap is empty or unbounded to the right.
"""
if self._values[-1] is _empty:
return self._keys[-1]
else:
# This is unbounded to the right
return None
def __eq__(self, other):
if isinstance(other, RangeMap):
return (
self._keys == other._keys and
self._values == other._values
)
else:
return False
def __getitem__(self, key):
try:
_check_key_slice(key)
except TypeError:
return self.__getitem(key)
else:
return self.get_range(key.start, key.stop)
def __setitem__(self, key, value):
_check_key_slice(key)
self.set(value, key.start, key.stop)
def __delitem__(self, key):
_check_key_slice(key)
self.delete(key.start, key.stop)
def __len__(self):
count = 0
for v in self._values:
if v is not _empty:
count += 1
return count
def keys(self):
"""Return a view of the keys."""
return KeysView(self)
def values(self):
"""Return a view of the values."""
return ValuesView(self)
def items(self):
"""Return a view of the item pairs."""
return ItemsView(self)
# Python2 - override slice methods
def __setslice__(self, i, j, value):
"""Implement __setslice__ to override behavior in Python 2.
This is required because empty slices pass integers in python2 as opposed
to None in python 3.
"""
raise SyntaxError('Assigning slices doesn\t work in Python 2, use set')
def __delslice__(self, i, j):
raise SyntaxError('Deleting slices doesn\t work in Python 2, use delete')
def __getslice__(self, i, j):
raise SyntaxError('Getting slices doesn\t work in Python 2, use get_range.')
+552
View File
@@ -0,0 +1,552 @@
"""Setlist class definitions."""
import random as random_
from collections.abc import (
Sequence,
Set,
MutableSequence,
MutableSet,
Hashable,
)
from . import _util
class _basesetlist(Sequence, Set):
"""A setlist is an ordered Collection of unique elements.
_basesetlist is the superclass of setlist and frozensetlist. It is immutable
and unhashable.
"""
def __init__(self, iterable=None, raise_on_duplicate=False):
"""Create a setlist.
Args:
iterable (Iterable): Values to initialize the setlist with.
"""
self._list = list()
self._dict = dict()
if iterable:
if raise_on_duplicate:
self._extend(iterable)
else:
self._update(iterable)
def __repr__(self):
if len(self) == 0:
return '{0}()'.format(self.__class__.__name__)
else:
repr_format = '{class_name}({values!r})'
return repr_format.format(
class_name=self.__class__.__name__,
values=tuple(self),
)
# Convenience methods
def _fix_neg_index(self, index):
if index < 0:
index += len(self)
if index < 0:
raise IndexError('index is out of range')
return index
def _fix_end_index(self, index):
if index is None:
return len(self)
else:
return self._fix_neg_index(index)
def _append(self, value):
# Checking value in self will check that value is Hashable
if value in self:
raise ValueError('Value "%s" already present' % str(value))
else:
self._dict[value] = len(self)
self._list.append(value)
def _extend(self, values):
new_values = set()
for value in values:
if value in new_values:
raise ValueError('New values contain duplicates')
elif value in self:
raise ValueError('New values contain elements already present in self')
else:
new_values.add(value)
for value in values:
self._dict[value] = len(self)
self._list.append(value)
def _add(self, item):
if item not in self:
self._dict[item] = len(self)
self._list.append(item)
def _update(self, values):
for value in values:
if value not in self:
self._dict[value] = len(self)
self._list.append(value)
@classmethod
def _from_iterable(cls, it, **kwargs):
return cls(it, **kwargs)
# Implement Container
def __contains__(self, value):
return value in self._dict
# Iterable we get by inheriting from Sequence
# Implement Sized
def __len__(self):
return len(self._list)
# Implement Sequence
def __getitem__(self, index):
if isinstance(index, slice):
return self._from_iterable(self._list[index])
return self._list[index]
def count(self, value):
"""Return the number of occurences of value in self.
This runs in O(1)
Args:
value: The value to count
Returns:
int: 1 if the value is in the setlist, otherwise 0
"""
if value in self:
return 1
else:
return 0
def index(self, value, start=0, end=None):
"""Return the index of value between start and end.
By default, the entire setlist is searched.
This runs in O(1)
Args:
value: The value to find the index of
start (int): The index to start searching at (defaults to 0)
end (int): The index to stop searching at (defaults to the end of the list)
Returns:
int: The index of the value
Raises:
ValueError: If the value is not in the list or outside of start - end
IndexError: If start or end are out of range
"""
try:
index = self._dict[value]
except KeyError:
raise ValueError
else:
start = self._fix_neg_index(start)
end = self._fix_end_index(end)
if start <= index and index < end:
return index
else:
raise ValueError
@classmethod
def _check_type(cls, other, operand_name):
if not isinstance(other, _basesetlist):
message = (
"unsupported operand type(s) for {operand_name}: "
"'{self_type}' and '{other_type}'").format(
operand_name=operand_name,
self_type=cls,
other_type=type(other),
)
raise TypeError(message)
def __add__(self, other):
self._check_type(other, '+')
out = self.copy()
out._extend(other)
return out
# Implement Set
def issubset(self, other):
return self <= other
def issuperset(self, other):
return self >= other
def union(self, other):
out = self.copy()
out.update(other)
return out
def intersection(self, other):
other = set(other)
return self._from_iterable(item for item in self if item in other)
def difference(self, other):
other = set(other)
return self._from_iterable(item for item in self if item not in other)
def symmetric_difference(self, other):
return self.union(other) - self.intersection(other)
def __sub__(self, other):
self._check_type(other, '-')
return self.difference(other)
def __and__(self, other):
self._check_type(other, '&')
return self.intersection(other)
def __or__(self, other):
self._check_type(other, '|')
return self.union(other)
def __xor__(self, other):
self._check_type(other, '^')
return self.symmetric_difference(other)
# Comparison
def __eq__(self, other):
if not isinstance(other, _basesetlist):
return False
if not len(self) == len(other):
return False
for self_elem, other_elem in zip(self, other):
if self_elem != other_elem:
return False
return True
def __ne__(self, other):
return not (self == other)
# New methods
def sub_index(self, sub, start=0, end=None):
"""Return the index of a subsequence.
This runs in O(len(sub))
Args:
sub (Sequence): An Iterable to search for
Returns:
int: The index of the first element of sub
Raises:
ValueError: If sub isn't a subsequence
TypeError: If sub isn't iterable
IndexError: If start or end are out of range
"""
start_index = self.index(sub[0], start, end)
end = self._fix_end_index(end)
if start_index + len(sub) > end:
raise ValueError
for i in range(1, len(sub)):
if sub[i] != self[start_index + i]:
raise ValueError
return start_index
def copy(self):
return self.__class__(self)
class setlist(_basesetlist, MutableSequence, MutableSet):
"""A mutable (unhashable) setlist."""
def __str__(self):
return '{[%s}]' % ', '.join(repr(v) for v in self)
# Helper methods
def _delete_all(self, elems_to_delete, raise_errors):
indices_to_delete = set()
for elem in elems_to_delete:
try:
elem_index = self._dict[elem]
except KeyError:
if raise_errors:
raise ValueError('Passed values contain elements not in self')
else:
if elem_index in indices_to_delete:
if raise_errors:
raise ValueError('Passed vales contain duplicates')
indices_to_delete.add(elem_index)
self._delete_values_by_index(indices_to_delete)
def _delete_values_by_index(self, indices_to_delete):
deleted_count = 0
for i, elem in enumerate(self._list):
if i in indices_to_delete:
deleted_count += 1
del self._dict[elem]
else:
new_index = i - deleted_count
self._list[new_index] = elem
self._dict[elem] = new_index
# Now remove deleted_count items from the end of the list
if deleted_count:
self._list = self._list[:-deleted_count]
# Set/Sequence agnostic
def pop(self, index=-1):
"""Remove and return the item at index."""
value = self._list.pop(index)
del self._dict[value]
return value
def clear(self):
"""Remove all elements from self."""
self._dict = dict()
self._list = list()
# Implement MutableSequence
def __setitem__(self, index, value):
if isinstance(index, slice):
old_values = self[index]
for v in value:
if v in self and v not in old_values:
raise ValueError
self._list[index] = value
self._dict = {}
for i, v in enumerate(self._list):
self._dict[v] = i
else:
index = self._fix_neg_index(index)
old_value = self._list[index]
if value in self:
if value == old_value:
return
else:
raise ValueError
del self._dict[old_value]
self._list[index] = value
self._dict[value] = index
def __delitem__(self, index):
if isinstance(index, slice):
indices_to_delete = set(self.index(e) for e in self._list[index])
self._delete_values_by_index(indices_to_delete)
else:
index = self._fix_neg_index(index)
value = self._list[index]
del self._dict[value]
for elem in self._list[index + 1:]:
self._dict[elem] -= 1
del self._list[index]
def insert(self, index, value):
"""Insert value at index.
Args:
index (int): Index to insert value at
value: Value to insert
Raises:
ValueError: If value already in self
IndexError: If start or end are out of range
"""
if value in self:
raise ValueError
index = self._fix_neg_index(index)
self._dict[value] = index
for elem in self._list[index:]:
self._dict[elem] += 1
self._list.insert(index, value)
def append(self, value):
"""Append value to the end.
Args:
value: Value to append
Raises:
ValueError: If value alread in self
TypeError: If value isn't hashable
"""
self._append(value)
def extend(self, values):
"""Append all values to the end.
If any of the values are present, ValueError will
be raised and none of the values will be appended.
Args:
values (Iterable): Values to append
Raises:
ValueError: If any values are already present or there are duplicates
in the passed values.
TypeError: If any of the values aren't hashable.
"""
self._extend(values)
def __iadd__(self, values):
"""Add all values to the end of self.
Args:
values (Iterable): Values to append
Raises:
ValueError: If any values are already present
"""
self._check_type(values, '+=')
self.extend(values)
return self
def remove(self, value):
"""Remove value from self.
Args:
value: Element to remove from self
Raises:
ValueError: if element is already present
"""
try:
index = self._dict[value]
except KeyError:
raise ValueError('Value "%s" is not present.')
else:
del self[index]
def remove_all(self, elems_to_delete):
"""Remove all elements from elems_to_delete, raises ValueErrors.
See Also:
discard_all
Args:
elems_to_delete (Iterable): Elements to remove.
Raises:
ValueError: If the count of any element is greater in
elems_to_delete than self.
TypeError: If any of the values aren't hashable.
"""
self._delete_all(elems_to_delete, raise_errors=True)
# Implement MutableSet
def add(self, item):
"""Add an item.
Note:
This does not raise a ValueError for an already present value like
append does. This is to match the behavior of set.add
Args:
item: Item to add
Raises:
TypeError: If item isn't hashable.
"""
self._add(item)
def update(self, values):
"""Add all values to the end.
If any of the values are present, silently ignore
them (as opposed to extend which raises an Error).
See also:
extend
Args:
values (Iterable): Values to add
Raises:
TypeError: If any of the values are unhashable.
"""
self._update(values)
def discard_all(self, elems_to_delete):
"""Discard all the elements from elems_to_delete.
This is much faster than removing them one by one.
This runs in O(len(self) + len(elems_to_delete))
Args:
elems_to_delete (Iterable): Elements to discard.
Raises:
TypeError: If any of the values aren't hashable.
"""
self._delete_all(elems_to_delete, raise_errors=False)
def discard(self, value):
"""Discard an item.
Note:
This does not raise a ValueError for a missing value like remove does.
This is to match the behavior of set.discard
"""
try:
self.remove(value)
except ValueError:
pass
def difference_update(self, other):
"""Update self to include only the differene with other."""
other = set(other)
indices_to_delete = set()
for i, elem in enumerate(self):
if elem in other:
indices_to_delete.add(i)
if indices_to_delete:
self._delete_values_by_index(indices_to_delete)
def intersection_update(self, other):
"""Update self to include only the intersection with other."""
other = set(other)
indices_to_delete = set()
for i, elem in enumerate(self):
if elem not in other:
indices_to_delete.add(i)
if indices_to_delete:
self._delete_values_by_index(indices_to_delete)
def symmetric_difference_update(self, other):
"""Update self to include only the symmetric difference with other."""
other = setlist(other)
indices_to_delete = set()
for i, item in enumerate(self):
if item in other:
indices_to_delete.add(i)
for item in other:
self.add(item)
self._delete_values_by_index(indices_to_delete)
def __isub__(self, other):
self._check_type(other, '-=')
self.difference_update(other)
return self
def __iand__(self, other):
self._check_type(other, '&=')
self.intersection_update(other)
return self
def __ior__(self, other):
self._check_type(other, '|=')
self.update(other)
return self
def __ixor__(self, other):
self._check_type(other, '^=')
self.symmetric_difference_update(other)
return self
# New methods
def shuffle(self, random=None):
"""Shuffle all of the elements in self randomly."""
random_.shuffle(self._list, random=random)
for i, elem in enumerate(self._list):
self._dict[elem] = i
def sort(self, *args, **kwargs):
"""Sort this setlist in place."""
self._list.sort(*args, **kwargs)
for index, value in enumerate(self._list):
self._dict[value] = index
class frozensetlist(_basesetlist, Hashable):
"""An immutable (hashable) setlist."""
def __hash__(self):
if not hasattr(self, '_hash_value'):
self._hash_value = _util.hash_iterable(self)
return self._hash_value
+28
View File
@@ -0,0 +1,28 @@
version: '{build}'
pull_requests:
do_not_increment_build_number: true
environment:
ProjectVersion: build$(APPVEYOR_BUILD_VERSION)
matrix:
- PYTHON: C:\PYTHON36
install:
- ps: 'if(Test-Path env:APPVEYOR_REPO_TAG_NAME) {$env:ProjectVersion=$env:APPVEYOR_REPO_TAG_NAME}'
- '%PYTHON%\python.exe --version'
- '%PYTHON%\Scripts\pip install pyinstaller'
- '%PYTHON%\Scripts\pip install markdown'
- '%PYTHON%\python.exe -m markdown README.md > README.html'
- '%PYTHON%\Scripts\pyinstaller bundle\EntranceRandomizer.spec'
- 'mkdir dist\EntranceRandomizer\ext'
- 'move dist\EntranceRandomizer\*.pyd dist\EntranceRandomizer\ext'
- 'move dist\EntranceRandomizer\tcl*.dll dist\EntranceRandomizer\ext'
- 'move dist\EntranceRandomizer\tk*.dll dist\EntranceRandomizer\ext'
- ps: '$env:ER_Version= &"$env:PYTHON\python.exe" -c "import Main; import re; print(re.match(''[0-9]+\\.[0-9]+\\.[0-9]+'',Main.__version__).group(0))"'
- '"%WIX%\bin\heat.exe" dir "dist\EntranceRandomizer" -sfrag -srd -suid -dr INSTALLDIR -cg ERFiles -ag -template fragment -t bundle\components.xslt -out build\components.wxs'
- '"%WIX%\bin\candle.exe" -out build\ bundle\*.wxs build\*.wxs'
- '"%WIX%\bin\light.exe" -ext WixUIExtension build\*.wixobj -o dist\EntranceRandomizer-Installer-%ProjectVersion%-win32.msi -b dist\EntranceRandomizer'
build: off
artifacts:
- path: dist/EntranceRandomizer*.msi
name: EntranceRandomizer-Installer-$(ProjectVersion)-win32.msi
- path: dist/EntranceRandomizer/
name: EntranceRandomizer-Raw-$(ProjectVersion)-win32.zip
+1 -1
View File
@@ -1,4 +1,4 @@
import os
import sys
import os
sys.path.append(os.path.join(sys._MEIPASS, "ext"))
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

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