Merge unstable into CrossGen
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
# workflow name
|
||||
name: Build
|
||||
|
||||
# fire on
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- DoorDev
|
||||
pull_request:
|
||||
branches:
|
||||
- DoorDev
|
||||
|
||||
# stuff to do
|
||||
jobs:
|
||||
# Install & Build
|
||||
# Set up environment
|
||||
# Build
|
||||
# Run build-gui.py
|
||||
# Run build-dr.py
|
||||
install-build:
|
||||
name: Install/Build
|
||||
# cycle through os list
|
||||
runs-on: ${{ matrix.os-name }}
|
||||
|
||||
# VM settings
|
||||
# os & python versions
|
||||
strategy:
|
||||
matrix:
|
||||
os-name: [ ubuntu-latest, ubuntu-16.04, macOS-latest, windows-latest ]
|
||||
python-version: [ 3.7 ]
|
||||
# needs: [ install-test ]
|
||||
steps:
|
||||
# checkout commit
|
||||
- name: Checkout commit
|
||||
uses: actions/checkout@v1
|
||||
# install python
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: "x64"
|
||||
- run: |
|
||||
python --version
|
||||
# install dependencies via pip
|
||||
- name: Install dependencies via pip
|
||||
env:
|
||||
OS_NAME: ${{ matrix.os-name }}
|
||||
run: |
|
||||
python ./resources/ci/common/install.py
|
||||
pip install pyinstaller
|
||||
# try to get UPX
|
||||
- name: Get UPX
|
||||
env:
|
||||
OS_NAME: ${{ matrix.os-name }}
|
||||
run: |
|
||||
python ./resources/ci/common/get_upx.py
|
||||
# run build-gui.py
|
||||
- name: Build GUI
|
||||
run: |
|
||||
python ./build-gui.py
|
||||
# run build-dr.py
|
||||
- name: Build DungeonRandomizer
|
||||
run: |
|
||||
python ./build-dr.py
|
||||
# prepare binary artifacts for later step
|
||||
- name: Prepare Binary Artifacts
|
||||
env:
|
||||
OS_NAME: ${{ matrix.os-name }}
|
||||
run: |
|
||||
python ./resources/ci/common/prepare_binary.py
|
||||
# upload binary artifacts for later step
|
||||
- name: Upload Binary Artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: binaries-${{ matrix.os-name }}
|
||||
path: ../artifact
|
||||
|
||||
# Install & Preparing Release
|
||||
# Set up environment
|
||||
# Local Prepare Release action
|
||||
install-prepare-release:
|
||||
name: Install/Prepare Release
|
||||
# cycle through os list
|
||||
runs-on: ${{ matrix.os-name }}
|
||||
|
||||
# VM settings
|
||||
# os & python versions
|
||||
strategy:
|
||||
matrix:
|
||||
# install/release on not xenial
|
||||
os-name: [ ubuntu-latest, macOS-latest, windows-latest ]
|
||||
python-version: [ 3.7 ]
|
||||
|
||||
needs: [ install-build ]
|
||||
steps:
|
||||
# checkout commit
|
||||
- name: Checkout commit
|
||||
uses: actions/checkout@v1
|
||||
# install python
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: "x64"
|
||||
- run: |
|
||||
python --version
|
||||
# install dependencies via pip
|
||||
- name: Install Dependencies via pip
|
||||
env:
|
||||
OS_NAME: ${{ matrix.os-name }}
|
||||
run: |
|
||||
python ./resources/ci/common/install.py
|
||||
# download binary artifact
|
||||
- name: Download Binary Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: binaries-${{ matrix.os-name }}
|
||||
path: ./
|
||||
# Prepare AppVersion & Release
|
||||
- name: Prepare AppVersion & Release
|
||||
env:
|
||||
OS_NAME: ${{ matrix.os-name }}
|
||||
run: |
|
||||
python ./build-app_version.py
|
||||
python ./resources/ci/common/prepare_appversion.py
|
||||
python ./resources/ci/common/prepare_release.py
|
||||
# upload appversion artifact for later step
|
||||
- name: Upload AppVersion Artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: appversion-${{ matrix.os-name }}
|
||||
path: ./resources/app/meta/manifests/app_version.txt
|
||||
# upload archive artifact for later step
|
||||
- name: Upload Archive Artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: archive-${{ matrix.os-name }}
|
||||
path: ../deploy
|
||||
|
||||
# Deploy to GitHub Releases
|
||||
# Release Name: ALttPDoorRandomizer v${GITHUB_TAG}
|
||||
# Release Body: Inline content of RELEASENOTES.md
|
||||
# Release Body: Fallback to URL to RELEASENOTES.md
|
||||
# Release Files: ../deploy
|
||||
deploy-release:
|
||||
name: Deploy GHReleases
|
||||
runs-on: ${{ matrix.os-name }}
|
||||
|
||||
# VM settings
|
||||
# os & python versions
|
||||
strategy:
|
||||
matrix:
|
||||
# release only on bionic
|
||||
os-name: [ ubuntu-latest ]
|
||||
python-version: [ 3.7 ]
|
||||
|
||||
needs: [ install-prepare-release ]
|
||||
steps:
|
||||
# checkout commit
|
||||
- name: Checkout commit
|
||||
uses: actions/checkout@v1
|
||||
- name: Install Dependencies via pip
|
||||
run: |
|
||||
python -m pip install pytz requests
|
||||
# download appversion artifact
|
||||
- name: Download AppVersion Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: appversion-${{ matrix.os-name }}
|
||||
path: ../build
|
||||
# download ubuntu archive artifact
|
||||
- name: Download Ubuntu Archive Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: archive-ubuntu-latest
|
||||
path: ../deploy/linux
|
||||
# download macos archive artifact
|
||||
- name: Download MacOS Archive Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: archive-macOS-latest
|
||||
path: ../deploy/macos
|
||||
# download windows archive artifact
|
||||
- name: Download Windows Archive Artifact
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: archive-windows-latest
|
||||
path: ../deploy/windows
|
||||
# debug info
|
||||
- name: Debug Info
|
||||
id: debug_info
|
||||
# shell: bash
|
||||
# git tag ${GITHUB_TAG}
|
||||
# git push origin ${GITHUB_TAG}
|
||||
run: |
|
||||
GITHUB_TAG="$(head -n 1 ../build/app_version.txt)"
|
||||
echo "::set-output name=github_tag::$GITHUB_TAG"
|
||||
GITHUB_TAG="v${GITHUB_TAG}"
|
||||
RELEASE_NAME="ALttPDoorRandomizer ${GITHUB_TAG}"
|
||||
echo "Release Name: ${RELEASE_NAME}"
|
||||
echo "Git Tag: ${GITHUB_TAG}"
|
||||
# read releasenotes
|
||||
- name: Read RELEASENOTES
|
||||
id: release_notes
|
||||
run: |
|
||||
body="$(cat RELEASENOTES.md)"
|
||||
body="${body//'%'/'%25'}"
|
||||
body="${body//$'\n'/'%0A'}"
|
||||
body="${body//$'\r'/'%0D'}"
|
||||
echo "::set-output name=body::$body"
|
||||
# create a pre/release
|
||||
- name: Create a Pre/Release
|
||||
id: create_release
|
||||
uses: actions/create-release@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: v${{ steps.debug_info.outputs.github_tag }}
|
||||
release_name: ALttPDoorRandomizer v${{ steps.debug_info.outputs.github_tag }}
|
||||
body: ${{ steps.release_notes.outputs.body }}
|
||||
draft: true
|
||||
prerelease: true
|
||||
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
|
||||
# upload linux archive asset
|
||||
- name: Upload Linux Archive Asset
|
||||
id: upload-linux-asset
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../deploy/linux/ALttPDoorRandomizer.tar.gz
|
||||
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-bionic.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
|
||||
# upload macos archive asset
|
||||
- name: Upload MacOS Archive Asset
|
||||
id: upload-macos-asset
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz
|
||||
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz
|
||||
asset_content_type: application/gzip
|
||||
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
|
||||
# upload windows archive asset
|
||||
- name: Upload Windows Archive Asset
|
||||
id: upload-windows-asset
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ../deploy/windows/ALttPDoorRandomizer.zip
|
||||
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-windows.zip
|
||||
asset_content_type: application/zip
|
||||
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
|
||||
+8
-2
@@ -1,6 +1,9 @@
|
||||
.idea
|
||||
.vscode
|
||||
*_Spoiler.txt
|
||||
*.bmbp
|
||||
*.log
|
||||
*_Spoiler.json
|
||||
*.pyc
|
||||
*.sfc
|
||||
*.srm
|
||||
@@ -18,9 +21,12 @@ EnemizerCLI/
|
||||
RaceRom.py
|
||||
upx/
|
||||
weights/
|
||||
/MultiMystery/
|
||||
/Players/
|
||||
/QUsb2Snes/
|
||||
|
||||
settings.json
|
||||
working_dirs.json
|
||||
resources/user/*
|
||||
!resources/user/.gitkeep
|
||||
|
||||
*.exe
|
||||
|
||||
|
||||
+41
-18
@@ -4,6 +4,7 @@ import logging
|
||||
import json
|
||||
from collections import OrderedDict, deque, defaultdict
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
from EntranceShuffle import door_addresses
|
||||
from _vendor.collections_extended import bag
|
||||
from Utils import int16_as_bytes
|
||||
@@ -71,8 +72,13 @@ class World(object):
|
||||
self.key_logic = {}
|
||||
self.pool_adjustment = {}
|
||||
self.key_layout = defaultdict(dict)
|
||||
self.fish = BabelFish()
|
||||
|
||||
for player in range(1, players + 1):
|
||||
# If World State is Retro, set to Open and set Retro flag
|
||||
if self.mode[player] == "retro":
|
||||
self.mode[player] = "open"
|
||||
self.retro[player] = True
|
||||
def set_player_attr(attr, val):
|
||||
self.__dict__.setdefault(attr, {})[player] = val
|
||||
set_player_attr('_region_cache', {})
|
||||
@@ -1774,43 +1780,60 @@ class Spoiler(object):
|
||||
outfile.write('\n\nDoors:\n\n')
|
||||
outfile.write('\n'.join(
|
||||
['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '',
|
||||
entry['entrance'],
|
||||
self.world.fish.translate("meta","doors",entry['entrance']),
|
||||
'<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>',
|
||||
entry['exit'],
|
||||
self.world.fish.translate("meta","doors",entry['exit']),
|
||||
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] == 'crossed' else '') for
|
||||
entry in self.doors.values()]))
|
||||
if self.doorTypes:
|
||||
# doorNames: For some reason these come in combined, somehow need to split on the thing to translate
|
||||
# doorTypes: Small Key, Bombable, Bonkable
|
||||
outfile.write('\n\nDoor Types:\n\n')
|
||||
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['doorNames'], entry['type']) for entry in self.doorTypes.values()]))
|
||||
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', self.world.fish.translate("meta","doors",entry['doorNames']), self.world.fish.translate("meta","doorTypes",entry['type'])) for entry in self.doorTypes.values()]))
|
||||
if self.entrances:
|
||||
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
|
||||
outfile.write('\n\nEntrances:\n\n')
|
||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()]))
|
||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","entrances",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","entrances",entry['exit'])) for entry in self.entrances.values()]))
|
||||
outfile.write('\n\nMedallions:\n')
|
||||
for dungeon, medallion in self.medallions.items():
|
||||
outfile.write(f'\n{dungeon}: {medallion}')
|
||||
outfile.write(f'\n{dungeon}: {medallion} Medallion')
|
||||
if self.startinventory:
|
||||
outfile.write('\n\nStarting Inventory:\n\n')
|
||||
outfile.write('\n'.join(self.startinventory))
|
||||
outfile.write('\n\nLocations:\n\n')
|
||||
outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
||||
outfile.write('\n\nShops:\n\n')
|
||||
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
|
||||
outfile.write('\n\nPlaythrough:\n\n')
|
||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||
if self.unreachables:
|
||||
outfile.write('\n\nUnreachable Items:\n\n')
|
||||
outfile.write('\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables]))
|
||||
outfile.write('\n\nPaths:\n\n')
|
||||
|
||||
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||
# items: Item names
|
||||
outfile.write('\n\nLocations:\n\n')
|
||||
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","locations",location), self.world.fish.translate("meta","items",item)) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
||||
|
||||
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||
# items: Item names
|
||||
outfile.write('\n\nShops:\n\n')
|
||||
outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta","locations",shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta","items",item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
|
||||
|
||||
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||
# items: Item names
|
||||
outfile.write('\n\nPlaythrough:\n\n')
|
||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (self.world.fish.translate("meta","locations",location), self.world.fish.translate("meta","items",item)) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||
if self.unreachables:
|
||||
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||
# items: Item names
|
||||
outfile.write('\n\nUnreachable Items:\n\n')
|
||||
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","items",unreachable.item), self.world.fish.translate("meta","locations",unreachable)) for unreachable in self.unreachables]))
|
||||
|
||||
# rooms: Change up room names; only if it's got no locations in it
|
||||
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
|
||||
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||
outfile.write('\n\nPaths:\n\n')
|
||||
path_listings = []
|
||||
for location, path in sorted(self.paths.items()):
|
||||
path_lines = []
|
||||
for region, exit in path:
|
||||
if exit is not None:
|
||||
path_lines.append("{} -> {}".format(region, exit))
|
||||
path_lines.append("{} -> {}".format(self.world.fish.translate("meta","rooms",region), self.world.fish.translate("meta","entrances",exit)))
|
||||
else:
|
||||
path_lines.append(region)
|
||||
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
|
||||
path_lines.append(self.world.fish.translate("meta","rooms",region))
|
||||
path_listings.append("{}\n {}".format(self.world.fish.translate("meta","locations",location), "\n => ".join(path_lines)))
|
||||
|
||||
outfile.write('\n'.join(path_listings))
|
||||
|
||||
|
||||
@@ -8,11 +8,10 @@ import textwrap
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from Main import main
|
||||
from Utils import is_bundled, close_console
|
||||
from Fill import FillError
|
||||
import source.classes.constants as CONST
|
||||
from source.classes.BabelFish import BabelFish
|
||||
|
||||
import classes.constants as CONST
|
||||
from Utils import update_deprecated_args
|
||||
|
||||
|
||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||
@@ -20,12 +19,15 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||
def _get_help_string(self, action):
|
||||
return textwrap.dedent(action.help)
|
||||
|
||||
def parse_arguments(argv, no_defaults=False):
|
||||
def parse_cli(argv, no_defaults=False):
|
||||
def defval(value):
|
||||
return value if not no_defaults else None
|
||||
|
||||
# get settings
|
||||
settings = get_settings()
|
||||
settings = parse_settings()
|
||||
|
||||
lang = "en"
|
||||
fish = BabelFish(lang=lang)
|
||||
|
||||
# we need to know how many players we have first
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
@@ -33,265 +35,45 @@ def parse_arguments(argv, no_defaults=False):
|
||||
multiargs, _ = parser.parse_known_args(argv)
|
||||
|
||||
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--create_spoiler', default=defval(settings["create_spoiler"] != 0), help='Output a Spoiler File', action='store_true')
|
||||
parser.add_argument('--logic', default=defval(settings["logic"]), 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=defval(settings["mode"]), 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=defval(settings["swords"]), 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=defval(settings["goal"]), const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'],
|
||||
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.
|
||||
Triforce Hunt: Places 30 Triforce Pieces in the world, collect
|
||||
20 of them to beat the game.
|
||||
''')
|
||||
parser.add_argument('--difficulty', default=defval(settings["difficulty"]), 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=defval(settings["item_functionality"]), 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=defval(settings["timer"]), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'],
|
||||
help='''\
|
||||
Select game timer setting. Affects available itempool. (default: %(default)s)
|
||||
None: No timer.
|
||||
Display: Displays a timer but does not affect
|
||||
the itempool.
|
||||
Timed: Starts with clock at zero. Green Clocks
|
||||
subtract 4 minutes (Total: 20), Blue Clocks
|
||||
subtract 2 minutes (Total: 10), Red Clocks add
|
||||
2 minutes (Total: 10). Winner is player with
|
||||
lowest time at the end.
|
||||
Timed OHKO: Starts clock at 10 minutes. Green Clocks add
|
||||
5 minutes (Total: 25). As long as clock is at 0,
|
||||
Link will die in one hit.
|
||||
OHKO: Like Timed OHKO, but no clock items are present
|
||||
and the clock is permenantly at zero.
|
||||
Timed Countdown: Starts with clock at 40 minutes. Same clocks as
|
||||
Timed mode. If time runs out, you lose (but can
|
||||
still keep playing).
|
||||
''')
|
||||
parser.add_argument('--progressive', default=defval(settings["progressive"]), const='normal', nargs='?', choices=['on', 'off', 'random'],
|
||||
help='''\
|
||||
Select progressive equipment setting. Affects available itempool. (default: %(default)s)
|
||||
On: Swords, Shields, Armor, and Gloves will
|
||||
all be progressive equipment. Each subsequent
|
||||
item of the same type the player finds will
|
||||
upgrade that piece of equipment by one stage.
|
||||
Off: Swords, Shields, Armor, and Gloves will not
|
||||
be progressive equipment. Higher level items may
|
||||
be found at any time. Downgrades are not possible.
|
||||
Random: Swords, Shields, Armor, and Gloves will, per
|
||||
category, be randomly progressive or not.
|
||||
Link will die in one hit.
|
||||
''')
|
||||
parser.add_argument('--algorithm', default=defval(settings["algorithm"]), const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'],
|
||||
help='''\
|
||||
Select item filling algorithm. (default: %(default)s
|
||||
balanced: vt26 derivative that aims to strike a balance between
|
||||
the overworld heavy vt25 and the dungeon heavy vt26
|
||||
algorithm.
|
||||
vt26: Shuffle items and place them in a random location
|
||||
that it is not impossible to be in. This includes
|
||||
dungeon keys and items.
|
||||
vt25: Shuffle items and place them in a random location
|
||||
that it is not impossible to be in.
|
||||
vt21: Unbiased in its selection, but has tendency to put
|
||||
Ice Rod in Turtle Rock.
|
||||
vt22: Drops off stale locations after 1/3 of progress
|
||||
items were placed to try to circumvent vt21\'s
|
||||
shortcomings.
|
||||
Freshness: Keep track of stale locations (ones that cannot be
|
||||
reached yet) and decrease likeliness of selecting
|
||||
them the more often they were found unreachable.
|
||||
Flood: Push out items starting from Link\'s House and
|
||||
slightly biased to placing progression items with
|
||||
less restrictions.
|
||||
''')
|
||||
parser.add_argument('--shuffle', default=defval(settings["shuffle"]), const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', '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.
|
||||
Insanity: Decouple entrances and exits from each other and
|
||||
shuffle them freely. Caves that used to be single
|
||||
entrance will still exit to the same location from
|
||||
which they are entered.
|
||||
Vanilla: All entrances are in the same locations they were
|
||||
in the base game.
|
||||
Legacy shuffles preserve behavior from older versions of the
|
||||
entrance randomizer including significant technical limitations.
|
||||
The dungeon variants only mix up dungeons and keep the rest of
|
||||
the overworld vanilla.
|
||||
''')
|
||||
parser.add_argument('--door_shuffle', default=defval(settings["door_shuffle"]), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed'],
|
||||
help='''\
|
||||
Select Door Shuffling Algorithm. (default: %(default)s)
|
||||
Basic: Doors are mixed within a single dungeon.
|
||||
(Not yet implemented)
|
||||
Crossed: Doors are mixed between all dungeons.
|
||||
(Not yet implemented)
|
||||
Vanilla: All doors are connected the same way they were in the
|
||||
base game.
|
||||
''')
|
||||
parser.add_argument('--experimental', default=defval(settings["experimental"] != 0), help='Enable experimental features', action='store_true')
|
||||
parser.add_argument('--dungeon_counters', default=defval(settings["dungeon_counters"]), help='Enable dungeon chest counters', const='off', nargs='?', choices=['off', 'on', 'pickup', 'default'])
|
||||
parser.add_argument('--crystals_ganon', default=defval(settings["crystals_ganon"]), 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=defval(settings["crystals_gt"]), 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('--openpyramid', default=defval(settings["openpyramid"] != 0), help='''\
|
||||
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it
|
||||
''', action='store_true')
|
||||
parser.add_argument('--rom', default=defval(settings["rom"]), help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||
parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
||||
parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help='Define seed number to generate.', type=int)
|
||||
parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else None), 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=defval(settings["fastmenu"]), 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', default=defval(settings["quickswap"] != 0), help='Enable quick item swapping with L and R.', action='store_true')
|
||||
parser.add_argument('--disablemusic', default=defval(settings["disablemusic"] != 0), help='Disables game music.', action='store_true')
|
||||
parser.add_argument('--mapshuffle', default=defval(settings["mapshuffle"] != 0), help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--compassshuffle', default=defval(settings["compassshuffle"] != 0), help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--keyshuffle', default=defval(settings["keyshuffle"] != 0), help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--bigkeyshuffle', default=defval(settings["bigkeyshuffle"] != 0), help='Big Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--keysanity', default=defval(settings["keysanity"] != 0), help=argparse.SUPPRESS, action='store_true')
|
||||
parser.add_argument('--retro', default=defval(settings["retro"] != 0), 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('--startinventory', default=defval(settings["startinventory"]), help='Specifies a list of items that will be in your starting inventory (separated by commas)')
|
||||
parser.add_argument('--usestartinventory', default=defval(settings["usestartinventory"] != 0), help='Not supported.')
|
||||
parser.add_argument('--custom', default=defval(settings["custom"] != 0), help='Not supported.')
|
||||
parser.add_argument('--customitemarray', default={}, help='Not supported.')
|
||||
parser.add_argument('--accessibility', default=defval(settings["accessibility"]), 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', default=defval(settings["hints"] != 0), help='''\
|
||||
Make telepathic tiles and storytellers give helpful hints.
|
||||
''', action='store_true')
|
||||
|
||||
# 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('--customitemarray', default={}, help=argparse.SUPPRESS)
|
||||
|
||||
# included for backwards compatibility
|
||||
parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(settings["shuffleganon"] != 0))
|
||||
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=defval(settings["heartbeep"]), 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=defval(settings["heartcolor"]), 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=defval(settings["ow_palettes"]), choices=['default', 'random', 'blackout'])
|
||||
parser.add_argument('--uw_palettes', default=defval(settings["uw_palettes"]), choices=['default', 'random', 'blackout'])
|
||||
parser.add_argument('--sprite', default=defval(settings["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('--suppress_rom', default=defval(settings["suppress_rom"] != 0), 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=defval(settings["skip_playthrough"] != 0))
|
||||
parser.add_argument('--enemizercli', default=defval(settings["enemizercli"]))
|
||||
parser.add_argument('--shufflebosses', default=defval(settings["shufflebosses"]), choices=['none', 'basic', 'normal', 'chaos'])
|
||||
parser.add_argument('--shuffleenemies', default=defval(settings["shuffleenemies"]), choices=['none', 'shuffled', 'chaos'])
|
||||
parser.add_argument('--enemy_health', default=defval(settings["enemy_health"]), choices=['default', 'easy', 'normal', 'hard', 'expert'])
|
||||
parser.add_argument('--enemy_damage', default=defval(settings["enemy_damage"]), choices=['default', 'shuffled', 'chaos'])
|
||||
parser.add_argument('--shufflepots', default=defval(settings["shufflepots"] != 0), action='store_true')
|
||||
parser.add_argument('--beemizer', default=defval(settings["beemizer"]), type=lambda value: min(max(int(value), 0), 4))
|
||||
parser.add_argument('--remote_items', default=defval(settings["remote_items"] != 0), action='store_true')
|
||||
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
|
||||
parser.add_argument('--names', default=defval(settings["names"]))
|
||||
parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1))
|
||||
parser.add_argument('--outputpath', default=defval(settings["outputpath"]))
|
||||
parser.add_argument('--race', default=defval(settings["race"] != 0), action='store_true')
|
||||
parser.add_argument('--saveonexit', default=defval(settings["saveonexit"]), choices=['never', 'ask', 'always'])
|
||||
parser.add_argument('--outputname')
|
||||
|
||||
if multiargs.multi:
|
||||
for player in range(1, multiargs.multi + 1):
|
||||
@@ -305,7 +87,7 @@ def parse_arguments(argv, no_defaults=False):
|
||||
if multiargs.multi:
|
||||
defaults = copy.deepcopy(ret)
|
||||
for player in range(1, multiargs.multi + 1):
|
||||
playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True)
|
||||
playerargs = parse_cli(shlex.split(getattr(ret,f"p{player}")), True)
|
||||
|
||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||
@@ -323,9 +105,10 @@ def parse_arguments(argv, no_defaults=False):
|
||||
return ret
|
||||
|
||||
|
||||
def get_settings():
|
||||
def parse_settings():
|
||||
# set default settings
|
||||
settings = {
|
||||
"lang": "en",
|
||||
"retro": False,
|
||||
"mode": "open",
|
||||
"logic": "noglitches",
|
||||
@@ -340,8 +123,9 @@ def get_settings():
|
||||
"accessibility": "items",
|
||||
"algorithm": "balanced",
|
||||
|
||||
# Shuffle Ganon defaults to TRUE
|
||||
"openpyramid": False,
|
||||
"shuffleganon": False,
|
||||
"shuffleganon": True,
|
||||
"shuffle": "vanilla",
|
||||
|
||||
"shufflepots": False,
|
||||
@@ -363,25 +147,30 @@ def get_settings():
|
||||
"multi": 1,
|
||||
"names": "",
|
||||
|
||||
# Hints default to TRUE
|
||||
"hints": True,
|
||||
"no_hints": False,
|
||||
"disablemusic": False,
|
||||
"quickswap": False,
|
||||
"heartcolor": "red",
|
||||
"heartbeep": "normal",
|
||||
"sprite": None,
|
||||
"sprite": os.path.join(".","data","sprites","official","001.link.1.zspr"),
|
||||
"fastmenu": "normal",
|
||||
"ow_palettes": "default",
|
||||
"uw_palettes": "default",
|
||||
|
||||
# Spoiler defaults to FALSE
|
||||
# Playthrough defaults to TRUE
|
||||
# ROM defaults to TRUE
|
||||
"create_spoiler": False,
|
||||
"skip_playthrough": False,
|
||||
"suppress_rom": False,
|
||||
"calc_playthrough": True,
|
||||
"create_rom": True,
|
||||
"usestartinventory": False,
|
||||
"custom": False,
|
||||
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
|
||||
|
||||
"seed": None,
|
||||
"count": None,
|
||||
"seed": "",
|
||||
"count": 1,
|
||||
"startinventory": "",
|
||||
"beemizer": 0,
|
||||
"remote_items": False,
|
||||
@@ -462,6 +251,7 @@ def get_settings():
|
||||
"randomSprite": False,
|
||||
"outputpath": os.path.join("."),
|
||||
"saveonexit": "ask",
|
||||
"outputname": "",
|
||||
"startinventoryarray": {}
|
||||
}
|
||||
|
||||
@@ -477,11 +267,14 @@ def get_settings():
|
||||
settings[k] = v
|
||||
return settings
|
||||
|
||||
|
||||
# Priority fallback is:
|
||||
# 1: CLI
|
||||
# 2: Settings file
|
||||
# 3: Canned defaults
|
||||
def get_args_priority(settings_args, gui_args, cli_args):
|
||||
args = {}
|
||||
args["settings"] = get_settings() if settings_args is None else settings_args
|
||||
args["gui"] = {} if gui_args is None else gui_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"]
|
||||
@@ -492,17 +285,38 @@ def get_args_priority(settings_args, gui_args, cli_args):
|
||||
|
||||
if args["cli"] is None:
|
||||
args["cli"] = {}
|
||||
cli = vars(parse_arguments(None))
|
||||
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"]
|
||||
different_val = (k in args["load"] and k in args["cli"]) and (args["load"][k] != args["cli"][k])
|
||||
cli_has_empty_dict = k in args["cli"] and isinstance(args["cli"][k], dict) and len(args["cli"][k]) == 0
|
||||
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] = args["cli"][k]
|
||||
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
|
||||
|
||||
+36
-26
@@ -318,7 +318,7 @@ def within_dungeon(world, player):
|
||||
dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
|
||||
dungeon_builders[key].entrance_list = list(entrances_map[key])
|
||||
recombinant_builders = {}
|
||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish)
|
||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||
|
||||
paths = determine_required_paths(world, player)
|
||||
@@ -328,15 +328,15 @@ def within_dungeon(world, player):
|
||||
start = time.process_time()
|
||||
for builder in world.dungeon_layouts[player].values():
|
||||
shuffle_key_doors(builder, world, player)
|
||||
logging.getLogger('').info('Key door shuffle time: %s', time.process_time()-start)
|
||||
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time"), time.process_time()-start)
|
||||
smooth_door_pairs(world, player)
|
||||
|
||||
|
||||
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map):
|
||||
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, fish):
|
||||
for name, split_list in split_region_starts.items():
|
||||
builder = dungeon_builders.pop(name)
|
||||
recombinant_builders[name] = builder
|
||||
split_builders = split_dungeon_builder(builder, split_list)
|
||||
split_builders = split_dungeon_builder(builder, split_list, fish)
|
||||
dungeon_builders.update(split_builders)
|
||||
for sub_name, split_entrances in split_list.items():
|
||||
sub_builder = dungeon_builders[name+' '+sub_name]
|
||||
@@ -370,7 +370,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
||||
last_key = builder.name
|
||||
loops += 1
|
||||
else:
|
||||
logging.getLogger('').info('Generating dungeon: %s', builder.name)
|
||||
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","generating.dungeon"), builder.name)
|
||||
ds = generate_dungeon(builder, origin_list_sans_drops, split_dungeon, world, player)
|
||||
find_new_entrances(ds, entrances_map, connections, potentials, enabled_entrances, world, player)
|
||||
ds.name = name
|
||||
@@ -691,7 +691,7 @@ def cross_dungeon(world, player):
|
||||
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
|
||||
loc.forced_item = loc.item = ItemFactory(key_name, player)
|
||||
recombinant_builders = {}
|
||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish)
|
||||
|
||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||
|
||||
@@ -699,25 +699,15 @@ def cross_dungeon(world, player):
|
||||
check_required_paths(paths, world, player)
|
||||
|
||||
hc = world.get_dungeon('Hyrule Castle', player)
|
||||
del hc.dungeon_items[0] # removes map
|
||||
hc.dungeon_items.append(ItemFactory('Compass (Escape)', player))
|
||||
at = world.get_dungeon('Agahnims Tower', player)
|
||||
at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player))
|
||||
gt = world.get_dungeon('Ganons Tower', player)
|
||||
del gt.dungeon_items[0] # removes map
|
||||
at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player))
|
||||
|
||||
assign_cross_keys(dungeon_builders, world, player)
|
||||
all_dungeon_items = [y for x in world.dungeons if x.player == player for y in x.all_items]
|
||||
target_items = 34 if world.retro[player] else 63
|
||||
d_items = target_items - len(all_dungeon_items)
|
||||
if d_items > 0:
|
||||
if d_items >= 1: # restore HC map
|
||||
world.get_dungeon('Hyrule Castle', player).dungeon_items.append(ItemFactory('Map (Escape)', player))
|
||||
if d_items >= 2: # restore GT map
|
||||
world.get_dungeon('Ganons Tower', player).dungeon_items.append(ItemFactory('Map (Ganons Tower)', player))
|
||||
if d_items > 2:
|
||||
world.pool_adjustment[player] = d_items - 2
|
||||
elif d_items < 0:
|
||||
world.pool_adjustment[player] = d_items
|
||||
smooth_door_pairs(world, player)
|
||||
|
||||
@@ -812,7 +802,7 @@ def assign_cross_keys(dungeon_builders, world, player):
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||
logging.getLogger('').info('Cross Dungeon: Key door shuffle time: %s', time.process_time()-start)
|
||||
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time.crossed"), time.process_time()-start)
|
||||
|
||||
|
||||
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||
@@ -969,14 +959,14 @@ def calc_used_dungeon_items(builder):
|
||||
|
||||
def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
|
||||
logger = logging.getLogger('')
|
||||
logger.info('Shuffling Key doors for %s', builder.name)
|
||||
logger.info('%s %s', world.fish.translate("cli","cli","shuffling.keydoors"), builder.name)
|
||||
# find valid combination of candidates
|
||||
if len(builder.candidates) < builder.key_doors_num:
|
||||
if not drop_keys:
|
||||
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
||||
return False
|
||||
builder.key_doors_num = len(builder.candidates) # reduce number of key doors
|
||||
logger.info('Lowering key door count because not enough candidates: %s', builder.name)
|
||||
logger.info('%s: %s', world.fish.translate("cli","cli","lowering.keys.candidates"), builder.name)
|
||||
combinations = ncr(len(builder.candidates), builder.key_doors_num)
|
||||
itr = 0
|
||||
start = time.process_time()
|
||||
@@ -996,7 +986,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
|
||||
if not drop_keys:
|
||||
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
||||
return False
|
||||
logger.info('Lowering key door count because no valid layouts: %s', builder.name)
|
||||
logger.info('%s: %s', world.fish.translate("cli","cli","lowering.keys.layouts"), builder.name)
|
||||
builder.key_doors_num -= 1
|
||||
if builder.key_doors_num < 0:
|
||||
raise Exception('Bad dungeon %s - 0 key doors not valid' % builder.name)
|
||||
@@ -1043,8 +1033,9 @@ def log_key_logic(d_name, key_logic):
|
||||
logger.debug('*Rule for %s:', rule.door_reference)
|
||||
if rule.bk_conditional_set:
|
||||
logger.debug('**BK Checks %s', ','.join([x.name for x in rule.bk_conditional_set]))
|
||||
logger.debug('**BK Blocked By Door (%s) : %s', rule.needed_keys_wo_bk, ','.join([x.name for x in rule.check_locations_wo_bk]))
|
||||
logger.debug('**BK Elsewhere (%s) : %s', rule.needed_keys_w_bk, ','.join([x.name for x in rule.check_locations_w_bk]))
|
||||
logger.debug('**BK Blocked (%s) : %s', rule.needed_keys_wo_bk, ','.join([x.name for x in rule.check_locations_wo_bk]))
|
||||
if rule.needed_keys_w_bk:
|
||||
logger.debug('**BK Available (%s) : %s', rule.needed_keys_w_bk, ','.join([x.name for x in rule.check_locations_w_bk]))
|
||||
|
||||
|
||||
def build_pair_list(flat_list):
|
||||
@@ -1294,7 +1285,7 @@ def stateful_door(door, kind):
|
||||
|
||||
|
||||
def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b):
|
||||
r_kind = random.choices([DoorKind.Normal, DoorKind.Bombable, DoorKind.Dashable], [5, 2, 3], k=1)[0]
|
||||
r_kind = random.choices([DoorKind.Normal, DoorKind.Bombable, DoorKind.Dashable], [15, 4, 6], k=1)[0]
|
||||
if r_kind != DoorKind.Normal:
|
||||
if door.type == DoorType.Normal:
|
||||
add_pair(door, partner, world, player)
|
||||
@@ -1486,6 +1477,7 @@ class DROptions(Flag):
|
||||
NoOptions = 0x00
|
||||
Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart
|
||||
Town_Portal = 0x02 # If on, Players will start with mirror scroll
|
||||
Map_Info = 0x04
|
||||
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
|
||||
|
||||
# DATA GOES DOWN HERE
|
||||
@@ -1572,10 +1564,12 @@ logical_connections = [
|
||||
('Ice Big Chest Landing Push Blocks', 'Ice Big Chest View'),
|
||||
('Mire Lobby Gap', 'Mire Post-Gap'),
|
||||
('Mire Post-Gap Gap', 'Mire Lobby'),
|
||||
('Mire Hub Upper Blue Barrier', 'Mire Hub Top'),
|
||||
('Mire Hub Upper Blue Barrier', 'Mire Hub Switch'),
|
||||
('Mire Hub Lower Blue Barrier', 'Mire Hub Right'),
|
||||
('Mire Hub Right Blue Barrier', 'Mire Hub'),
|
||||
('Mire Hub Top Blue Barrier', 'Mire Hub'),
|
||||
('Mire Hub Top Blue Barrier', 'Mire Hub Switch'),
|
||||
('Mire Hub Switch Blue Barrier N', 'Mire Hub Top'),
|
||||
('Mire Hub Switch Blue Barrier S', 'Mire Hub'),
|
||||
('Mire Map Spike Side Drop Down', 'Mire Lone Shooter'),
|
||||
('Mire Map Spike Side Blue Barrier', 'Mire Crystal Dead End'),
|
||||
('Mire Map Spot Blue Barrier', 'Mire Crystal Dead End'),
|
||||
@@ -2246,3 +2240,19 @@ compass_data = {
|
||||
'Turtle Rock': (0x11F, 0xcb, 0x15e, 0, 0x18),
|
||||
'Ganons Tower': (0x13A, 0xcc, 0x170, 2, 0x1a)
|
||||
}
|
||||
|
||||
# For compass boss indicator
|
||||
boss_indicator = {
|
||||
'Eastern Palace': (0x04, 'Eastern Boss SE'),
|
||||
'Desert Palace': (0x06, 'Desert Boss SW'),
|
||||
'Agahnims Tower': (0x08, 'Tower Agahnim 1 SW'),
|
||||
'Swamp Palace': (0x0a, 'Swamp Boss SW'),
|
||||
'Palace of Darkness': (0x0c, 'PoD Boss SE'),
|
||||
'Misery Mire': (0x0e, 'Mire Boss SW'),
|
||||
'Skull Woods': (0x10, 'Skull Spike Corner SW'),
|
||||
'Ice Palace': (0x12, 'Ice Antechamber NE'),
|
||||
'Tower of Hera': (0x14, 'Hera Boss Down Stairs'),
|
||||
'Thieves Town': (0x16, 'Thieves Boss SE'),
|
||||
'Turtle Rock': (0x18, 'TR Boss SW'),
|
||||
'Ganons Tower': (0x1a, 'GT Agahnim 2 SW')
|
||||
}
|
||||
|
||||
@@ -666,8 +666,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'Ice Freezors Bomb Hole', Hole), # combine these two? -- they have to lead to the same spot
|
||||
create_door(player, 'Ice Freezors Ledge Hole', Hole),
|
||||
create_door(player, 'Ice Freezors Ledge ES', Intr).dir(Ea, 0x7e, Bot, High).pos(2),
|
||||
create_door(player, 'Ice Tall Hint WS', Intr).dir(We, 0x7e, Bot, High).pos(2),
|
||||
create_door(player, 'Ice Tall Hint EN', Nrml).dir(Ea, 0x7e, Top, High).pos(1),
|
||||
create_door(player, 'Ice Tall Hint WS', Intr).dir(We, 0x7e, Bot, High).pos(1),
|
||||
create_door(player, 'Ice Tall Hint EN', Nrml).dir(Ea, 0x7e, Top, High).pos(2),
|
||||
create_door(player, 'Ice Tall Hint SE', Nrml).dir(So, 0x7e, Right, High).small_key().pos(0),
|
||||
create_door(player, 'Ice Hookshot Ledge WN', Nrml).dir(We, 0x7f, Top, High).no_exit().trap(0x4).pos(0).kill(),
|
||||
create_door(player, 'Ice Hookshot Ledge Path', Lgcl),
|
||||
@@ -722,6 +722,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'Mire Hub Lower Blue Barrier', Lgcl),
|
||||
create_door(player, 'Mire Hub Right Blue Barrier', Lgcl),
|
||||
create_door(player, 'Mire Hub Top Blue Barrier', Lgcl),
|
||||
create_door(player, 'Mire Hub Switch Blue Barrier N', Lgcl),
|
||||
create_door(player, 'Mire Hub Switch Blue Barrier S', Lgcl),
|
||||
create_door(player, 'Mire Hub Right EN', Nrml).dir(Ea, 0xc2, Top, High).small_key().pos(0),
|
||||
create_door(player, 'Mire Hub Top NW', Nrml).dir(No, 0xc2, Left, High).pos(2),
|
||||
create_door(player, 'Mire Lone Shooter WS', Nrml).dir(We, 0xc3, Bot, High).pos(6),
|
||||
@@ -1161,6 +1163,8 @@ def create_doors(world, player):
|
||||
world.get_door('Mire Hub Lower Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Mire Hub Right Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Mire Hub Top Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Mire Hub Switch Blue Barrier N', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Mire Hub Switch Blue Barrier S', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Mire Map Spike Side Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Mire Map Spot Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('Mire Crystal Dead End Left Barrier', player).barrier(CrystalBarrier.Blue)
|
||||
|
||||
+15
-27
@@ -710,7 +710,7 @@ class ExplorationState(object):
|
||||
self.key_locations += 1
|
||||
if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']:
|
||||
self.ttl_locations += 1
|
||||
if location not in self.found_locations:
|
||||
if location not in self.found_locations: # todo: special logic for TT Boss?
|
||||
self.found_locations.append(location)
|
||||
if not bk_Flag:
|
||||
self.bk_found.add(location)
|
||||
@@ -1152,8 +1152,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge
|
||||
# polarity:
|
||||
if not global_pole.is_valid(dungeon_map):
|
||||
raise NeutralizingException('Either free location/crystal assignment is already globally invalid - lazy dev check this earlier!')
|
||||
logger.info('-Balancing Doors')
|
||||
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
|
||||
logger.info(world.fish.translate("cli","cli","balance.doors"))
|
||||
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, world.fish)
|
||||
# the rest
|
||||
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
|
||||
return dungeon_map
|
||||
@@ -1322,7 +1322,7 @@ def assign_location_sectors(dungeon_map, free_location_sectors, global_pole):
|
||||
totals[choice] += sector.chest_locations
|
||||
valid = True
|
||||
for d_name, idx in d_idx.items():
|
||||
if totals[idx] < minimal_locations(d_name):
|
||||
if totals[idx] < 5: # min locations for dungeons is 5 (bk exception)
|
||||
valid = False
|
||||
break
|
||||
for i, choice in enumerate(choices):
|
||||
@@ -1353,18 +1353,6 @@ def weighted_random_locations(dungeon_map, free_location_sectors):
|
||||
return choices, d_idx, totals
|
||||
|
||||
|
||||
def minimal_locations(dungeon_name):
|
||||
# bump to 5 if maps do something useful for all these dungeons
|
||||
if dungeon_name == 'Hyrule Castle':
|
||||
return 4 # bk + compass + 2 others
|
||||
if dungeon_name == 'Agahnims Tower':
|
||||
return 4
|
||||
if dungeon_name == 'Ganons Tower':
|
||||
return 4
|
||||
# reduce gt to 4 once compasses work
|
||||
return 5
|
||||
|
||||
|
||||
def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole, assign_one=False):
|
||||
population = []
|
||||
some_c_switches_present = False
|
||||
@@ -1566,9 +1554,9 @@ def sum_polarity(sector_list):
|
||||
return pol
|
||||
|
||||
|
||||
def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger):
|
||||
def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, fish):
|
||||
# step 1: fix polarity connection issues
|
||||
logger.info('--Basic Traversal')
|
||||
logger.info(fish.translate("cli","cli","basic.traversal"))
|
||||
unconnected_builders = identify_polarity_issues(dungeon_map)
|
||||
while len(unconnected_builders) > 0:
|
||||
for name, builder in unconnected_builders.items():
|
||||
@@ -1606,7 +1594,7 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
|
||||
problem_builders = identify_simple_branching_issues(problem_builders)
|
||||
|
||||
# step 3: fix neutrality issues
|
||||
polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger)
|
||||
polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish)
|
||||
|
||||
# step 4: fix dead ends again
|
||||
neutral_choices: List[List] = neutralize_the_rest(polarized_sectors)
|
||||
@@ -1657,7 +1645,7 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
|
||||
tries += 1
|
||||
|
||||
|
||||
def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger):
|
||||
def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish):
|
||||
# step 3a: fix odd builders
|
||||
odd_builders = [x for x in dungeon_map.values() if sum_polarity(x.sectors).charge() % 2 != 0]
|
||||
random.shuffle(odd_builders)
|
||||
@@ -1688,7 +1676,7 @@ def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger):
|
||||
random.shuffle(builder_order)
|
||||
for builder in builder_order:
|
||||
# global_pole.check_odd_polarities(polarized_sectors, dungeon_map)
|
||||
logger.info('--Balancing %s', builder.name)
|
||||
logger.info('%s %s', fish.translate("cli", "cli", "balancing"), builder.name)
|
||||
while not builder.polarity().is_neutral():
|
||||
rejects = []
|
||||
candidates = find_neutralizing_candidates(builder, polarized_sectors, rejects)
|
||||
@@ -2203,9 +2191,9 @@ def assign_the_rest(dungeon_map, neutral_sectors, global_pole):
|
||||
tries += 1
|
||||
|
||||
|
||||
def split_dungeon_builder(builder, split_list):
|
||||
def split_dungeon_builder(builder, split_list, fish):
|
||||
logger = logging.getLogger('')
|
||||
logger.info('Splitting Up Desert/Skull')
|
||||
logger.info(fish.translate("cli","cli","splitting.up") + ' ' + 'Desert/Skull')
|
||||
candidate_sectors = dict.fromkeys(builder.sectors)
|
||||
global_pole = GlobalPolarity(candidate_sectors)
|
||||
|
||||
@@ -2216,10 +2204,10 @@ def split_dungeon_builder(builder, split_list):
|
||||
sub_builder.all_entrances = split_entrances
|
||||
for r_name in split_entrances:
|
||||
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
|
||||
return balance_split(candidate_sectors, dungeon_map, global_pole)
|
||||
return balance_split(candidate_sectors, dungeon_map, global_pole, fish)
|
||||
|
||||
|
||||
def balance_split(candidate_sectors, dungeon_map, global_pole):
|
||||
def balance_split(candidate_sectors, dungeon_map, global_pole, fish):
|
||||
logger = logging.getLogger('')
|
||||
# categorize sectors
|
||||
check_for_forced_dead_ends(dungeon_map, candidate_sectors, global_pole)
|
||||
@@ -2236,8 +2224,8 @@ def balance_split(candidate_sectors, dungeon_map, global_pole):
|
||||
# blue barriers
|
||||
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
|
||||
# polarity:
|
||||
logger.info('-Re-balancing ' + next(iter(dungeon_map.keys())) + ' et al')
|
||||
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
|
||||
logger.info(fish.translate("cli","cli","re-balancing") + ' ' + next(iter(dungeon_map.keys())) + ' et al')
|
||||
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, fish)
|
||||
# the rest
|
||||
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
|
||||
return dungeon_map
|
||||
|
||||
+26
-9
@@ -8,14 +8,24 @@ import textwrap
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from CLI import parse_arguments
|
||||
from Main import main
|
||||
from source.classes.BabelFish import BabelFish
|
||||
import source.classes.diags as diagnostics
|
||||
|
||||
from CLI import parse_cli, get_args_priority
|
||||
from Main import main, EnemizerError, __version__
|
||||
from Rom import get_sprite_from_name
|
||||
from Utils import is_bundled, close_console
|
||||
from Fill import FillError
|
||||
|
||||
def start():
|
||||
args = parse_arguments(None)
|
||||
args = parse_cli(None)
|
||||
|
||||
# print diagnostics
|
||||
# usage: py DungeonRandomizer.py --diags
|
||||
if args.diags:
|
||||
diags = diagnostics.output(__version__)
|
||||
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
|
||||
@@ -42,20 +52,27 @@ def start():
|
||||
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)
|
||||
elif args.count is not None:
|
||||
elif args.count is not None and args.count > 1:
|
||||
random.seed(None)
|
||||
seed = args.seed or random.randint(0, 999999999)
|
||||
failures = []
|
||||
logger = logging.getLogger('')
|
||||
for _ in range(args.count):
|
||||
try:
|
||||
main(seed=seed, args=args)
|
||||
logger.info('Finished run %s', _+1)
|
||||
except (FillError, Exception, RuntimeError) as err:
|
||||
main(seed=seed, args=args, fish=fish)
|
||||
logger.info('%s %s', fish.translate("cli","cli","finished.run"), _+1)
|
||||
except (FillError, EnemizerError, Exception, RuntimeError) as err:
|
||||
failures.append((err, seed))
|
||||
logger.warning('Generation failed: %s', err)
|
||||
logger.warning('%s: %s', fish.translate("cli","cli","generation.failed"), err)
|
||||
seed = random.randint(0, 999999999)
|
||||
for fail in failures:
|
||||
logger.info('%s\tseed failed with: %s', fail[1], fail[0])
|
||||
@@ -66,7 +83,7 @@ def start():
|
||||
logger.info('Generation fail rate: ' + str(fail_rate[0] ).rjust(3, " ") + '.' + str(fail_rate[1] ).ljust(6, '0') + '%')
|
||||
logger.info('Generation success rate: ' + str(success_rate[0]).rjust(3, " ") + '.' + str(success_rate[1]).ljust(6, '0') + '%')
|
||||
else:
|
||||
main(seed=args.seed, args=args)
|
||||
main(seed=args.seed, args=args, fish=fish)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+11
-3
@@ -1,8 +1,12 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
import sys
|
||||
|
||||
block_cipher = None
|
||||
console = True
|
||||
|
||||
BINARY_SLUG = "DungeonRandomizer"
|
||||
|
||||
def recurse_for_py_files(names_so_far):
|
||||
returnvalue = []
|
||||
for name in os.listdir(os.path.join(*names_so_far)):
|
||||
@@ -21,10 +25,14 @@ def recurse_for_py_files(names_so_far):
|
||||
return returnvalue
|
||||
|
||||
hiddenimports = []
|
||||
binaries = []
|
||||
|
||||
a = Analysis(['DungeonRandomizer.py'],
|
||||
#if sys.platform.find("windows"):
|
||||
# binaries.append(("ucrtbase.dll","."))
|
||||
|
||||
a = Analysis([f"./{BINARY_SLUG}.py"],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
binaries=binaries,
|
||||
datas=[],
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
@@ -50,7 +58,7 @@ exe = EXE(pyz,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='DungeonRandomizer',
|
||||
name=BINARY_SLUG,
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
|
||||
+5
-5
@@ -255,11 +255,11 @@ ice_regions = [
|
||||
]
|
||||
|
||||
mire_regions = [
|
||||
'Mire Lobby', 'Mire Post-Gap', 'Mire 2', 'Mire Hub', 'Mire Hub Right', 'Mire Hub Top', 'Mire Lone Shooter',
|
||||
'Mire Failure Bridge', 'Mire Falling Bridge', '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 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', '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 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',
|
||||
|
||||
@@ -234,7 +234,7 @@ def valid_key_placement(item, location, itempool, world):
|
||||
return True
|
||||
key_logic = world.key_logic[item.player][dungeon.name]
|
||||
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
|
||||
return key_logic.check_placement(unplaced_keys)
|
||||
return key_logic.check_placement(unplaced_keys, location if item.bigkey else None)
|
||||
else:
|
||||
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
|
||||
or (item.bigkey and not world.bigkeyshuffle[item.player]))
|
||||
@@ -392,7 +392,7 @@ def balance_multiworld_progression(world):
|
||||
threshold = max(reachable_locations_count.values()) - 20
|
||||
|
||||
balancing_players = [player for player, reachables in reachable_locations_count.items() if reachables < threshold]
|
||||
if balancing_players:
|
||||
if balancing_players is not None and len(balancing_players) > 0:
|
||||
balancing_state = state.copy()
|
||||
balancing_unchecked_locations = unchecked_locations.copy()
|
||||
balancing_reachables = reachable_locations_count.copy()
|
||||
|
||||
@@ -4,26 +4,29 @@ import os
|
||||
import sys
|
||||
from tkinter import Tk, Button, BOTTOM, TOP, StringVar, BooleanVar, X, BOTH, RIGHT, ttk, messagebox
|
||||
|
||||
from argparse import Namespace
|
||||
from CLI import get_settings, get_args_priority
|
||||
from DungeonRandomizer import parse_arguments
|
||||
from gui.adjust.overview import adjust_page
|
||||
from gui.startinventory.overview import startinventory_page
|
||||
from gui.custom.overview import custom_page
|
||||
from gui.loadcliargs import loadcliargs, loadadjustargs
|
||||
from gui.randomize.item import item_page
|
||||
from gui.randomize.entrando import entrando_page
|
||||
from gui.randomize.enemizer import enemizer_page
|
||||
from gui.randomize.dungeon import dungeon_page
|
||||
from gui.randomize.multiworld import multiworld_page
|
||||
from gui.randomize.gameoptions import gameoptions_page
|
||||
from gui.randomize.generation import generation_page
|
||||
from gui.bottom import bottom_frame, create_guiargs
|
||||
from CLI import get_args_priority
|
||||
from DungeonRandomizer import parse_cli
|
||||
from source.gui.adjust.overview import adjust_page
|
||||
from source.gui.startinventory.overview import startinventory_page
|
||||
from source.gui.custom.overview import custom_page
|
||||
from source.gui.loadcliargs import loadcliargs, loadadjustargs
|
||||
from source.gui.randomize.item import item_page
|
||||
from source.gui.randomize.entrando import entrando_page
|
||||
from source.gui.randomize.enemizer import enemizer_page
|
||||
from source.gui.randomize.dungeon import dungeon_page
|
||||
#from source.gui.randomize.multiworld import multiworld_page
|
||||
from source.gui.randomize.gameoptions import gameoptions_page
|
||||
from source.gui.randomize.generation import generation_page
|
||||
from source.gui.bottom import bottom_frame, create_guiargs
|
||||
from GuiUtils import set_icon
|
||||
from Main import __version__ as ESVersion
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
from source.classes.Empty import Empty
|
||||
|
||||
|
||||
def guiMain(args=None):
|
||||
# Save settings to file
|
||||
def save_settings(args):
|
||||
user_resources_path = os.path.join(".", "resources", "user")
|
||||
settings_path = os.path.join(user_resources_path)
|
||||
@@ -35,6 +38,7 @@ def guiMain(args=None):
|
||||
f.write(json.dumps(args, indent=2))
|
||||
os.chmod(os.path.join(settings_path, "settings.json"),0o755)
|
||||
|
||||
# Save settings from GUI
|
||||
def save_settings_from_gui(confirm):
|
||||
gui_args = vars(create_guiargs(self))
|
||||
if self.randomSprite.get():
|
||||
@@ -73,9 +77,13 @@ def guiMain(args=None):
|
||||
# get args
|
||||
# getting Settings & CLI (no GUI built yet)
|
||||
self.args = get_args_priority(None, None, None)
|
||||
lang = "en"
|
||||
if "load" in self.args and "lang" in self.args["load"]:
|
||||
lang = self.args["load"].lang
|
||||
self.fish = BabelFish(lang=lang)
|
||||
|
||||
# get saved settings
|
||||
self.settings = self.args["settings"]
|
||||
self.settings = vars(self.args["settings"])
|
||||
|
||||
# make array for pages
|
||||
self.pages = {}
|
||||
@@ -83,6 +91,7 @@ def guiMain(args=None):
|
||||
# make array for frames
|
||||
self.frames = {}
|
||||
|
||||
# make pages for each section
|
||||
self.notebook = ttk.Notebook(self)
|
||||
self.pages["randomizer"] = ttk.Frame(self.notebook)
|
||||
self.pages["adjust"] = ttk.Frame(self.notebook)
|
||||
@@ -127,8 +136,8 @@ def guiMain(args=None):
|
||||
self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["dungeon"], text="Dungeon Shuffle")
|
||||
|
||||
# Multiworld
|
||||
self.pages["randomizer"].pages["multiworld"],self.settings = multiworld_page(self.pages["randomizer"].notebook,self.settings)
|
||||
self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["multiworld"], text="Multiworld")
|
||||
# self.pages["randomizer"].pages["multiworld"],self.settings = multiworld_page(self.pages["randomizer"].notebook,self.settings)
|
||||
# self.pages["randomizer"].notebook.add(self.pages["randomizer"].pages["multiworld"], text="Multiworld")
|
||||
|
||||
# Game Options
|
||||
self.pages["randomizer"].pages["gameoptions"] = gameoptions_page(self, self.pages["randomizer"].notebook)
|
||||
@@ -142,13 +151,15 @@ def guiMain(args=None):
|
||||
self.pages["randomizer"].notebook.pack()
|
||||
|
||||
# bottom of window: Open Output Directory, Open Documentation (if exists)
|
||||
self.frames["bottom"] = bottom_frame(self, self, None)
|
||||
self.pages["bottom"] = Empty()
|
||||
self.pages["bottom"].pages = {}
|
||||
self.pages["bottom"].pages["content"] = bottom_frame(self, self, None)
|
||||
## Save Settings Button
|
||||
savesettingsButton = Button(self.frames["bottom"], text='Save Settings to File', command=lambda: save_settings_from_gui(True))
|
||||
savesettingsButton = Button(self.pages["bottom"].pages["content"], text='Save Settings to File', command=lambda: save_settings_from_gui(True))
|
||||
savesettingsButton.pack(side=RIGHT)
|
||||
|
||||
# set bottom frame to main window
|
||||
self.frames["bottom"].pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
||||
self.pages["bottom"].pages["content"].pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
||||
|
||||
self.outputPath = StringVar()
|
||||
self.randomSprite = BooleanVar()
|
||||
@@ -178,9 +189,10 @@ def guiMain(args=None):
|
||||
# load adjust settings into options
|
||||
loadadjustargs(self, self.settings)
|
||||
|
||||
# run main window
|
||||
mainWindow.mainloop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parse_arguments(None)
|
||||
args = parse_cli(None)
|
||||
guiMain(args)
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
import sys
|
||||
|
||||
block_cipher = None
|
||||
console = True
|
||||
|
||||
if sys.platform.find("mac") or sys.platform.find("osx"):
|
||||
console = False
|
||||
BINARY_SLUG = "Gui"
|
||||
|
||||
def recurse_for_py_files(names_so_far):
|
||||
returnvalue = []
|
||||
for name in os.listdir(os.path.join(*names_so_far)):
|
||||
@@ -21,10 +27,14 @@ def recurse_for_py_files(names_so_far):
|
||||
return returnvalue
|
||||
|
||||
hiddenimports = []
|
||||
binaries = []
|
||||
|
||||
a = Analysis(['Gui.py'],
|
||||
#if sys.platform.find("windows"):
|
||||
# binaries.append(("ucrtbase.dll","."))
|
||||
|
||||
a = Analysis([f"./{BINARY_SLUG}.py"],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
binaries=binaries,
|
||||
datas=[],
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
@@ -50,7 +60,7 @@ exe = EXE(pyz,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='Gui',
|
||||
name=BINARY_SLUG,
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
|
||||
+4
-3
@@ -1,13 +1,14 @@
|
||||
import queue
|
||||
import os
|
||||
import threading
|
||||
import tkinter as tk
|
||||
|
||||
from Utils import local_path
|
||||
|
||||
def set_icon(window):
|
||||
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'))
|
||||
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")))
|
||||
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
|
||||
|
||||
+13
-9
@@ -9,7 +9,7 @@ from EntranceShuffle import connect_entrance
|
||||
from Fill import FillError, fill_restrictive
|
||||
from Items import ItemFactory
|
||||
|
||||
import classes.constants as CONST
|
||||
import source.classes.constants as CONST
|
||||
|
||||
|
||||
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||
@@ -126,6 +126,7 @@ difficulties = {
|
||||
),
|
||||
}
|
||||
|
||||
# Translate between Mike's label array and YAML/JSON keys
|
||||
def get_custom_array_key(item):
|
||||
label_switcher = {
|
||||
"silverarrow": "silversupgrade",
|
||||
@@ -257,17 +258,17 @@ def generate_itempool(world, player):
|
||||
# set up item pool
|
||||
if world.custom:
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray)
|
||||
world.rupoor_cost = min(world.customitemarray["rupoorcost"], 9999)
|
||||
world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999)
|
||||
else:
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player])
|
||||
|
||||
if player in world.pool_adjustment.keys():
|
||||
amt = world.pool_adjustment[player]
|
||||
if amt < 0:
|
||||
for i in range(0, amt):
|
||||
for _ in range(0, amt):
|
||||
pool.remove('Rupees (20)')
|
||||
elif amt > 0:
|
||||
for i in range(0, amt):
|
||||
for _ in range(0, amt):
|
||||
pool.append('Rupees (20)')
|
||||
|
||||
for item in precollected_items:
|
||||
@@ -321,9 +322,9 @@ def generate_itempool(world, player):
|
||||
# logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
|
||||
# rather than making all hearts/heart pieces progression items (which slows down generation considerably)
|
||||
# We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
|
||||
if world.difficulty[player] in ['normal', 'hard'] and not (world.custom and world.customitemarray["heartcontainer"] == 0):
|
||||
if world.difficulty[player] in ['normal', 'hard'] and not (world.custom and world.customitemarray[player]["heartcontainer"] == 0):
|
||||
[item for item in items if item.name == 'Boss Heart Container'][0].advancement = True
|
||||
elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray["heartpiece"] < 4):
|
||||
elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray[player]["heartpiece"] < 4):
|
||||
adv_heart_pieces = [item for item in items if item.name == 'Piece of Heart'][0:4]
|
||||
for hp in adv_heart_pieces:
|
||||
hp.advancement = True
|
||||
@@ -600,6 +601,8 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
|
||||
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
|
||||
if isinstance(customitemarray,dict) and 1 in customitemarray:
|
||||
customitemarray = customitemarray[1]
|
||||
pool = []
|
||||
placed_items = {}
|
||||
precollected_items = []
|
||||
@@ -697,7 +700,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
||||
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode
|
||||
if itemtotal < total_items_to_place:
|
||||
nothings = total_items_to_place - itemtotal
|
||||
print("Placing " + str(nothings) + " Nothings")
|
||||
# print("Placing " + str(nothings) + " Nothings")
|
||||
pool.extend(['Nothing'] * nothings)
|
||||
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
@@ -707,12 +710,13 @@ def test():
|
||||
for difficulty in ['normal', 'hard', 'expert']:
|
||||
for goal in ['ganon', 'triforcehunt', 'pedestal']:
|
||||
for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']:
|
||||
for mode in ['open', 'standard', 'inverted']:
|
||||
for mode in ['open', 'standard', 'inverted', 'retro']:
|
||||
for swords in ['random', 'assured', 'swordless', 'vanilla']:
|
||||
for progressive in ['on', 'off']:
|
||||
for shuffle in ['full', 'insanity_legacy']:
|
||||
for retro in [True, False]:
|
||||
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro)
|
||||
for door_shuffle in ['basic', 'crossed', 'vanilla']:
|
||||
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, door_shuffle)
|
||||
count = len(out[0]) + len(out[1])
|
||||
|
||||
correct_count = total_items_to_place
|
||||
|
||||
@@ -130,6 +130,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', '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, 'A big key to Agahnim', '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, 'Now you can find Aga1!', '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, '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, 'A small key to darkness', '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 to darkness', '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, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
|
||||
|
||||
+263
-89
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import logging
|
||||
from collections import defaultdict, deque
|
||||
|
||||
@@ -22,6 +23,7 @@ class KeyLayout(object):
|
||||
self.all_chest_locations = {}
|
||||
self.big_key_special = False
|
||||
self.all_locations = set()
|
||||
self.item_locations = set()
|
||||
|
||||
# bk special?
|
||||
# bk required? True if big chests or big doors exists
|
||||
@@ -31,6 +33,8 @@ class KeyLayout(object):
|
||||
self.flat_prop = flatten_pair_list(self.proposal)
|
||||
self.key_logic = KeyLogic(self.sector.name)
|
||||
self.max_chests = calc_max_chests(builder, self, world, player)
|
||||
self.all_locations = set()
|
||||
self.item_locations = set()
|
||||
|
||||
|
||||
class KeyLogic(object):
|
||||
@@ -48,10 +52,14 @@ class KeyLogic(object):
|
||||
self.placement_rules = []
|
||||
self.outside_keys = 0
|
||||
|
||||
def check_placement(self, unplaced_keys):
|
||||
def check_placement(self, unplaced_keys, big_key_loc=None):
|
||||
for rule in self.placement_rules:
|
||||
if not rule.is_satisfiable(self.outside_keys, unplaced_keys):
|
||||
return False
|
||||
if big_key_loc:
|
||||
for rule_a, rule_b in itertools.combinations(self.placement_rules, 2):
|
||||
if rule_a.contradicts(rule_b, unplaced_keys, big_key_loc):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -66,6 +74,7 @@ class DoorRules(object):
|
||||
# for a place with only 1 free location/key_only_location behind it ... no goals and locations
|
||||
self.allow_small = False
|
||||
self.small_location = None
|
||||
self.opposite = None
|
||||
|
||||
|
||||
class PlacementRule(object):
|
||||
@@ -78,6 +87,36 @@ class PlacementRule(object):
|
||||
self.needed_keys_wo_bk = None
|
||||
self.check_locations_w_bk = None
|
||||
self.check_locations_wo_bk = None
|
||||
self.bk_relevant = True
|
||||
|
||||
def contradicts(self, rule, unplaced_keys, big_key_loc):
|
||||
bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False
|
||||
rule_blocked = big_key_loc in rule.bk_conditional_set if rule.bk_conditional_set else False
|
||||
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
|
||||
rule_locations = rule.check_locations_wo_bk if rule_blocked else rule.check_locations_w_bk
|
||||
if check_locations is None or rule_locations is None:
|
||||
return False
|
||||
check_locations = check_locations - {big_key_loc}
|
||||
rule_locations = rule_locations - {big_key_loc}
|
||||
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||
rule_threshold = rule.needed_keys_wo_bk if rule_blocked else rule.needed_keys_w_bk
|
||||
common_locations = rule_locations & check_locations
|
||||
shared = len(common_locations)
|
||||
if min(rule_threshold, threshold) - shared > 0:
|
||||
left = unplaced_keys - shared
|
||||
check_locations = check_locations - common_locations
|
||||
check_needed = threshold - shared
|
||||
if len(check_locations) < check_needed or left < check_needed:
|
||||
return True
|
||||
else:
|
||||
left -= check_needed
|
||||
rule_locations = rule_locations - common_locations
|
||||
rule_needed = rule_threshold - shared
|
||||
if len(rule_locations) < rule_needed or left < rule_needed:
|
||||
return True
|
||||
else:
|
||||
left -= rule_needed
|
||||
return False
|
||||
|
||||
def is_satisfiable(self, outside_keys, unplaced_keys):
|
||||
bk_blocked = False
|
||||
@@ -86,9 +125,11 @@ class PlacementRule(object):
|
||||
if loc.item and loc.item.bigkey:
|
||||
bk_blocked = True
|
||||
break
|
||||
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
|
||||
if not bk_blocked and check_locations is None:
|
||||
return True
|
||||
available_keys = outside_keys
|
||||
empty_chests = 0
|
||||
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
|
||||
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||
for loc in check_locations:
|
||||
if not loc.item:
|
||||
@@ -134,9 +175,27 @@ def build_key_layout(builder, start_regions, proposal, world, player):
|
||||
key_layout.max_drops = count_key_drops(key_layout.sector)
|
||||
key_layout.max_chests = calc_max_chests(builder, key_layout, world, player)
|
||||
key_layout.big_key_special = 'Hyrule Dungeon Cellblock' in key_layout.sector.region_set()
|
||||
key_layout.all_locations = find_all_locations(key_layout.sector)
|
||||
return key_layout
|
||||
|
||||
|
||||
def count_key_drops(sector):
|
||||
cnt = 0
|
||||
for region in sector.regions:
|
||||
for loc in region.locations:
|
||||
if loc.event and 'Small Key' in loc.item.name:
|
||||
cnt += 1
|
||||
return cnt
|
||||
|
||||
|
||||
def find_all_locations(sector):
|
||||
all_locations = set()
|
||||
for region in sector.regions:
|
||||
for loc in region.locations:
|
||||
all_locations.add(loc)
|
||||
return all_locations
|
||||
|
||||
|
||||
def calc_max_chests(builder, key_layout, world, player):
|
||||
if world.doorShuffle[player] != 'crossed':
|
||||
return len(world.get_dungeon(key_layout.sector.name, player).small_keys)
|
||||
@@ -165,17 +224,13 @@ def analyze_dungeon(key_layout, world, player):
|
||||
raw_avail = chest_keys + len(key_counter.key_only_locations)
|
||||
available = raw_avail - key_counter.used_keys
|
||||
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
|
||||
avail_bigs = exist_relevant_big_doors(key_counter, key_layout)
|
||||
avail_bigs = exist_relevant_big_doors(key_counter, key_layout) or exist_big_chest(key_counter)
|
||||
non_big_locs = count_locations_big_optional(key_counter.free_locations)
|
||||
if not key_counter.big_key_opened:
|
||||
if chest_keys == non_big_locs and chest_keys > 0 and available <= possible_smalls and not avail_bigs:
|
||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
||||
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
||||
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
||||
# todo: detect forced subsequent keys - see keypuzzles
|
||||
# try to relax the rules here? - smallest requirement that doesn't force a softlock
|
||||
child_queue = deque()
|
||||
smallest_rule = None
|
||||
for child in key_counter.child_doors.keys():
|
||||
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
|
||||
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world, player)
|
||||
@@ -183,34 +238,20 @@ def analyze_dungeon(key_layout, world, player):
|
||||
child_queue.append((child, odd_counter, empty_flag))
|
||||
if child in doors_completed and child in key_logic.door_rules.keys():
|
||||
rule = key_logic.door_rules[child]
|
||||
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
||||
smallest_rule = rule.small_key_num
|
||||
while len(child_queue) > 0:
|
||||
child, odd_counter, empty_flag = child_queue.popleft()
|
||||
if not child.bigKey and child not in doors_completed:
|
||||
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False, empty_flag)
|
||||
rule = create_rule(best_counter, key_counter, key_layout, world, player)
|
||||
# todo: seems to be caused by best_counter not opening the big key door when that's logically required. Re-evaluate usage of this
|
||||
# if not rule.is_valid:
|
||||
# logging.getLogger('').warning('Key logic for door %s requires too many chests. Seed may be beatable anyway.', child.name)
|
||||
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
||||
smallest_rule = rule.small_key_num
|
||||
check_for_self_lock_key(rule, child, best_counter, key_layout, world, player)
|
||||
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world, player)
|
||||
key_logic.door_rules[child.name] = rule
|
||||
create_placement_rule(key_layout, child, odd_counter, key_counter, world, player)
|
||||
doors_completed.add(child)
|
||||
next_counter = find_next_counter(child, key_counter, key_layout)
|
||||
ctr_id = cid(next_counter, key_layout)
|
||||
if ctr_id not in visited_cid:
|
||||
queue.append((child, next_counter))
|
||||
visited_cid.add(ctr_id)
|
||||
possible_smalls_collected = len(key_counter.key_only_locations) + non_big_locs
|
||||
if not key_counter.big_key_opened:
|
||||
if smallest_rule is not None and smallest_rule >= possible_smalls_collected and not avail_bigs:
|
||||
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
|
||||
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
|
||||
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
|
||||
check_rules(original_key_counter, key_layout, world, player)
|
||||
|
||||
# Flip bk rules if more restrictive, to prevent placing a big key in a softlocking location
|
||||
@@ -219,59 +260,49 @@ def analyze_dungeon(key_layout, world, player):
|
||||
max_counter = find_max_counter(key_layout)
|
||||
rule.alternate_big_key_loc = set(max_counter.free_locations.keys()).difference(rule.alternate_big_key_loc)
|
||||
rule.small_key_num, rule.alternate_small_key = rule.alternate_small_key, rule.small_key_num
|
||||
create_exhaustive_placement_rules(key_layout, world, player)
|
||||
set_paired_rules(key_logic, world, player)
|
||||
|
||||
|
||||
def create_placement_rule(key_layout, door, odd_ctr, current_ctr, world, player):
|
||||
def create_exhaustive_placement_rules(key_layout, world, player):
|
||||
key_logic = key_layout.key_logic
|
||||
worst_ctr = find_worst_counter(door, odd_ctr, current_ctr, key_layout, False)
|
||||
sm_num = worst_ctr.used_keys + 1
|
||||
accessible_loc = set()
|
||||
accessible_loc.update(worst_ctr.free_locations)
|
||||
accessible_loc.update(worst_ctr.key_only_locations)
|
||||
worst_ctr_wo_bk, post_ctr, alt_num = find_worst_counter_wo_bk(sm_num, accessible_loc, door, odd_ctr, current_ctr, key_layout)
|
||||
blocked_loc = key_layout.all_locations.difference(accessible_loc)
|
||||
|
||||
if len(blocked_loc) > 0:
|
||||
rule = PlacementRule()
|
||||
rule.door_reference = door
|
||||
rule.small_key = key_logic.small_key_name
|
||||
rule.needed_keys_w_bk = sm_num
|
||||
placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player)
|
||||
rule.check_locations_w_bk = accessible_loc
|
||||
if worst_ctr_wo_bk:
|
||||
accessible_wo_bk, post_set = set(), set()
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
|
||||
post_set.update(post_ctr.free_locations)
|
||||
post_set.update(post_ctr.key_only_locations)
|
||||
blocked_wo_bk = post_set.difference(accessible_wo_bk)
|
||||
if len(blocked_wo_bk) > 0:
|
||||
rule.bk_conditional_set = blocked_wo_bk
|
||||
rule.needed_keys_wo_bk = alt_num
|
||||
# can this self lock a key if bk not avail? I'm thinking no.
|
||||
# placement_self_lock_adjustment(rule, key_layout, ???, worst_ctr_wo_bk, world, player)
|
||||
rule.check_locations_wo_bk = accessible_wo_bk
|
||||
key_logic.placement_rules.append(rule)
|
||||
if worst_ctr_wo_bk:
|
||||
check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num)
|
||||
|
||||
|
||||
def check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num):
|
||||
avail_keys = len(worst_ctr_wo_bk.key_only_locations)
|
||||
place_able_keys = min(key_layout.max_chests, len(worst_ctr_wo_bk.free_locations))
|
||||
if avail_keys + place_able_keys < alt_num:
|
||||
accessible_wo_bk, post_set = set(), set()
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
|
||||
post_set.update(post_ctr.free_locations)
|
||||
post_set.update(post_ctr.key_only_locations)
|
||||
key_layout.key_logic.bk_restricted.update(post_set.difference(accessible_wo_bk))
|
||||
|
||||
|
||||
def placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player):
|
||||
if len(blocked_loc) == 1 and world.accessibility[player] != 'locations':
|
||||
max_ctr = find_max_counter(key_layout)
|
||||
blocked_others = set(max_ctr.other_locations).difference(set(worst_ctr.other_locations))
|
||||
for code, key_counter in key_layout.key_counters.items():
|
||||
accessible_loc = set()
|
||||
accessible_loc.update(key_counter.free_locations)
|
||||
accessible_loc.update(key_counter.key_only_locations)
|
||||
blocked_loc = key_layout.item_locations.difference(accessible_loc)
|
||||
valid_rule = True
|
||||
# min_keys = max(count_unique_sm_doors(key_counter.child_doors), key_counter.used_keys + 1)
|
||||
min_keys = key_counter.used_keys + 1
|
||||
if len(blocked_loc) > 0 and len(key_counter.key_only_locations) < min_keys:
|
||||
rule = PlacementRule()
|
||||
rule.door_reference = code
|
||||
rule.small_key = key_logic.small_key_name
|
||||
if key_counter.big_key_opened or not big_key_progress(key_counter):
|
||||
rule.needed_keys_w_bk = min_keys
|
||||
rule.bk_relevant = key_counter.big_key_opened
|
||||
if key_counter.big_key_opened and rule.needed_keys_w_bk + 1 > len(accessible_loc):
|
||||
valid_rule = False # indicates that the big key cannot be in the accessible locations
|
||||
key_logic.bk_restricted.update(accessible_loc.difference(max_ctr.key_only_locations))
|
||||
else:
|
||||
placement_self_lock_adjustment(rule, max_ctr, blocked_loc, key_counter, world, player)
|
||||
rule.check_locations_w_bk = accessible_loc
|
||||
check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc)
|
||||
else:
|
||||
if big_key_progress(key_counter) and only_sm_doors(key_counter):
|
||||
create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player)
|
||||
rule.bk_conditional_set = blocked_loc
|
||||
rule.needed_keys_wo_bk = min_keys
|
||||
rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc))
|
||||
if valid_rule:
|
||||
key_logic.placement_rules.append(rule)
|
||||
refine_placement_rules(key_layout, max_ctr)
|
||||
|
||||
|
||||
def placement_self_lock_adjustment(rule, max_ctr, blocked_loc, ctr, world, player):
|
||||
if len(blocked_loc) == 1 and world.accessibility[player] != 'locations':
|
||||
blocked_others = set(max_ctr.other_locations).difference(set(ctr.other_locations))
|
||||
important_found = False
|
||||
for loc in blocked_others:
|
||||
if important_location(loc, world, player):
|
||||
@@ -281,13 +312,115 @@ def placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, wor
|
||||
rule.needed_keys_w_bk -= 1
|
||||
|
||||
|
||||
def count_key_drops(sector):
|
||||
cnt = 0
|
||||
for region in sector.regions:
|
||||
for loc in region.locations:
|
||||
if loc.event and 'Small Key' in loc.item.name:
|
||||
cnt += 1
|
||||
return cnt
|
||||
def check_sm_restriction_needed(key_layout, max_ctr, rule, blocked):
|
||||
if rule.needed_keys_w_bk == key_layout.max_chests + len(max_ctr.key_only_locations):
|
||||
key_layout.key_logic.sm_restricted.update(blocked.difference(max_ctr.key_only_locations))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def refine_placement_rules(key_layout, max_ctr):
|
||||
key_logic = key_layout.key_logic
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
rules_to_remove = []
|
||||
for rule in key_logic.placement_rules:
|
||||
if rule.check_locations_w_bk:
|
||||
rule.check_locations_w_bk.difference_update(key_logic.sm_restricted)
|
||||
key_onlys = rule.check_locations_w_bk.intersection(max_ctr.key_only_locations)
|
||||
if len(key_onlys) > 0:
|
||||
rule.check_locations_w_bk.difference_update(key_onlys)
|
||||
rule.needed_keys_w_bk -= len(key_onlys)
|
||||
if rule.needed_keys_w_bk == 0:
|
||||
rules_to_remove.append(rule)
|
||||
if rule.bk_relevant and len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1:
|
||||
new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk
|
||||
if len(new_restricted - key_logic.bk_restricted) > 0:
|
||||
key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations
|
||||
changed = True
|
||||
if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk:
|
||||
logging.getLogger('').warning('Invalid rule - what went wrong here??')
|
||||
rules_to_remove.append(rule)
|
||||
changed = True
|
||||
if rule.bk_conditional_set is not None:
|
||||
rule.bk_conditional_set.difference_update(key_logic.bk_restricted)
|
||||
rule.bk_conditional_set.difference_update(max_ctr.key_only_locations)
|
||||
if len(rule.bk_conditional_set) == 0:
|
||||
rules_to_remove.append(rule)
|
||||
if rule.check_locations_wo_bk:
|
||||
rule.check_locations_wo_bk.difference_update(key_logic.sm_restricted)
|
||||
key_onlys = rule.check_locations_wo_bk.intersection(max_ctr.key_only_locations)
|
||||
if len(key_onlys) > 0:
|
||||
rule.check_locations_wo_bk.difference_update(key_onlys)
|
||||
rule.needed_keys_wo_bk -= len(key_onlys)
|
||||
if rule.needed_keys_wo_bk == 0:
|
||||
rules_to_remove.append(rule)
|
||||
if len(rule.check_locations_wo_bk) < rule.needed_keys_wo_bk or rule.needed_keys_wo_bk > key_layout.max_chests:
|
||||
if len(rule.bk_conditional_set) > 0:
|
||||
key_logic.bk_restricted.update(rule.bk_conditional_set)
|
||||
rules_to_remove.append(rule)
|
||||
changed = True # impossible for bk to be here, I think
|
||||
for rule_a, rule_b in itertools.combinations([x for x in key_logic.placement_rules if x not in rules_to_remove], 2):
|
||||
if rule_b.bk_conditional_set and rule_a.check_locations_w_bk:
|
||||
temp = rule_a
|
||||
rule_a = rule_b
|
||||
rule_b = temp
|
||||
if rule_a.bk_conditional_set and rule_b.check_locations_w_bk:
|
||||
common_needed = min(rule_a.needed_keys_wo_bk, rule_b.needed_keys_w_bk)
|
||||
if len(rule_b.check_locations_w_bk & rule_a.check_locations_wo_bk) < common_needed:
|
||||
key_logic.bk_restricted.update(rule_a.bk_conditional_set)
|
||||
rules_to_remove.append(rule_a)
|
||||
changed = True
|
||||
break
|
||||
equivalent_rules = []
|
||||
for rule in key_logic.placement_rules:
|
||||
for rule2 in key_logic.placement_rules:
|
||||
if rule != rule2:
|
||||
if rule.check_locations_w_bk and rule2.check_locations_w_bk:
|
||||
if rule2.check_locations_w_bk == rule.check_locations_w_bk and rule2.needed_keys_w_bk > rule.needed_keys_w_bk:
|
||||
rules_to_remove.append(rule)
|
||||
elif rule2.needed_keys_w_bk == rule.needed_keys_w_bk and rule2.check_locations_w_bk < rule.check_locations_w_bk:
|
||||
rules_to_remove.append(rule)
|
||||
elif rule2.check_locations_w_bk == rule.check_locations_w_bk and rule2.needed_keys_w_bk == rule.needed_keys_w_bk:
|
||||
equivalent_rules.append((rule, rule2))
|
||||
if rule.check_locations_wo_bk and rule2.check_locations_wo_bk and rule.bk_conditional_set == rule2.bk_conditional_set:
|
||||
if rule2.check_locations_wo_bk == rule.check_locations_wo_bk and rule2.needed_keys_wo_bk > rule.needed_keys_wo_bk:
|
||||
rules_to_remove.append(rule)
|
||||
elif rule2.needed_keys_wo_bk == rule.needed_keys_wo_bk and rule2.check_locations_wo_bk < rule.check_locations_wo_bk:
|
||||
rules_to_remove.append(rule)
|
||||
elif rule2.check_locations_wo_bk == rule.check_locations_wo_bk and rule2.needed_keys_wo_bk == rule.needed_keys_wo_bk:
|
||||
equivalent_rules.append((rule, rule2))
|
||||
if len(rules_to_remove) > 0:
|
||||
key_logic.placement_rules = [x for x in key_logic.placement_rules if x not in rules_to_remove]
|
||||
equivalent_rules = [x for x in equivalent_rules if x[0] not in rules_to_remove and x[1] not in rules_to_remove]
|
||||
if len(equivalent_rules) > 0:
|
||||
removed_rules = {}
|
||||
for r1, r2 in equivalent_rules:
|
||||
if r1 in removed_rules.keys():
|
||||
r1 = removed_rules[r1]
|
||||
if r2 in removed_rules.keys():
|
||||
r2 = removed_rules[r2]
|
||||
if r1 != r2:
|
||||
r1.door_reference += ','+r2.door_reference
|
||||
key_logic.placement_rules.remove(r2)
|
||||
removed_rules[r2] = r1
|
||||
|
||||
|
||||
def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player):
|
||||
key_logic = key_layout.key_logic
|
||||
rule = PlacementRule()
|
||||
rule.door_reference = code
|
||||
rule.small_key = key_logic.small_key_name
|
||||
rule.needed_keys_w_bk = min_keys
|
||||
if key_counter.big_key_opened and rule.needed_keys_w_bk + 1 > len(accessible_loc):
|
||||
# indicates that the big key cannot be in the accessible locations
|
||||
key_logic.bk_restricted.update(accessible_loc.difference(max_ctr.key_only_locations))
|
||||
else:
|
||||
placement_self_lock_adjustment(rule, max_ctr, blocked_loc, key_counter, world, player)
|
||||
rule.check_locations_w_bk = accessible_loc
|
||||
check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc)
|
||||
key_logic.placement_rules.append(rule)
|
||||
|
||||
|
||||
def queue_sorter(queue_item):
|
||||
@@ -314,8 +447,8 @@ def find_bk_locked_sections(key_layout, world, player):
|
||||
big_chest_allowed_big_key = world.accessibility[player] != 'locations'
|
||||
for counter in key_counters.values():
|
||||
key_layout.all_chest_locations.update(counter.free_locations)
|
||||
key_layout.all_locations.update(counter.free_locations)
|
||||
key_layout.all_locations.update(counter.key_only_locations)
|
||||
key_layout.item_locations.update(counter.free_locations)
|
||||
key_layout.item_locations.update(counter.key_only_locations)
|
||||
if counter.big_key_opened and counter.important_location:
|
||||
big_chest_allowed_big_key = False
|
||||
if not counter.big_key_opened:
|
||||
@@ -710,6 +843,16 @@ def count_unique_sm_doors(doors):
|
||||
return len(unique_d_set)
|
||||
|
||||
|
||||
def big_key_progress(key_counter):
|
||||
return not only_sm_doors(key_counter) or exist_big_chest(key_counter)
|
||||
|
||||
|
||||
def only_sm_doors(key_counter):
|
||||
for door in key_counter.child_doors:
|
||||
if door.bigKey:
|
||||
return False
|
||||
return True
|
||||
|
||||
# doesn't count dest doors
|
||||
def count_unique_small_doors(key_counter, proposal):
|
||||
cnt = 0
|
||||
@@ -738,6 +881,13 @@ def exist_relevant_big_doors(key_counter, key_layout):
|
||||
return False
|
||||
|
||||
|
||||
def exist_big_chest(key_counter):
|
||||
for loc in key_counter.free_locations:
|
||||
if '- Big Chest' in loc.name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def count_locations_big_optional(locations, bk=False):
|
||||
cnt = 0
|
||||
for loc in locations:
|
||||
@@ -989,6 +1139,13 @@ def reduce_rules(small_rules, collected, collected_alt):
|
||||
rule.small_key_num = collected
|
||||
|
||||
|
||||
def set_paired_rules(key_logic, world, player):
|
||||
for d_name, rule in key_logic.door_rules.items():
|
||||
door = world.get_door(d_name, player)
|
||||
if door.dest.name in key_logic.door_rules.keys():
|
||||
rule.opposite = key_logic.door_rules[door.dest.name]
|
||||
|
||||
|
||||
# Soft lock stuff
|
||||
def validate_key_layout(key_layout, world, player):
|
||||
# retro is all good - except for hyrule castle in standard mode
|
||||
@@ -1015,7 +1172,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
ttl_key_only = count_key_only_locations(state)
|
||||
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_key_only, state, world, player)
|
||||
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
||||
if invalid_self_locking_key(state, prev_state, prev_avail, world, player):
|
||||
if invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player):
|
||||
return False
|
||||
# todo: allow more key shuffles - refine placement rules
|
||||
# if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||
@@ -1054,18 +1211,24 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
return True
|
||||
|
||||
|
||||
def invalid_self_locking_key(state, prev_state, prev_avail, world, player):
|
||||
def invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player):
|
||||
if prev_state is None or state.used_smalls == prev_state.used_smalls:
|
||||
return False
|
||||
new_locations = set(state.found_locations).difference(set(prev_state.found_locations))
|
||||
new_bk_doors = set(state.big_doors).difference(set(prev_state.big_doors))
|
||||
state_copy = state.copy()
|
||||
while len(new_bk_doors) > 0:
|
||||
for door in new_bk_doors:
|
||||
open_a_door(door.door, state_copy, key_layout.flat_prop)
|
||||
new_bk_doors = set(state_copy.big_doors).difference(set(prev_state.big_doors))
|
||||
expand_key_state(state_copy, key_layout.flat_prop, world, player)
|
||||
new_locations = set(state_copy.found_locations).difference(set(prev_state.found_locations))
|
||||
important_found = False
|
||||
for loc in new_locations:
|
||||
important_found |= important_location(loc, world, player)
|
||||
if not important_found:
|
||||
return False
|
||||
new_small_doors = set(state.small_doors).difference(set(prev_state.small_doors))
|
||||
new_bk_doors = set(state.big_doors).difference(set(prev_state.big_doors))
|
||||
if len(new_small_doors) > 0 or len(new_bk_doors) > 0:
|
||||
if len(new_small_doors) > 0:
|
||||
return False
|
||||
return prev_avail - 1 == 0
|
||||
|
||||
@@ -1151,11 +1314,21 @@ def create_key_counter(state, key_layout, world, player):
|
||||
return key_counter
|
||||
|
||||
|
||||
def important_location(loc, world, player):
|
||||
important_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
|
||||
imp_locations = None
|
||||
|
||||
|
||||
def imp_locations_factory(world, player):
|
||||
global imp_locations
|
||||
if imp_locations:
|
||||
return imp_locations
|
||||
imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
|
||||
if world.mode[player] == 'standard' or world.doorShuffle[player] == 'crossed':
|
||||
important_locations.append('Hyrule Dungeon Cellblock')
|
||||
return '- Prize' in loc.name or loc.name in important_locations
|
||||
imp_locations.append('Hyrule Dungeon Cellblock')
|
||||
return imp_locations
|
||||
|
||||
|
||||
def important_location(loc, world, player):
|
||||
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player)
|
||||
|
||||
|
||||
def create_odd_key_counter(door, parent_counter, key_layout, world, player):
|
||||
@@ -1428,7 +1601,7 @@ def validate_key_placement(key_layout, world, player):
|
||||
max_counter = find_max_counter(key_layout)
|
||||
big_key_outside = dungeon.big_key not in (l.item for l in max_counter.free_locations)
|
||||
|
||||
for counter in key_layout.key_counters.values():
|
||||
for code, counter in key_layout.key_counters.items():
|
||||
if len(counter.child_doors) == 0:
|
||||
continue
|
||||
big_found = any(i.item == dungeon.big_key for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
|
||||
@@ -1445,6 +1618,7 @@ def validate_key_placement(key_layout, world, player):
|
||||
# missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations?
|
||||
if len(missing_items) > 0: # world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0):
|
||||
logging.getLogger('').error("Keylock - can't open locations: ")
|
||||
logging.getLogger('').error("code: " + code)
|
||||
for i in missing_locations:
|
||||
logging.getLogger('').error(i)
|
||||
return False
|
||||
|
||||
@@ -24,10 +24,14 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||
from Utils import output_path, parse_player_names
|
||||
|
||||
__version__ = '0.0.h-dev'
|
||||
__version__ = '0.0.22.0u'
|
||||
|
||||
|
||||
def main(args, seed=None):
|
||||
class EnemizerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def main(args, seed=None, fish=None):
|
||||
if args.outputpath:
|
||||
os.makedirs(args.outputpath, exist_ok=True)
|
||||
output_path.cached_path = args.outputpath
|
||||
@@ -59,10 +63,15 @@ def main(args, seed=None):
|
||||
world.beemizer = args.beemizer.copy()
|
||||
world.experimental = args.experimental.copy()
|
||||
world.dungeon_counters = args.dungeon_counters.copy()
|
||||
world.fish = fish
|
||||
|
||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||
|
||||
logger.info('ALttP Door Randomizer Version %s - Seed: %s\n', __version__, world.seed)
|
||||
logger.info(
|
||||
world.fish.translate("cli","cli","app.title") + "\n",
|
||||
__version__,
|
||||
world.seed
|
||||
)
|
||||
|
||||
parsed_names = parse_player_names(args.names, world.players, args.teams)
|
||||
world.teams = len(parsed_names)
|
||||
@@ -77,6 +86,7 @@ def main(args, seed=None):
|
||||
world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
|
||||
|
||||
if world.mode[player] == 'standard' and world.enemy_shuffle[player] != 'none':
|
||||
if hasattr(world,"escape_assist") and player in world.escape_assist:
|
||||
world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it
|
||||
|
||||
for tok in filter(None, args.startinventory[player].split(',')):
|
||||
@@ -94,7 +104,7 @@ def main(args, seed=None):
|
||||
create_rooms(world, player)
|
||||
create_dungeons(world, player)
|
||||
|
||||
logger.info('Shuffling the World about.')
|
||||
logger.info(world.fish.translate("cli","cli","shuffling.world"))
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
if world.mode[player] != 'inverted':
|
||||
@@ -102,7 +112,7 @@ def main(args, seed=None):
|
||||
else:
|
||||
link_inverted_entrances(world, player)
|
||||
|
||||
logger.info('Shuffling dungeons')
|
||||
logger.info(world.fish.translate("cli","cli","shuffling.dungeons"))
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
link_doors(world, player)
|
||||
@@ -110,21 +120,21 @@ def main(args, seed=None):
|
||||
mark_light_world_regions(world, player)
|
||||
else:
|
||||
mark_dark_world_regions(world, player)
|
||||
logger.info('Generating Item Pool.')
|
||||
logger.info(world.fish.translate("cli","cli","generating.itempool"))
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
generate_itempool(world, player)
|
||||
|
||||
logger.info('Calculating Access Rules.')
|
||||
logger.info(world.fish.translate("cli","cli","calc.access.rules"))
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
set_rules(world, player)
|
||||
|
||||
logger.info('Placing Dungeon Prizes.')
|
||||
logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes"))
|
||||
|
||||
fill_prizes(world)
|
||||
|
||||
logger.info('Placing Dungeon Items.')
|
||||
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
||||
|
||||
shuffled_locations = None
|
||||
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
||||
@@ -138,9 +148,17 @@ def main(args, seed=None):
|
||||
for player in range(1, world.players+1):
|
||||
for key_layout in world.key_layout[player].values():
|
||||
if not validate_key_placement(key_layout, world, player):
|
||||
raise RuntimeError("Keylock detected: %s (Player %d)" % (key_layout.sector.name, player))
|
||||
raise RuntimeError(
|
||||
"%s: %s (%s %d)" %
|
||||
(
|
||||
world.fish.translate("cli","cli","keylock.detected"),
|
||||
key_layout.sector.name,
|
||||
world.fish.translate("cli","cli","player"),
|
||||
player
|
||||
)
|
||||
)
|
||||
|
||||
logger.info('Fill the world.')
|
||||
logger.info(world.fish.translate("cli","cli","fill.world"))
|
||||
|
||||
if args.algorithm == 'flood':
|
||||
flood_items(world) # different algo, biased towards early game progress items
|
||||
@@ -159,20 +177,20 @@ def main(args, seed=None):
|
||||
distribute_items_restrictive(world, True)
|
||||
|
||||
if world.players > 1:
|
||||
logger.info('Balancing multiworld progression.')
|
||||
logger.info(world.fish.translate("cli","cli","balance.multiworld"))
|
||||
balance_multiworld_progression(world)
|
||||
|
||||
# if we only check for beatable, we can do this sanity check first before creating the rom
|
||||
if not world.can_beat_game():
|
||||
raise RuntimeError('Cannot beat game. Something went terribly wrong here!')
|
||||
|
||||
logger.info('Patching ROM.')
|
||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game"))
|
||||
|
||||
outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed)
|
||||
|
||||
rom_names = []
|
||||
jsonout = {}
|
||||
enemized = False
|
||||
if not args.suppress_rom:
|
||||
logger.info(world.fish.translate("cli","cli","patching.rom"))
|
||||
for team in range(world.teams):
|
||||
for player in range(1, world.players + 1):
|
||||
sprite_random_on_hit = type(args.sprite[player]) is str and args.sprite[player].lower() == 'randomonhit'
|
||||
@@ -182,16 +200,21 @@ def main(args, seed=None):
|
||||
|
||||
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
|
||||
|
||||
patch_rom(world, rom, player, team, use_enemizer)
|
||||
|
||||
if use_enemizer and (args.enemizercli or not args.jsonout):
|
||||
if args.rom and not(os.path.isfile(args.rom)):
|
||||
raise RuntimeError("Could not find valid base rom for enemizing at expected path %s." % args.rom)
|
||||
if os.path.exists(args.enemizercli):
|
||||
patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit)
|
||||
enemized = True
|
||||
if not args.jsonout:
|
||||
rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000)
|
||||
else:
|
||||
logging.warning("EnemizerCLI not found at:" + args.enemizercli)
|
||||
logging.warning("No Enemizer options will be applied until this is resolved.")
|
||||
enemizerMsg = world.fish.translate("cli","cli","enemizer.not.found") + ': ' + args.enemizercli + "\n"
|
||||
enemizerMsg += world.fish.translate("cli","cli","enemizer.nothing.applied")
|
||||
logging.warning(enemizerMsg)
|
||||
raise EnemizerError(enemizerMsg)
|
||||
|
||||
patch_rom(world, rom, player, team, enemized)
|
||||
|
||||
if args.race:
|
||||
patch_race_rom(rom)
|
||||
@@ -219,13 +242,44 @@ def main(args, seed=None):
|
||||
outfilepname += f'_P{player}'
|
||||
if world.players > 1 or world.teams > 1:
|
||||
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
|
||||
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s_%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], world.difficulty_adjustments[player],
|
||||
world.mode[player], world.goal[player],
|
||||
"" if world.timer in ['none', 'display'] else "-" + world.timer,
|
||||
world.shuffle[player], world.doorShuffle[player], world.algorithm, mcsb_name,
|
||||
"-retro" if world.retro[player] else "",
|
||||
"-prog_" + world.progressive if world.progressive in ['off', 'random'] else "",
|
||||
"-nohints" if not world.hints[player] else "")) if not args.outputname else ''
|
||||
outfilestuffs = {
|
||||
"logic": world.logic[player], # 0
|
||||
"difficulty": world.difficulty[player], # 1
|
||||
"difficulty_adjustments": world.difficulty_adjustments[player], # 2
|
||||
"mode": world.mode[player], # 3
|
||||
"goal": world.goal[player], # 4
|
||||
"timer": str(world.timer), # 5
|
||||
"shuffle": world.shuffle[player], # 6
|
||||
"doorShuffle": world.doorShuffle[player], # 7
|
||||
"algorithm": world.algorithm, # 8
|
||||
"mscb": mcsb_name, # 9
|
||||
"retro": world.retro[player], # A
|
||||
"progressive": world.progressive, # B
|
||||
"hints": 'True' if world.hints[player] else 'False' # C
|
||||
}
|
||||
# 0 1 2 3 4 5 6 7 8 9 A B C
|
||||
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s_%s-%s%s%s%s%s' % (
|
||||
# 0 1 2 3 4 5 6 7 8 9 A B C
|
||||
# _noglitches_normal-normal-open-ganon-ohko_simple_basic-balanced-keysanity-retro-prog_swords-nohints
|
||||
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity-retro
|
||||
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity -prog_swords
|
||||
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity -nohints
|
||||
outfilestuffs["logic"], # 0
|
||||
|
||||
outfilestuffs["difficulty"], # 1
|
||||
outfilestuffs["difficulty_adjustments"], # 2
|
||||
outfilestuffs["mode"], # 3
|
||||
outfilestuffs["goal"], # 4
|
||||
"" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5
|
||||
|
||||
outfilestuffs["shuffle"], # 6
|
||||
outfilestuffs["doorShuffle"], # 7
|
||||
outfilestuffs["algorithm"], # 8
|
||||
outfilestuffs["mscb"], # 9
|
||||
|
||||
"-retro" if outfilestuffs["retro"] == "True" else "", # A
|
||||
"-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # B
|
||||
"-nohints" if not outfilestuffs["hints"] == "True" else "")) if not args.outputname else '' # C
|
||||
rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc'))
|
||||
|
||||
if world.players > 1:
|
||||
@@ -241,20 +295,34 @@ def main(args, seed=None):
|
||||
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
||||
f.write(multidata)
|
||||
|
||||
if args.create_spoiler and not args.jsonout:
|
||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||
|
||||
if not args.skip_playthrough:
|
||||
logger.info('Calculating playthrough.')
|
||||
logger.info(world.fish.translate("cli","cli","calc.playthrough"))
|
||||
create_playthrough(world)
|
||||
|
||||
if args.jsonout:
|
||||
print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()}))
|
||||
elif args.create_spoiler and not args.skip_playthrough:
|
||||
elif args.create_spoiler:
|
||||
logger.info(world.fish.translate("cli","cli","patching.spoiler"))
|
||||
if args.jsonout:
|
||||
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
|
||||
outfile.write(world.spoiler.to_json())
|
||||
else:
|
||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||
|
||||
logger.info('Done. Enjoy.')
|
||||
logger.info('Total Time: %s', time.perf_counter() - start)
|
||||
YES = world.fish.translate("cli","cli","yes")
|
||||
NO = world.fish.translate("cli","cli","no")
|
||||
logger.info("")
|
||||
logger.info(world.fish.translate("cli","cli","done"))
|
||||
logger.info("")
|
||||
logger.info(world.fish.translate("cli","cli","made.rom") % (YES if (args.create_rom) else NO))
|
||||
logger.info(world.fish.translate("cli","cli","made.playthrough") % (YES if (args.calc_playthrough) else NO))
|
||||
logger.info(world.fish.translate("cli","cli","made.spoiler") % (YES if (not args.jsonout and args.create_spoiler) else NO))
|
||||
logger.info(world.fish.translate("cli","cli","used.enemizer") % (YES if enemized else NO))
|
||||
logger.info(world.fish.translate("cli","cli","seed") + ": %d", world.seed)
|
||||
logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start)
|
||||
|
||||
# print_wiki_doors_by_room(dungeon_regions,world,1)
|
||||
# print_wiki_doors_by_region(dungeon_regions,world,1)
|
||||
|
||||
return world
|
||||
|
||||
@@ -398,7 +466,7 @@ def create_playthrough(world):
|
||||
collection_spheres = []
|
||||
state = CollectionState(world)
|
||||
sphere_candidates = list(prog_locations)
|
||||
logging.getLogger('').debug('Building up collection spheres.')
|
||||
logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres"))
|
||||
while sphere_candidates:
|
||||
state.sweep_for_events(key_only=True)
|
||||
state.sweep_for_crystal_access()
|
||||
@@ -417,11 +485,11 @@ def create_playthrough(world):
|
||||
|
||||
state_cache.append(state.copy())
|
||||
|
||||
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations))
|
||||
logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
||||
if not sphere:
|
||||
logging.getLogger('').debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
||||
logging.getLogger('').debug(world.fish.translate("cli","cli","cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
||||
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
||||
raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.')
|
||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
|
||||
else:
|
||||
old_world.spoiler.unreachables = sphere_candidates.copy()
|
||||
break
|
||||
@@ -473,9 +541,9 @@ def create_playthrough(world):
|
||||
|
||||
collection_spheres.append(sphere)
|
||||
|
||||
logging.getLogger('').debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations))
|
||||
logging.getLogger('').debug(world.fish.translate("cli","cli","building.final.spheres"), len(collection_spheres), len(sphere), len(required_locations))
|
||||
if not sphere:
|
||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required"))
|
||||
|
||||
# store the required locations for statistical analysis
|
||||
old_world.required_locations = [(location.name, location.player) for sphere in collection_spheres for location in sphere]
|
||||
|
||||
+8
-3
@@ -5,8 +5,9 @@ import urllib.request
|
||||
import urllib.parse
|
||||
import re
|
||||
|
||||
from DungeonRandomizer import parse_arguments
|
||||
from DungeonRandomizer import parse_cli
|
||||
from Main import main as DRMain
|
||||
from source.classes.BabelFish import BabelFish
|
||||
|
||||
def parse_yaml(txt):
|
||||
def strip(s):
|
||||
@@ -71,7 +72,7 @@ def main():
|
||||
weights_cache[path] = get_weights(path)
|
||||
print(f"P{player} Weights: {path} >> {weights_cache[path]['description']}")
|
||||
|
||||
erargs = parse_arguments(['--multi', str(args.multi)])
|
||||
erargs = parse_cli(['--multi', str(args.multi)])
|
||||
erargs.seed = seed
|
||||
erargs.names = args.names
|
||||
erargs.create_spoiler = args.create_spoiler
|
||||
@@ -100,7 +101,7 @@ def main():
|
||||
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)
|
||||
DRMain(erargs, seed, BabelFish())
|
||||
|
||||
def get_weights(path):
|
||||
try:
|
||||
@@ -150,6 +151,10 @@ def roll_settings(weights):
|
||||
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
||||
ret.experimental = get_choice('experimental') == 'on'
|
||||
|
||||
ret.dungeon_counters = get_choice('dungeon_counters')
|
||||
if ret.dungeon_counters == 'default':
|
||||
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
|
||||
|
||||
goal = get_choice('goals')
|
||||
ret.goal = {'ganon': 'ganon',
|
||||
'fast_ganon': 'crystals',
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# New Features
|
||||
|
||||
* Mirror Scroll no longer erases blocks, the real mirror still will. (Sorry!)
|
||||
* Standard+Crossed Dungeon now gives you a little magic, a few bombs, and a few arrows if you die or S&Q after meeting your uncle (also works with mirror/scroll)
|
||||
* Dungeon reminder added to hud for Crossed dungeons
|
||||
* Blinking red square added to hud and it indicates a boss room is close by. Only appears if you have the compass. (Basic & Crossed)
|
||||
* Agahnims dungeon items can be started with now
|
||||
* GUI updates courtesy of Mike T
|
||||
|
||||
## Map Features (Crossed only + Experimental)
|
||||
|
||||
* Key counters added to hud. Indicates number of keys in chests (found/total). In small key shuffle, this count indicates how many smalls for that dungeon could be outside it.
|
||||
* Total key indicator added to hud if you have found the map. Counts down from the total number of keys in dungeon to 0 as you collect them.
|
||||
* Big Key indicator added to hud. Indicates if BK is not in the dungeon, or if BnC guard has it (Probably will move away from hud if kept)
|
||||
|
||||
Note: Only one of the key indicator will probably become core at most.
|
||||
|
||||
## Experimental changes
|
||||
|
||||
* Mirror scroll is now core for non-vanilla Door Shuffle (no longer experimental)
|
||||
* GT Bosses stay dead in non-vanilla Door Shuffle (no longer experimental)
|
||||
* Map features listed above are now experimental
|
||||
|
||||
# Bug Fixes
|
||||
|
||||
* Splashing at hobo no longer prevents you from buying bomb capacity upgrades
|
||||
* Small vitreous eyeballs will not drop items (DR basic and crossed only)
|
||||
* In Vanilla doors the HC back hallway area was broken - should be better now - also Trap Doors
|
||||
* Firebar speed should now be consistent. Ice palace rooms have slow firebars even if shuffled to other dungeons. Others should have normal speed firebars.
|
||||
+2
-1
@@ -540,7 +540,8 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'Mire 2', 'Misery Mire', None, ['Mire 2 Up Stairs', 'Mire 2 NE']),
|
||||
create_dungeon_region(player, 'Mire Hub', 'Misery Mire', None, ['Mire Hub SE', 'Mire Hub ES', 'Mire Hub E', 'Mire Hub NE', 'Mire Hub WN', 'Mire Hub WS', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier']),
|
||||
create_dungeon_region(player, 'Mire Hub Right', 'Misery Mire', None, ['Mire Hub Right EN', 'Mire Hub Right Blue Barrier']),
|
||||
create_dungeon_region(player, 'Mire Hub Top', 'Misery Mire', ['Misery Mire - Main Lobby'], ['Mire Hub Top NW', 'Mire Hub Top Blue Barrier']),
|
||||
create_dungeon_region(player, 'Mire Hub Top', 'Misery Mire', None, ['Mire Hub Top NW', 'Mire Hub Top Blue Barrier']),
|
||||
create_dungeon_region(player, 'Mire Hub Switch', 'Misery Mire', ['Misery Mire - Main Lobby'], ['Mire Hub Switch Blue Barrier N', 'Mire Hub Switch Blue Barrier S']),
|
||||
create_dungeon_region(player, 'Mire Lone Shooter', 'Misery Mire', None, ['Mire Lone Shooter WS', 'Mire Lone Shooter ES']),
|
||||
create_dungeon_region(player, 'Mire Failure Bridge', 'Misery Mire', None, ['Mire Failure Bridge W', 'Mire Failure Bridge E']),
|
||||
create_dungeon_region(player, 'Mire Falling Bridge', 'Misery Mire', ['Misery Mire - Big Chest'], ['Mire Falling Bridge WS', 'Mire Falling Bridge W', 'Mire Falling Bridge WN']),
|
||||
|
||||
@@ -10,7 +10,7 @@ import sys
|
||||
import subprocess
|
||||
|
||||
from BaseClasses import CollectionState, ShopType, Region, Location, DoorType
|
||||
from DoorShuffle import compass_data, DROptions
|
||||
from DoorShuffle import compass_data, DROptions, boss_indicator
|
||||
from Dungeons import dungeon_music_addresses
|
||||
from Regions import location_table
|
||||
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
|
||||
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '5e01caffabb4509a0987ef2f2f0bcd56'
|
||||
RANDOMIZERBASEHASH = 'a4d716a9c9b3299267deee4ddb1143a5'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -78,6 +78,8 @@ class LocalRom(object):
|
||||
self.name = name
|
||||
self.hash = hash
|
||||
self.orig_buffer = None
|
||||
if not os.path.isfile(file):
|
||||
raise RuntimeError("Could not find valid local base rom for patching at expected path %s." % file)
|
||||
with open(file, 'rb') as stream:
|
||||
self.buffer = read_rom(stream)
|
||||
if patch:
|
||||
@@ -160,7 +162,7 @@ def read_rom(stream):
|
||||
|
||||
def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, random_sprite_on_hit):
|
||||
baserom_path = os.path.abspath(baserom_path)
|
||||
basepatch_path = os.path.abspath(local_path('data/base2current.json'))
|
||||
basepatch_path = os.path.abspath(local_path(os.path.join("data","base2current.json")))
|
||||
enemizer_basepatch_path = os.path.join(os.path.dirname(enemizercli), "enemizerBasePatch.json")
|
||||
randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json'))
|
||||
options_path = os.path.abspath(output_path('enemizer_options.json'))
|
||||
@@ -303,7 +305,7 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, r
|
||||
_sprite_table = {}
|
||||
def _populate_sprite_table():
|
||||
if not _sprite_table:
|
||||
for dir in [local_path('data/sprites/official'), local_path('data/sprites/unofficial')]:
|
||||
for dir in [local_path(os.path.join("data","sprites","official")), local_path(os.path.join("data","sprites","unofficial"))]:
|
||||
for file in os.listdir(dir):
|
||||
filepath = os.path.join(dir, file)
|
||||
if not os.path.isfile(filepath):
|
||||
@@ -383,7 +385,7 @@ class Sprite(object):
|
||||
|
||||
@staticmethod
|
||||
def default_link_sprite():
|
||||
return Sprite(local_path('data/default.zspr'))
|
||||
return get_sprite_from_name('Link')
|
||||
|
||||
def decode8(self, pos):
|
||||
arr = [[0 for _ in range(8)] for _ in range(8)]
|
||||
@@ -590,10 +592,22 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
if world.mode[player] == 'inverted':
|
||||
patch_shuffled_dark_sanc(world, rom, player)
|
||||
|
||||
# setup dr option flags based on experimental, etc.
|
||||
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
|
||||
if world.experimental[player]:
|
||||
dr_flags |= DROptions.Map_Info
|
||||
|
||||
# patch doors
|
||||
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' or not world.experimental[player] else DROptions.Town_Portal
|
||||
if world.doorShuffle[player] == 'crossed':
|
||||
rom.write_byte(0x139004, 2)
|
||||
for name, layout in world.key_layout[player].items():
|
||||
offset = compass_data[name][4]//2
|
||||
rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops)
|
||||
rom.write_byte(0x13f02a+offset, layout.max_chests)
|
||||
builder = world.dungeon_layouts[player][name]
|
||||
bk_status = 1 if builder.bk_required else 0
|
||||
bk_status = 2 if builder.bk_provided else bk_status
|
||||
rom.write_byte(0x13f038+offset*2, bk_status)
|
||||
rom.write_byte(0x151f1, 2)
|
||||
rom.write_byte(0x15270, 2)
|
||||
rom.write_byte(0x1597b, 2)
|
||||
@@ -617,6 +631,13 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
if builder.pre_open_stonewall:
|
||||
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW':
|
||||
dr_flags |= DROptions.Open_Desert_Wall
|
||||
for name, pair in boss_indicator.items():
|
||||
dungeon_id, boss_door = pair
|
||||
opposite_door = world.get_door(boss_door, player).dest
|
||||
if opposite_door.roomIndex > -1:
|
||||
dungeon_name = opposite_door.entrance.parent_region.dungeon.name
|
||||
dungeon_id = boss_indicator[dungeon_name][0]
|
||||
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
|
||||
rom.write_byte(0x139006, dr_flags.value)
|
||||
if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted':
|
||||
rom.write_byte(0x139008, 1)
|
||||
@@ -1043,6 +1064,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10),
|
||||
'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20),
|
||||
'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0),
|
||||
'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08),
|
||||
'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02),
|
||||
'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10),
|
||||
'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80),
|
||||
@@ -1227,6 +1249,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
rom.write_bytes(0x180185, [0,0,0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0,0,0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0,0,0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
bow_max, bomb_max, magic_max = 0, 0, 0
|
||||
if world.mode[player] == 'standard':
|
||||
if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']:
|
||||
rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
|
||||
@@ -1234,16 +1257,25 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
rom.write_bytes(0x180185, [0,0,70]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0,0,10]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0,0,10]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
bow_max = 70
|
||||
elif uncle_location.item is not None and uncle_location.item.name in ['Bombs (10)']:
|
||||
rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
|
||||
rom.write_bytes(0x180185, [0,50,0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0,3,0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0,3,0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
bomb_max = 50
|
||||
elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']:
|
||||
rom.write_byte(0x18004E, 4) # Escape Fill (magic)
|
||||
rom.write_bytes(0x180185, [0x80,0,0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0x20,0,0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0x20,0,0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
magic_max = 0x80
|
||||
if world.doorShuffle[player] == 'crossed':
|
||||
# Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)])
|
||||
rom.write_bytes(0x180188, [0x20, 3, 10]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0x20, 3, 10]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
|
||||
|
||||
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
|
||||
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)
|
||||
|
||||
@@ -342,6 +342,8 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Mire Hub Lower Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
|
||||
set_rule(world.get_entrance('Mire Hub Right Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Right', player), player))
|
||||
set_rule(world.get_entrance('Mire Hub Top Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Top', player), player))
|
||||
set_rule(world.get_entrance('Mire Hub Switch Blue Barrier N', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Switch', player), player))
|
||||
set_rule(world.get_entrance('Mire Hub Switch Blue Barrier S', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Switch', player), player))
|
||||
set_rule(world.get_entrance('Mire Map Spike Side Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spike Side', player), player))
|
||||
set_rule(world.get_entrance('Mire Map Spot Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spot', player), player))
|
||||
set_rule(world.get_entrance('Mire Crystal Dead End Left Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
|
||||
@@ -1516,7 +1518,8 @@ bunny_impassible_doors = {
|
||||
'Ice Crystal Right Blue Hole', 'Ice Crystal Left Blue Barrier', 'Ice Big Chest Landing Push Blocks',
|
||||
'Ice Backwards Room Hole', 'Ice Switch Room SE', 'Ice Antechamber NE', 'Ice Antechamber Hole', 'Mire Lobby Gap',
|
||||
'Mire Post-Gap Gap', 'Mire 2 NE', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier',
|
||||
'Mire Hub Right Blue Barrier', 'Mire Hub Top Blue Barrier', 'Mire Falling Bridge WN',
|
||||
'Mire Hub Right Blue Barrier', 'Mire Hub Top Blue Barrier', 'Mire Hub Switch Blue Barrier N',
|
||||
'Mire Hub Switch Blue Barrier S', 'Mire Falling Bridge WN',
|
||||
'Mire Map Spike Side Blue Barrier', 'Mire Map Spot Blue Barrier', 'Mire Crystal Dead End Left Barrier',
|
||||
'Mire Crystal Dead End Right Barrier', 'Mire Cross ES', 'Mire Hidden Shooters Block Path S',
|
||||
'Mire Hidden Shooters Block Path N', 'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
|
||||
@@ -1546,7 +1549,10 @@ def add_key_logic_rules(world, player):
|
||||
key_logic = world.key_logic[player]
|
||||
for d_name, d_logic in key_logic.items():
|
||||
for door_name, keys in d_logic.door_rules.items():
|
||||
add_rule(world.get_entrance(door_name, player), create_advanced_key_rule(d_logic, player, keys))
|
||||
spot = world.get_entrance(door_name, player)
|
||||
add_rule(spot, create_advanced_key_rule(d_logic, player, keys))
|
||||
if keys.opposite:
|
||||
add_rule(spot, create_advanced_key_rule(d_logic, player, keys.opposite), 'or')
|
||||
for location in d_logic.bk_restricted:
|
||||
if location.name not in key_only_locations.keys():
|
||||
forbid_item(location, d_logic.bk_name, player)
|
||||
|
||||
@@ -76,12 +76,16 @@ def output_path(path):
|
||||
NSUserDomainMask = 1
|
||||
# True for expanding the tilde into a fully qualified path
|
||||
documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]
|
||||
elif sys.platform.find("linux") or sys.platform.find("ubuntu") or sys.platform.find("unix"):
|
||||
documents = os.path.join(os.path.expanduser("~"),"Documents")
|
||||
else:
|
||||
raise NotImplementedError('Not supported yet')
|
||||
|
||||
output_path.cached_path = os.path.join(documents, 'ALttPEntranceRandomizer')
|
||||
output_path.cached_path = os.path.join(documents, 'ALttPDoorRandomizer')
|
||||
if not os.path.exists(output_path.cached_path):
|
||||
os.mkdir(output_path.cached_path)
|
||||
os.makedirs(output_path.cached_path)
|
||||
if not os.path.join(output_path.cached_path, path):
|
||||
os.makedirs(os.path.join(output_path.cached_path, path))
|
||||
return os.path.join(output_path.cached_path, path)
|
||||
|
||||
output_path.cached_path = None
|
||||
@@ -201,8 +205,7 @@ def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan)
|
||||
print(string)
|
||||
|
||||
|
||||
def print_wiki_doors(d_regions, world, player):
|
||||
|
||||
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:
|
||||
@@ -217,28 +220,140 @@ def print_wiki_doors(d_regions, world, player):
|
||||
if tile not in tile_map:
|
||||
tile_map[tile] = []
|
||||
tile_map[tile].append(r)
|
||||
print(d)
|
||||
print('{| class="wikitable"')
|
||||
print('|-')
|
||||
print('! Room')
|
||||
print('! Supertile')
|
||||
print('! Doors')
|
||||
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:
|
||||
print('|-')
|
||||
print('| '+region.name)
|
||||
toprint += ('|-') + "\n"
|
||||
toprint += ('| {{Dungeon Room|{{PAGENAME}}|' + region.name + '}}') + "\n"
|
||||
if not tile_done:
|
||||
listlen = len(region_list)
|
||||
link = '| {{UnderworldMapLink|'+str(tile)+'}}'
|
||||
print(link if listlen < 2 else '| rowspan = '+str(listlen)+' '+link)
|
||||
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(ext.name)
|
||||
print('| '+' <br /> '.join(strs_to_print))
|
||||
print('|}')
|
||||
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" ]
|
||||
# Hints default to TRUE
|
||||
# Don't do: Yes
|
||||
# Do: No
|
||||
if "no_hints" in argVars:
|
||||
src = "no_hints"
|
||||
if isinstance(argVars["hints"],dict):
|
||||
tmp = {}
|
||||
for idx in range(1,len(argVars["hints"]) + 1):
|
||||
tmp[idx] = argVars[src] not in truthy # tmp = !src
|
||||
args.hints = tmp # dest = tmp
|
||||
else:
|
||||
args.hints = args.no_hints not in truthy # dest = !src
|
||||
# Don't do: No
|
||||
# Do: Yes
|
||||
if "hints" in argVars:
|
||||
src = "hints"
|
||||
if isinstance(argVars["hints"],dict):
|
||||
tmp = {}
|
||||
for idx in range(1,len(argVars["hints"]) + 1):
|
||||
tmp[idx] = argVars[src] not in truthy # tmp = !src
|
||||
args.no_hints = tmp # dest = tmp
|
||||
else:
|
||||
args.no_hints = args.hints not in truthy # dest = !src
|
||||
|
||||
# Spoiler defaults to FALSE
|
||||
# Don't do: No
|
||||
# Do: Yes
|
||||
if "create_spoiler" in argVars:
|
||||
args.suppress_spoiler = not args.create_spoiler in truthy
|
||||
# Don't do: Yes
|
||||
# Do: No
|
||||
if "suppress_spoiler" in argVars:
|
||||
args.create_spoiler = not args.suppress_spoiler in truthy
|
||||
|
||||
# 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:
|
||||
args.shuffleganon = not args.no_shuffleganon in truthy
|
||||
# Don't do: No
|
||||
# Do: Yes
|
||||
if "shuffleganon" in argVars:
|
||||
args.no_shuffleganon = not args.shuffleganon 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')
|
||||
|
||||
@@ -21,6 +21,7 @@ incsrc keydoors.asm
|
||||
incsrc overrides.asm
|
||||
;incsrc edges.asm
|
||||
;incsrc math.asm
|
||||
incsrc hudadditions.asm
|
||||
warnpc $279000
|
||||
|
||||
; Data Section
|
||||
|
||||
@@ -541,3 +541,20 @@ db $58,$50,$30, $98,$50,$70 ; TT Ambush
|
||||
db $58,$50,$30 ; TT Nook
|
||||
MultDivInfo: ; (1placeholder, 1, 2, 3, 4, 5, 6, 10, 20)
|
||||
db $01, $01, $02, $03, $04, $05, $06, $0a, $14
|
||||
|
||||
|
||||
; dungeon tables
|
||||
; HC HC EP DP AT SP PD MM SW IP TH TT TR GT
|
||||
org $27f000
|
||||
CompassBossIndicator:
|
||||
dw $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000
|
||||
TotalKeys: ;27f01c
|
||||
db $04, $04, $02, $04, $04, $06, $06, $06, $05, $06, $01, $03, $06, $08
|
||||
ChestKeys: ;27f02a
|
||||
db $01, $01, $00, $01, $02, $01, $06, $03, $03, $02, $01, $01, $04, $04
|
||||
BigKeyStatus: ;27f038 (status 2 indicate BnC guard)
|
||||
dw $0002, $0002, $0001, $0001, $0000, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001
|
||||
DungeonReminderTable: ;27f054
|
||||
dw $2D50, $2D50, $2D51, $2D52, $2D54, $2D56, $2D55, $2D5A, $2D57, $2D59, $2D53, $2D58, $2D5B, $2D5C
|
||||
;27f070
|
||||
|
||||
|
||||
+30
-4
@@ -11,12 +11,14 @@ jsl AdjustTransition
|
||||
nop
|
||||
|
||||
;turn off linking doors -- see .notRoomLinkDoor label in Bank02.asm
|
||||
org $02b5a6
|
||||
bra NotLinkDoor1
|
||||
org $02b5a8 ; <- 135a8 - Bank02.asm : 8368 (LDA $7EC004 : STA $A0)
|
||||
jsl CheckLinkDoorR
|
||||
bcc NotLinkDoor1
|
||||
org $02b5b6
|
||||
NotLinkDoor1:
|
||||
org $02b647
|
||||
bra NotLinkDoor2
|
||||
org $02b649 ; <- 135a8 - Bank02.asm : 8482 (LDA $7EC004 : STA $A0)
|
||||
jsl CheckLinkDoorL
|
||||
bcc NotLinkDoor2
|
||||
org $02b657
|
||||
NotLinkDoor2:
|
||||
|
||||
@@ -77,7 +79,31 @@ org $2081f2
|
||||
jsl MirrorCheckOverride2
|
||||
org $20825c
|
||||
jsl MirrorCheckOverride2
|
||||
org $07a955 ; <- Bank07.asm : around 6564 (JP is a bit different) (STZ $05FC : STZ $05FD)
|
||||
jsl BlockEraseFix
|
||||
nop #2
|
||||
|
||||
org $02b82a
|
||||
jsl FixShopCode
|
||||
|
||||
org $1ddeea ; <- Bank1D.asm : 286 (JSL Sprite_LoadProperties)
|
||||
jsl VitreousKeyReset
|
||||
|
||||
org $1ed024 ; f5024 sprite_guruguru_bar.asm : 27 (LDA $040C : CMP.b #$12 : INY #2
|
||||
jsl GuruguruFix : bra .next
|
||||
nop #3
|
||||
.next
|
||||
|
||||
; also rando's hooks.asm line 1360
|
||||
org $a0ee11 ; <- 6FC4C - headsup_display.asm : 836 (LDA $7EF36E : AND.w #$00FF : ADD.w #$0007 : AND.w #$FFF8 : TAX)
|
||||
jsl DrHudOverride
|
||||
org $098638 ; rando's hooks.asm line 2192
|
||||
jsl CountChestKeys
|
||||
org $06D192 ; rando's hooks.asm line 457
|
||||
jsl CountAbsorbedKeys
|
||||
; rando's hooks.asm line 1020
|
||||
org $05FC7E ; <- 2FC7E - sprite_dash_item.asm : 118 (LDA $7EF36F : INC A : STA $7EF36F)
|
||||
jsl CountBonkItem
|
||||
|
||||
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
|
||||
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
DrHudOverride:
|
||||
{
|
||||
jsl.l NewDrawHud
|
||||
jsr HudAdditions
|
||||
rtl
|
||||
}
|
||||
|
||||
HudAdditions:
|
||||
{
|
||||
ldx $040c : cpx #$ff : bne + : rts : +
|
||||
lda DRMode : bne + : rts : +
|
||||
phb : phk : plb
|
||||
lda $7ef364 : and.l $0098c0, x : beq +
|
||||
lda CompassBossIndicator, x : and #$00ff : cmp $a0 : bne +
|
||||
lda $1a : and #$0010 : beq +
|
||||
lda #$345e : sta $7ec790 : bra .next
|
||||
+ lda #$207f : sta $7ec790
|
||||
.next lda DRMode : and #$0002 : bne + : plb : rts : +
|
||||
lda $7ef36d : and #$00ff : beq +
|
||||
lda DungeonReminderTable, x : bra .reminder
|
||||
+ lda #$207f
|
||||
.reminder sta $7ec702
|
||||
+ lda DRFlags : and #$0004 : beq .restore
|
||||
lda $7ef368 : and.l $0098c0, x : beq .restore
|
||||
|
||||
lda #$2811 : sta $7ec740
|
||||
lda $7ef366 : and.l $0098c0, x : bne .check
|
||||
lda BigKeyStatus, x : and #$00ff : bne + ;todo: "and" is redundant or change table to one byte
|
||||
lda #$2574 : bra ++
|
||||
+ cmp #$0002 : bne +
|
||||
lda #$2420 : bra ++
|
||||
+ lda #$207f : bra ++
|
||||
.check lda #$2826
|
||||
++ sta $7ec742
|
||||
txa : lsr : tax
|
||||
|
||||
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
|
||||
lda #$2830 : sta $7ec7a4
|
||||
lda ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6
|
||||
|
||||
lda #$2871 : sta $7ec780
|
||||
lda TotalKeys, x
|
||||
sep #$20 : !sub $7ef4b0, x : rep #$20
|
||||
jsr ConvertToDisplay : sta $7ec782
|
||||
|
||||
.restore
|
||||
plb : rts
|
||||
}
|
||||
|
||||
ConvertToDisplay:
|
||||
and #$00ff : cmp #$000a : !blt +
|
||||
!add #$2553 : rts
|
||||
+ !add #$2490 : rts
|
||||
|
||||
|
||||
CountChestKeys:
|
||||
jsl ItemDowngradeFix
|
||||
jsr CountChest
|
||||
rtl
|
||||
|
||||
CountChest:
|
||||
cpy #$24 : beq +
|
||||
cpy #$a0 : !blt .end
|
||||
cpy #$ae : !bge .end
|
||||
pha : phx
|
||||
tya : and #$0f : bne ++
|
||||
inc a
|
||||
++ tax : bra .count
|
||||
+ pha : phx
|
||||
lda $040c : lsr : tax
|
||||
.count
|
||||
lda $7ef4b0, x : inc : sta $7ef4b0, x
|
||||
lda $7ef4e0, x : inc : sta $7ef4e0, x
|
||||
.restore plx : pla
|
||||
.end rts
|
||||
|
||||
CountAbsorbedKeys:
|
||||
jsl IncrementSmallKeysNoPrimary : phx
|
||||
lda $040c : cmp #$ff : beq +
|
||||
lsr : tax
|
||||
lda $7ef4b0, x : inc : sta $7ef4b0, x
|
||||
+ plx : rtl
|
||||
|
||||
CountBonkItem:
|
||||
jsl GiveBonkItem
|
||||
lda $a0 ; check room ID - only bonk keys in 2 rooms so we're just checking the lower byte
|
||||
cmp #115 : bne + ; Desert Bonk Key
|
||||
lda.l BonkKey_Desert
|
||||
bra ++
|
||||
+ : cmp #140 : bne + ; GTower Bonk Key
|
||||
lda.l BonkKey_GTower
|
||||
bra ++
|
||||
+ lda.b #$24 ; default to small key
|
||||
++
|
||||
phy : tay : jsr CountChest : ply
|
||||
rtl
|
||||
+2
-1
@@ -12,8 +12,9 @@
|
||||
CheckIfDoorsOpen: {
|
||||
jsr TrapDoorFixer ; see normal.asm
|
||||
; note we are 16bit mode right now
|
||||
lda DRMode : beq +
|
||||
lda $040c : cmp #$00ff : bne .gtg
|
||||
lda $a0 : dec : tax : and #$000f ; hijacked code
|
||||
+ lda $a0 : dec : tax : and #$000f ; hijacked code
|
||||
sec : rtl ; set carry to indicate normal behavior
|
||||
|
||||
.gtg
|
||||
|
||||
@@ -42,6 +42,20 @@ WarpDown:
|
||||
jsr Cleanup
|
||||
rtl
|
||||
|
||||
; carry set = use link door like normal
|
||||
; carry clear = we are in dr mode, never use linking doors
|
||||
CheckLinkDoorR:
|
||||
lda DRMode : bne +
|
||||
lda $7ec004 : sta $a0 ; what we wrote over
|
||||
sec : rtl
|
||||
+ clc : rtl
|
||||
|
||||
CheckLinkDoorL:
|
||||
lda DRMode : bne +
|
||||
lda $7ec003 : sta $a0 ; what we wrote over
|
||||
sec : rtl
|
||||
+ clc : rtl
|
||||
|
||||
TrapDoorFixer:
|
||||
lda $fe : and #$0038 : beq .end
|
||||
xba : asl #2 : sta $00
|
||||
|
||||
@@ -51,3 +51,26 @@ MirrorCheckOverride:
|
||||
|
||||
MirrorCheckOverride2:
|
||||
lda $7ef353 : and #$02 : rtl
|
||||
|
||||
|
||||
BlockEraseFix:
|
||||
lda $7ef353 : and #$02 : beq +
|
||||
stz $05fc : stz $05fd
|
||||
+ rtl
|
||||
|
||||
FixShopCode:
|
||||
cpx #$300 : !bge +
|
||||
sta $7ef000, x
|
||||
+ rtl
|
||||
|
||||
VitreousKeyReset:
|
||||
lda DRMode : beq +
|
||||
stz $0cba, x
|
||||
+ jsl $0db818 ;restore old code
|
||||
rtl
|
||||
|
||||
GuruguruFix:
|
||||
lda $a0 : cmp #$df : !bge +
|
||||
and #$0f : cmp #$0e : !blt +
|
||||
iny #2
|
||||
+ rtl
|
||||
@@ -0,0 +1,5 @@
|
||||
from Main import __version__ as DRVersion
|
||||
import os
|
||||
|
||||
with(open(os.path.join("resources","app","meta","manifests","app_version.txt"),"w+")) as f:
|
||||
f.write(DRVersion)
|
||||
+9
-2
@@ -1,18 +1,25 @@
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# Spec file
|
||||
SPEC_FILE = os.path.join("DungeonRandomizer.spec")
|
||||
|
||||
# Destination is current dir
|
||||
DEST_DIRECTORY = '.'
|
||||
|
||||
# Check for UPX
|
||||
if os.path.isdir("upx"):
|
||||
upx_string = "--upx-dir=upx"
|
||||
else:
|
||||
upx_string = ""
|
||||
|
||||
if os.path.isdir("build"):
|
||||
if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.find("osx"):
|
||||
shutil.rmtree("build")
|
||||
|
||||
subprocess.run(" ".join(["pyinstaller DungeonRandomizer.spec ",
|
||||
# Run pyinstaller for DungeonRandomizer
|
||||
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
||||
upx_string,
|
||||
"-y ",
|
||||
"--onefile ",
|
||||
|
||||
+9
-2
@@ -1,18 +1,25 @@
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# Spec file
|
||||
SPEC_FILE = os.path.join("Gui.spec")
|
||||
|
||||
# Destination is current dir
|
||||
DEST_DIRECTORY = '.'
|
||||
|
||||
# Check for UPX
|
||||
if os.path.isdir("upx"):
|
||||
upx_string = "--upx-dir=upx"
|
||||
else:
|
||||
upx_string = ""
|
||||
|
||||
if os.path.isdir("build"):
|
||||
if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.find("osx"):
|
||||
shutil.rmtree("build")
|
||||
|
||||
subprocess.run(" ".join(["pyinstaller Gui.spec ",
|
||||
# Run pyinstaller for Gui
|
||||
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
||||
upx_string,
|
||||
"-y ",
|
||||
"--onefile ",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# do nothing, just exist to make "classes" package
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
||||
# do nothing, just exist to make "gui" package
|
||||
@@ -1 +0,0 @@
|
||||
# do nothing, just exist to make "gui.about" package
|
||||
@@ -1 +0,0 @@
|
||||
# do nothing, just exist to make "gui.adjust" package
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
from tkinter import ttk, messagebox, StringVar, Button, Entry, Frame, Label, Spinbox, E, W, LEFT, RIGHT, X
|
||||
from argparse import Namespace
|
||||
from functools import partial
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
from CLI import parse_arguments, get_settings
|
||||
from Main import main
|
||||
from Utils import local_path, output_path, open_file
|
||||
import classes.constants as CONST
|
||||
import gui.widgets as widgets
|
||||
|
||||
|
||||
def bottom_frame(self, parent, args=None):
|
||||
# Bottom Frame
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Bottom Frame options
|
||||
self.widgets = {}
|
||||
|
||||
seedCountFrame = Frame(self)
|
||||
seedCountFrame.pack()
|
||||
## Seed #
|
||||
seedLabel = Label(self, text='Seed #')
|
||||
savedSeed = parent.settings["seed"]
|
||||
self.seedVar = StringVar(value=savedSeed)
|
||||
def saveSeed(caller,_,mode):
|
||||
savedSeed = self.seedVar.get()
|
||||
parent.settings["seed"] = int(savedSeed) if savedSeed.isdigit() else None
|
||||
self.seedVar.trace_add("write",saveSeed)
|
||||
seedEntry = Entry(self, width=15, textvariable=self.seedVar)
|
||||
seedLabel.pack(side=LEFT)
|
||||
seedEntry.pack(side=LEFT)
|
||||
|
||||
## Number of Generation attempts
|
||||
key = "generationcount"
|
||||
self.widgets[key] = widgets.make_widget(
|
||||
self,
|
||||
"spinbox",
|
||||
self,
|
||||
"Count",
|
||||
None,
|
||||
None,
|
||||
{"label": {"side": LEFT}, "spinbox": {"side": RIGHT}}
|
||||
)
|
||||
self.widgets[key].pack(side=LEFT)
|
||||
|
||||
def generateRom():
|
||||
guiargs = create_guiargs(parent)
|
||||
# get default values for missing parameters
|
||||
for k,v in vars(parse_arguments(['--multi', str(guiargs.multi)])).items():
|
||||
if k not in vars(guiargs):
|
||||
setattr(guiargs, k, v)
|
||||
elif type(v) is dict: # use same settings for every player
|
||||
setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)})
|
||||
try:
|
||||
if guiargs.count is not None:
|
||||
seed = guiargs.seed
|
||||
for _ in range(guiargs.count):
|
||||
main(seed=seed, args=guiargs)
|
||||
seed = random.randint(0, 999999999)
|
||||
else:
|
||||
main(seed=guiargs.seed, args=guiargs)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||
else:
|
||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||
|
||||
## Generate Button
|
||||
generateButton = Button(self, text='Generate Patched Rom', command=generateRom)
|
||||
generateButton.pack(side=LEFT)
|
||||
|
||||
def open_output():
|
||||
if args and args.outputpath:
|
||||
open_file(output_path(args.outputpath))
|
||||
else:
|
||||
open_file(output_path(parent.settings["outputpath"]))
|
||||
|
||||
openOutputButton = Button(self, text='Open Output Directory', command=open_output)
|
||||
openOutputButton.pack(side=RIGHT)
|
||||
|
||||
## Documentation Button
|
||||
if os.path.exists(local_path('README.html')):
|
||||
def open_readme():
|
||||
open_file(local_path('README.html'))
|
||||
openReadmeButton = Button(self, text='Open Documentation', command=open_readme)
|
||||
openReadmeButton.pack(side=RIGHT)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def create_guiargs(parent):
|
||||
guiargs = Namespace()
|
||||
|
||||
# set up settings to gather
|
||||
# Page::Subpage::GUI-id::param-id
|
||||
options = CONST.SETTINGSTOPROCESS
|
||||
|
||||
for mainpage in options:
|
||||
for subpage in options[mainpage]:
|
||||
for widget in options[mainpage][subpage]:
|
||||
arg = options[mainpage][subpage][widget]
|
||||
setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get())
|
||||
|
||||
guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.get()
|
||||
|
||||
guiargs.multi = int(parent.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.get())
|
||||
|
||||
guiargs.rom = parent.pages["randomizer"].pages["generation"].romVar.get()
|
||||
guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get())
|
||||
|
||||
guiargs.seed = int(parent.frames["bottom"].seedVar.get()) if parent.frames["bottom"].seedVar.get() else None
|
||||
guiargs.count = int(parent.frames["bottom"].widgets["generationcount"].storageVar.get()) if parent.frames["bottom"].widgets["generationcount"].storageVar.get() != '1' else None
|
||||
|
||||
adjustargs = {
|
||||
"nobgm": "disablemusic",
|
||||
"quickswap": "quickswap",
|
||||
"heartcolor": "heartcolor",
|
||||
"heartbeep": "heartbeep",
|
||||
"menuspeed": "fastmenu",
|
||||
"owpalettes": "ow_palettes",
|
||||
"uwpalettes": "uw_palettes"
|
||||
}
|
||||
for adjustarg in adjustargs:
|
||||
internal = adjustargs[adjustarg]
|
||||
setattr(guiargs,"adjust." + internal, parent.pages["adjust"].content.widgets[adjustarg].storageVar.get())
|
||||
|
||||
customitems = CONST.CUSTOMITEMS
|
||||
guiargs.startinventory = []
|
||||
guiargs.customitemarray = {}
|
||||
guiargs.startinventoryarray = {}
|
||||
for customitem in customitems:
|
||||
if customitem not in ["triforcepiecesgoal", "triforce", "rupoor", "rupoorcost"]:
|
||||
amount = int(parent.pages["startinventory"].content.startingWidgets[customitem].storageVar.get())
|
||||
guiargs.startinventoryarray[customitem] = amount
|
||||
for i in range(0, amount):
|
||||
label = CONST.CUSTOMITEMLABELS[customitems.index(customitem)]
|
||||
guiargs.startinventory.append(label)
|
||||
guiargs.customitemarray[customitem] = int(parent.pages["custom"].content.customWidgets[customitem].storageVar.get())
|
||||
|
||||
guiargs.startinventory = ','.join(guiargs.startinventory)
|
||||
|
||||
guiargs.sprite = parent.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"]
|
||||
guiargs.randomSprite = parent.randomSprite.get()
|
||||
guiargs.outputpath = parent.outputPath.get()
|
||||
return guiargs
|
||||
@@ -1 +0,0 @@
|
||||
# do nothing, just exist to make "gui.custom" package
|
||||
@@ -1,74 +0,0 @@
|
||||
from classes.SpriteSelector import SpriteSelector as spriteSelector
|
||||
from gui.randomize.gameoptions import set_sprite
|
||||
from Rom import Sprite, get_sprite_from_name
|
||||
import classes.constants as CONST
|
||||
|
||||
def loadcliargs(gui, args, settings=None):
|
||||
if args is not None:
|
||||
# for k, v in vars(args).items():
|
||||
# if type(v) is dict:
|
||||
# setattr(args, k, v[1]) # only get values for player 1 for now
|
||||
# load values from commandline args
|
||||
|
||||
# set up options to get
|
||||
# Page::Subpage::GUI-id::param-id
|
||||
options = CONST.SETTINGSTOPROCESS
|
||||
|
||||
for mainpage in options:
|
||||
for subpage in options[mainpage]:
|
||||
for widget in options[mainpage][subpage]:
|
||||
arg = options[mainpage][subpage][widget]
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
|
||||
if subpage == "gameoptions" and not widget == "hints":
|
||||
hasSettings = settings is not None
|
||||
hasWidget = ("adjust." + widget) in settings if hasSettings else None
|
||||
if hasWidget is None:
|
||||
gui.pages["adjust"].content.widgets[widget].storageVar.set(args[arg])
|
||||
|
||||
gui.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.set(args["enemizercli"])
|
||||
gui.pages["randomizer"].pages["generation"].romVar.set(args["rom"])
|
||||
|
||||
if args["multi"]:
|
||||
gui.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.set(str(args["multi"]))
|
||||
if args["seed"]:
|
||||
gui.frames["bottom"].seedVar.set(str(args["seed"]))
|
||||
if args["count"]:
|
||||
gui.frames["bottom"].widgets["generationcount"].storageVar.set(str(args["count"]))
|
||||
gui.outputPath.set(args["outputpath"])
|
||||
|
||||
def sprite_setter(spriteObject):
|
||||
gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"] = spriteObject
|
||||
if args["sprite"] is not None:
|
||||
sprite_obj = args.sprite if isinstance(args["sprite"], Sprite) else get_sprite_from_name(args["sprite"])
|
||||
set_sprite(sprite_obj, False, spriteSetter=sprite_setter,
|
||||
spriteNameVar=gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteNameVar"],
|
||||
randomSpriteVar=gui.randomSprite)
|
||||
|
||||
def sprite_setter_adj(spriteObject):
|
||||
gui.pages["adjust"].content.sprite = spriteObject
|
||||
if args["sprite"] is not None:
|
||||
sprite_obj = args.sprite if isinstance(args["sprite"], Sprite) else get_sprite_from_name(args["sprite"])
|
||||
set_sprite(sprite_obj, False, spriteSetter=sprite_setter_adj,
|
||||
spriteNameVar=gui.pages["adjust"].content.spriteNameVar2,
|
||||
randomSpriteVar=gui.randomSprite)
|
||||
|
||||
def loadadjustargs(gui, settings):
|
||||
options = {
|
||||
"adjust": {
|
||||
"content": {
|
||||
"nobgm": "adjust.nobgm",
|
||||
"quickswap": "adjust.quickswap",
|
||||
"heartcolor": "adjust.heartcolor",
|
||||
"heartbeep": "adjust.heartbeep",
|
||||
"menuspeed": "adjust.menuspeed",
|
||||
"owpalettes": "adjust.owpalettes",
|
||||
"uwpalettes": "adjust.uwpalettes"
|
||||
}
|
||||
}
|
||||
}
|
||||
for mainpage in options:
|
||||
for subpage in options[mainpage]:
|
||||
for widget in options[mainpage][subpage]:
|
||||
key = options[mainpage][subpage][widget]
|
||||
if key in settings:
|
||||
gui.pages[mainpage].content.widgets[widget].storageVar.set(settings[key])
|
||||
@@ -1 +0,0 @@
|
||||
# do nothing, just exist to make "gui.randomize" package
|
||||
@@ -1,63 +0,0 @@
|
||||
import os
|
||||
from tkinter import ttk, filedialog, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, LabelFrame, OptionMenu, N, E, W, LEFT, RIGHT, BOTTOM, X
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
def enemizer_page(parent,settings):
|
||||
def open_enemizer_download(_evt):
|
||||
webbrowser.open("https://github.com/Bonta0/Enemizer/releases")
|
||||
|
||||
# Enemizer
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Enemizer options
|
||||
self.widgets = {}
|
||||
|
||||
# Enemizer option sections
|
||||
self.frames = {}
|
||||
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
self.frames["selectOptionsFrame"] = Frame(self)
|
||||
self.frames["leftEnemizerFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
self.frames["rightEnemizerFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
self.frames["bottomEnemizerFrame"] = Frame(self)
|
||||
self.frames["selectOptionsFrame"].pack(fill=X)
|
||||
self.frames["leftEnemizerFrame"].pack(side=LEFT)
|
||||
self.frames["rightEnemizerFrame"].pack(side=RIGHT)
|
||||
self.frames["bottomEnemizerFrame"].pack(fill=X)
|
||||
|
||||
with open(os.path.join("resources","app","gui","randomize","enemizer","widgets.json")) as widgetDefns:
|
||||
myDict = json.load(widgetDefns)
|
||||
for framename,theseWidgets in myDict.items():
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
packAttrs = {"anchor":E}
|
||||
if self.widgets[key].type == "checkbox":
|
||||
packAttrs["anchor"] = W
|
||||
self.widgets[key].pack(packAttrs)
|
||||
|
||||
## Enemizer CLI Path
|
||||
enemizerPathFrame = Frame(self.frames["bottomEnemizerFrame"])
|
||||
enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ")
|
||||
enemizerCLIlabel.pack(side=LEFT)
|
||||
enemizerURL = Label(enemizerPathFrame, text="(get online)", fg="blue", cursor="hand2")
|
||||
enemizerURL.pack(side=LEFT)
|
||||
enemizerURL.bind("<Button-1>", open_enemizer_download)
|
||||
self.enemizerCLIpathVar = StringVar(value=settings["enemizercli"])
|
||||
enemizerCLIpathEntry = Entry(enemizerPathFrame, textvariable=self.enemizerCLIpathVar)
|
||||
enemizerCLIpathEntry.pack(side=LEFT, fill=X, expand=True)
|
||||
def EnemizerSelectPath():
|
||||
path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")], initialdir=os.path.join("."))
|
||||
if path:
|
||||
self.enemizerCLIpathVar.set(path)
|
||||
settings["enemizercli"] = path
|
||||
enemizerCLIbrowseButton = Button(enemizerPathFrame, text='...', command=EnemizerSelectPath)
|
||||
enemizerCLIbrowseButton.pack(side=LEFT)
|
||||
enemizerPathFrame.pack(fill=X)
|
||||
|
||||
return self,settings
|
||||
@@ -1,45 +0,0 @@
|
||||
import os
|
||||
from tkinter import ttk, filedialog, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, E, W, LEFT, RIGHT, X
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
def generation_page(parent,settings):
|
||||
# Generation Setup
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Generation Setup options
|
||||
self.widgets = {}
|
||||
|
||||
# Generation Setup option sections
|
||||
self.frames = {}
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
with open(os.path.join("resources","app","gui","randomize","generation","checkboxes.json")) as checkboxes:
|
||||
myDict = json.load(checkboxes)
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["checkboxes"])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
self.widgets[key].pack(anchor=W)
|
||||
|
||||
self.frames["baserom"] = Frame(self)
|
||||
self.frames["baserom"].pack(anchor=W, fill=X)
|
||||
## Locate base ROM
|
||||
baseRomFrame = Frame(self.frames["baserom"])
|
||||
baseRomLabel = Label(baseRomFrame, text='Base Rom: ')
|
||||
self.romVar = StringVar()
|
||||
romEntry = Entry(baseRomFrame, textvariable=self.romVar)
|
||||
self.romVar.set(settings["rom"])
|
||||
|
||||
def RomSelect():
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")], initialdir=os.path.join("."))
|
||||
self.romVar.set(rom)
|
||||
romSelectButton = Button(baseRomFrame, text='Select Rom', command=RomSelect)
|
||||
|
||||
baseRomLabel.pack(side=LEFT)
|
||||
romEntry.pack(side=LEFT, fill=X, expand=True)
|
||||
romSelectButton.pack(side=LEFT)
|
||||
baseRomFrame.pack(fill=X)
|
||||
|
||||
return self,settings
|
||||
@@ -1,38 +0,0 @@
|
||||
from tkinter import ttk, StringVar, Entry, Frame, Label, Spinbox, N, E, W, X, LEFT, RIGHT
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
def multiworld_page(parent,settings):
|
||||
# Multiworld
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Multiworld options
|
||||
self.widgets = {}
|
||||
|
||||
# Multiworld option sections
|
||||
self.frames = {}
|
||||
self.frames["widgets"] = Frame(self)
|
||||
self.frames["widgets"].pack(anchor=W, fill=X)
|
||||
|
||||
with open(os.path.join("resources","app","gui","randomize","multiworld","widgets.json")) as multiworldItems:
|
||||
myDict = json.load(multiworldItems)
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["widgets"])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
self.widgets[key].pack(side=LEFT, anchor=N)
|
||||
|
||||
## List of Player Names
|
||||
key = "names"
|
||||
self.widgets[key] = Frame(self.frames["widgets"])
|
||||
self.widgets[key].label = Label(self.widgets[key], text='Player names')
|
||||
self.widgets[key].storageVar = StringVar(value=settings["names"])
|
||||
def saveMultiNames(caller,_,mode):
|
||||
settings["names"] = self.widgets[key].storageVar.get()
|
||||
self.widgets[key].storageVar.trace_add("write",saveMultiNames)
|
||||
self.widgets[key].textbox = Entry(self.widgets[key], textvariable=self.widgets[key].storageVar)
|
||||
self.widgets[key].label.pack(side=LEFT)
|
||||
self.widgets[key].textbox.pack(side=LEFT, fill=X, expand=True)
|
||||
self.widgets[key].pack(anchor=N, fill=X, expand=True)
|
||||
|
||||
return self,settings
|
||||
@@ -1 +0,0 @@
|
||||
# do nothing, just exist to make "gui.startinventory" package
|
||||
@@ -1,3 +0,0 @@
|
||||
aioconsole==0.1.15
|
||||
colorama==0.4.3
|
||||
websockets==8.1
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "resources" package
|
||||
@@ -0,0 +1,337 @@
|
||||
{
|
||||
"lang": {},
|
||||
"diags": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"create_spoiler": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"suppress_spoiler": {
|
||||
"action": "store_false",
|
||||
"dest": "create_spoiler",
|
||||
"help": "suppress"
|
||||
},
|
||||
"logic": {
|
||||
"choices": [
|
||||
"noglitches",
|
||||
"minorglitches",
|
||||
"nologic"
|
||||
]
|
||||
},
|
||||
"mode": {
|
||||
"choices": [
|
||||
"open",
|
||||
"standard",
|
||||
"inverted",
|
||||
"retro"
|
||||
]
|
||||
},
|
||||
"swords": {
|
||||
"choices": [
|
||||
"random",
|
||||
"assured",
|
||||
"swordless",
|
||||
"vanilla"
|
||||
]
|
||||
},
|
||||
"goal": {
|
||||
"choices": [
|
||||
"ganon",
|
||||
"pedestal",
|
||||
"dungeons",
|
||||
"triforcehunt",
|
||||
"crystals"
|
||||
]
|
||||
},
|
||||
"difficulty": {
|
||||
"choices": [
|
||||
"normal",
|
||||
"hard",
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"item_functionality": {
|
||||
"choices": [
|
||||
"normal",
|
||||
"hard",
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"timer": {
|
||||
"choices": [
|
||||
"none",
|
||||
"display",
|
||||
"timed",
|
||||
"timed-ohko",
|
||||
"ohko",
|
||||
"timed-countdown"
|
||||
]
|
||||
},
|
||||
"progressive": {
|
||||
"choices": [
|
||||
"on",
|
||||
"off",
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"algorithm": {
|
||||
"choices": [
|
||||
"balanced",
|
||||
"freshness",
|
||||
"flood",
|
||||
"vt21",
|
||||
"vt22",
|
||||
"vt25",
|
||||
"vt26"
|
||||
]
|
||||
},
|
||||
"shuffle": {
|
||||
"choices": [
|
||||
"vanilla",
|
||||
"simple",
|
||||
"restricted",
|
||||
"full",
|
||||
"crossed",
|
||||
"insanity",
|
||||
"restricted_legacy",
|
||||
"full_legacy",
|
||||
"madness_legacy",
|
||||
"insanity_legacy",
|
||||
"dungeonsfull",
|
||||
"dungeonssimple"
|
||||
]
|
||||
},
|
||||
"door_shuffle": {
|
||||
"choices": [
|
||||
"basic",
|
||||
"crossed",
|
||||
"vanilla"
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"dungeon_counters": {
|
||||
"choices": [
|
||||
"default",
|
||||
"off",
|
||||
"on",
|
||||
"pickup"
|
||||
]
|
||||
},
|
||||
"crystals_ganon": {
|
||||
"choices": [
|
||||
7, 6, 5, 4, 3, 2, 1, 0, "random"
|
||||
]
|
||||
},
|
||||
"crystals_gt": {
|
||||
"choices": [
|
||||
7, 6, 5, 4, 3, 2, 1, 0, "random"
|
||||
]
|
||||
},
|
||||
"openpyramid": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"rom": {},
|
||||
"loglevel": {
|
||||
"choices": [
|
||||
"info",
|
||||
"error",
|
||||
"warning",
|
||||
"debug"
|
||||
]
|
||||
},
|
||||
"fastmenu": {
|
||||
"choices": [
|
||||
"normal",
|
||||
"instant",
|
||||
"double",
|
||||
"triple",
|
||||
"quadruple",
|
||||
"half"
|
||||
]
|
||||
},
|
||||
"quickswap": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"disablemusic": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"mapshuffle": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"compassshuffle": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"keyshuffle": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"bigkeyshuffle": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"keysanity": {
|
||||
"action": "store_true",
|
||||
"type": "bool",
|
||||
"help": "suppress"
|
||||
},
|
||||
"retro": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"startinventory": {},
|
||||
"usestartinventory": {
|
||||
"type": "bool"
|
||||
},
|
||||
"custom": {
|
||||
"type": "bool",
|
||||
"help": "suppress"
|
||||
},
|
||||
"accessibility": {
|
||||
"choices": [
|
||||
"items",
|
||||
"locations",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"hints": {
|
||||
"action": "store_false",
|
||||
"type": "bool"
|
||||
},
|
||||
"no_hints": {
|
||||
"action": "store_true",
|
||||
"dest": "hints",
|
||||
"help": "suppress"
|
||||
},
|
||||
"heartbeep": {
|
||||
"choices": [
|
||||
"normal",
|
||||
"double",
|
||||
"half",
|
||||
"quarter",
|
||||
"off"
|
||||
]
|
||||
},
|
||||
"heartcolor": {
|
||||
"choices": [
|
||||
"red",
|
||||
"blue",
|
||||
"green",
|
||||
"yellow",
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"ow_palettes": {
|
||||
"choices": [
|
||||
"default",
|
||||
"random",
|
||||
"blackout"
|
||||
]
|
||||
},
|
||||
"uw_palettes": {
|
||||
"choices": [
|
||||
"default",
|
||||
"random",
|
||||
"blackout"
|
||||
]
|
||||
},
|
||||
"sprite": {},
|
||||
"create_rom": {
|
||||
"action": "store_false",
|
||||
"type": "bool"
|
||||
},
|
||||
"suppress_rom": {
|
||||
"action": "store_true",
|
||||
"dest": "create_rom",
|
||||
"help": "suppress"
|
||||
},
|
||||
"shuffleganon": {
|
||||
"action": "store_false",
|
||||
"type": "bool"
|
||||
},
|
||||
"no_shuffleganon": {
|
||||
"action": "store_true",
|
||||
"dest": "shuffleganon",
|
||||
"help": "suppress"
|
||||
},
|
||||
"calc_playthrough": {
|
||||
"action": "store_false",
|
||||
"type": "bool"
|
||||
},
|
||||
"skip_playthrough": {
|
||||
"action": "store_true",
|
||||
"dest": "calc_playthrough",
|
||||
"help": "suppress"
|
||||
},
|
||||
"gui": {
|
||||
"action": "store_true"
|
||||
},
|
||||
"jsonout": {
|
||||
"action": "store_true"
|
||||
},
|
||||
"enemizercli": {
|
||||
"setting": "enemizercli"
|
||||
},
|
||||
"shufflebosses": {
|
||||
"choices": [
|
||||
"none",
|
||||
"basic",
|
||||
"normal",
|
||||
"chaos"
|
||||
]
|
||||
},
|
||||
"shuffleenemies": {
|
||||
"choices": [
|
||||
"none",
|
||||
"shuffled",
|
||||
"chaos"
|
||||
]
|
||||
},
|
||||
"enemy_health": {
|
||||
"choices": [
|
||||
"default",
|
||||
"easy",
|
||||
"normal",
|
||||
"hard",
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"enemy_damage": {
|
||||
"choices": [
|
||||
"default",
|
||||
"shuffled",
|
||||
"chaos"
|
||||
]
|
||||
},
|
||||
"shufflepots": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"remote_items": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"names": {},
|
||||
"outputpath": {},
|
||||
"race": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"saveonexit": {
|
||||
"choices": [
|
||||
"ask",
|
||||
"always",
|
||||
"never"
|
||||
]
|
||||
},
|
||||
"outputname": {}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"cli": {
|
||||
"app.title": "ALttP Tür Randomisier Version %s - Nummer: %d",
|
||||
"shuffling.world": "Welt wird durchmischt.",
|
||||
"generating.itempool": "Generier Gegenstandsbasis.",
|
||||
"calc.access.rules": "Berechne Zugriffsregeln.",
|
||||
"placing.dungeon.prizes": "Platziere Verliespreise.",
|
||||
"placing.dungeon.items": "Platziere Verliesgegenstände.",
|
||||
"fill.world": "Fülle die Welt.",
|
||||
"balance.multiworld": "Gleiche Multiwelt-Fortschritt aus.",
|
||||
"patching.rom": "Patche ROM.",
|
||||
"calc.playthrough": "Berechne Durschpiellösung.",
|
||||
"done": "Fertig. Viel Spaß.",
|
||||
"total.time": "Gesamtzeit: %s",
|
||||
"building.collection.spheres": "Baue Sammelbereiche auf.",
|
||||
"building.calculating.spheres": "Berechneter Bereich %i, beinhaltet %i von %i Progressionsgegenständen.",
|
||||
"cannot.reach.items": "Die folgenden Gegenstände können nicht erreicht werden: %s",
|
||||
"cannot.reach.item": "%s (Spieler %d) in %s (Spieler %d)",
|
||||
"check.item.location": "Prüfe ob %s (Spieler %d) benötigt wird um das Spiel zu schlagen.",
|
||||
"check.item.location.true": "Ja, Gegenstand wird benötigt um das Spiel zu schlagen.",
|
||||
"check.item.location.false": "Nein, Gegenstand wird nicht benötigt um das Spiel zu schlagen.",
|
||||
"building.final.spheres": "Berechneter Finalbereich %i, beinhaltet, %i von %i Progressionsgegenständen.",
|
||||
"cannot.beat.game": "Spiel is nicht schlagbar.",
|
||||
"cannot.reach.progression": "Nicht alle Progressionsgegenstände erreichbar.",
|
||||
"cannot.reach.required": "Nitch alle benötigten Gegenstände erreichbar."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
{
|
||||
"cli": {
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"app.title": "ALttP Door Randomizer Version %s - Seed: %d",
|
||||
"version": "Version",
|
||||
"seed": "Seed",
|
||||
"player": "Player",
|
||||
"shuffling.world": "Shuffling the World about",
|
||||
"shuffling.dungeons": "Shuffling dungeons",
|
||||
"basic.traversal": "--Basic Traversal",
|
||||
"generating.dungeon": "Generating dungeon",
|
||||
"shuffling.keydoors": "Shuffling Key doors for",
|
||||
"lowering.keys.candidates": "Lowering key door count because not enough candidates",
|
||||
"lowering.keys.layouts": "Lowering key door count because no valid layouts",
|
||||
"keydoor.shuffle.time": "Key door shuffle time",
|
||||
"keydoor.shuffle.time.crossed": "Cross Dungeon: Key door shuffle time",
|
||||
"generating.itempool": "Generating Item Pool",
|
||||
"calc.access.rules": "Calculating Access Rules",
|
||||
"placing.dungeon.prizes": "Placing Dungeon Prizes",
|
||||
"placing.dungeon.items": "Placing Dungeon Items",
|
||||
"keylock.detected": "Keylock detected",
|
||||
"fill.world": "Fill the world",
|
||||
"balance.doors": "-Balancing Doors",
|
||||
"re-balancing": "-Re-balancing",
|
||||
"balancing": "--Balancing",
|
||||
"splitting.up": "Splitting Up",
|
||||
"balance.multiworld": "Balancing multiworld progression",
|
||||
"cannot.beat.game": "Cannot beat game! Something went terribly wrong here!",
|
||||
"cannot.reach.items": "The following items could not be reached: %s",
|
||||
"cannot.reach.item": "%s (Player %d) at %s (Player %d)",
|
||||
"check.item.location": "Checking if %s (Player %d) is required to beat the game.",
|
||||
"check.item.location.true": "Yes, item is required.",
|
||||
"check.item.location.false": "No, item is not required.",
|
||||
"cannot.reach.progression": "Not all progression items reachable. Something went terribly wrong here.",
|
||||
"cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.",
|
||||
"patching.rom": "Patching ROM",
|
||||
"patching.spoiler": "Creating Spoiler",
|
||||
"calc.playthrough": "Calculating Playthrough",
|
||||
"made.rom": "Patched ROM: %s",
|
||||
"made.playthrough": "Printed Playthrough: %s",
|
||||
"made.spoiler": "Printed Spoiler: %s",
|
||||
"used.enemizer": "Enemized: %s",
|
||||
"done": "Done. Enjoy.",
|
||||
"total.time": "Total Time: %s",
|
||||
"finished.run": "Finished run",
|
||||
"generation.failed": "Generation failed",
|
||||
"generation.fail.rate": "Generation fail rate",
|
||||
"generation.success.rate": "Generation success rate",
|
||||
"enemizer.not.found": "Enemizer not found at",
|
||||
"enemizer.nothing.applied": "No Enemizer options will be applied until this is resolved.",
|
||||
"building.collection.spheres": "Building up collection spheres",
|
||||
"building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.",
|
||||
"building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items."
|
||||
},
|
||||
"help": {
|
||||
"lang": [ "App Language, if available, defaults to English" ],
|
||||
"create_spoiler": [ "Output a Spoiler File" ],
|
||||
"logic": [
|
||||
"Select Enforcement of Item Requirements. (default: %(default)s)",
|
||||
"No Glitches: No Glitch knowledge required.",
|
||||
"Minor Glitches: May require Fake Flippers, Bunny Revival",
|
||||
" and Dark Room Navigation.",
|
||||
"No Logic: Distribute items without regard for",
|
||||
" item requirements."
|
||||
],
|
||||
"mode": [
|
||||
"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.",
|
||||
"Retro: Keys are universal, shooting arrows costs rupees,",
|
||||
" and a few other little things make this more like Zelda-1."
|
||||
],
|
||||
"swords": [
|
||||
"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."
|
||||
],
|
||||
"goal": [
|
||||
"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.",
|
||||
"Triforce Hunt: Places 30 Triforce Pieces in the world, collect",
|
||||
" 20 of them to beat the game."
|
||||
],
|
||||
"difficulty": [
|
||||
"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."
|
||||
],
|
||||
"item_functionality": [
|
||||
"Select limits on item functionality to increase difficulty. (default: %(default)s)",
|
||||
"Normal: Normal functionality.",
|
||||
"Hard: Reduced functionality.",
|
||||
"Expert: Greatly reduced functionality."
|
||||
],
|
||||
"timer": [
|
||||
"Select game timer setting. Affects available itempool. (default: %(default)s)",
|
||||
"None: No timer.",
|
||||
"Display: Displays a timer but does not affect",
|
||||
" the itempool.",
|
||||
"Timed: Starts with clock at zero. Green Clocks",
|
||||
" subtract 4 minutes (Total: 20), Blue Clocks",
|
||||
" subtract 2 minutes (Total: 10), Red Clocks add",
|
||||
" 2 minutes (Total: 10). Winner is player with",
|
||||
" lowest time at the end.",
|
||||
"Timed OHKO: Starts clock at 10 minutes. Green Clocks add",
|
||||
" 5 minutes (Total: 25). As long as clock is at 0,",
|
||||
" Link will die in one hit.",
|
||||
"OHKO: Like Timed OHKO, but no clock items are present",
|
||||
" and the clock is permenantly at zero.",
|
||||
"Timed Countdown:Starts with clock at 40 minutes. Same clocks as",
|
||||
" Timed mode. If time runs out, you lose (but can",
|
||||
" still keep playing)."
|
||||
],
|
||||
"progressive": [
|
||||
"Select progressive equipment setting. Affects available itempool. (default: %(default)s)",
|
||||
"On: Swords, Shields, Armor, and Gloves will",
|
||||
" all be progressive equipment. Each subsequent",
|
||||
" item of the same type the player finds will",
|
||||
" upgrade that piece of equipment by one stage.",
|
||||
"Off: Swords, Shields, Armor, and Gloves will not",
|
||||
" be progressive equipment. Higher level items may",
|
||||
" be found at any time. Downgrades are not possible.",
|
||||
"Random: Swords, Shields, Armor, and Gloves will, per",
|
||||
" category, be randomly progressive or not.",
|
||||
" Link will die in one hit."
|
||||
],
|
||||
"algorithm": [
|
||||
"Select item filling algorithm. (default: %(default)s)",
|
||||
"balanced: vt26 derivative that aims to strike a balance between",
|
||||
" the overworld heavy vt25 and the dungeon heavy vt26",
|
||||
" algorithm.",
|
||||
"vt26: Shuffle items and place them in a random location",
|
||||
" that it is not impossible to be in. This includes",
|
||||
" dungeon keys and items.",
|
||||
"vt25: Shuffle items and place them in a random location",
|
||||
" that it is not impossible to be in.",
|
||||
"vt21: Unbiased in its selection, but has tendency to put",
|
||||
" Ice Rod in Turtle Rock.",
|
||||
"vt22: Drops off stale locations after 1/3 of progress",
|
||||
" items were placed to try to circumvent vt21\\'s",
|
||||
" shortcomings.",
|
||||
"Freshness: Keep track of stale locations (ones that cannot be",
|
||||
" reached yet) and decrease likeliness of selecting",
|
||||
" them the more often they were found unreachable.",
|
||||
"Flood: Push out items starting from Link\\'s House and",
|
||||
" slightly biased to placing progression items with",
|
||||
" less restrictions."
|
||||
],
|
||||
"shuffle": [
|
||||
"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.",
|
||||
"Insanity: Decouple entrances and exits from each other and",
|
||||
" shuffle them freely. Caves that used to be single",
|
||||
" entrance will still exit to the same location from",
|
||||
" which they are entered.",
|
||||
"Vanilla: All entrances are in the same locations they were",
|
||||
" in the base game.",
|
||||
"Legacy shuffles preserve behavior from older versions of the",
|
||||
"entrance randomizer including significant technical limitations.",
|
||||
"The dungeon variants only mix up dungeons and keep the rest of",
|
||||
"the overworld vanilla."
|
||||
],
|
||||
"door_shuffle": [
|
||||
"Select Door Shuffling Algorithm. (default: %(default)s)",
|
||||
"Basic: Doors are mixed within a single dungeon.",
|
||||
"Crossed: Doors are mixed between all dungeons.",
|
||||
"Vanilla: All doors are connected the same way they were in the",
|
||||
" base game."
|
||||
],
|
||||
"experimental": [ "Enable experimental features. (default: %(default)s)" ],
|
||||
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
|
||||
"crystals_ganon": [
|
||||
"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"
|
||||
],
|
||||
"crystals_gt": [
|
||||
"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"
|
||||
],
|
||||
"openpyramid": [ "Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. (default: %(default)s)" ],
|
||||
"rom": [
|
||||
"Path to an ALttP JP (1.0) rom to use as a base." ,
|
||||
"(default: %(default)s)"
|
||||
],
|
||||
"loglevel": [ "Select level of logging for output. (default: %(default)s)" ],
|
||||
"seed": [ "Define seed number to generate." ],
|
||||
"count": [
|
||||
"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 %(default)s seed(s) with",
|
||||
"--seed given will produce the same %(default)s (different) rom(s) each",
|
||||
"time)."
|
||||
],
|
||||
"fastmenu": [
|
||||
"Select the rate at which the menu opens and closes. (default: %(default)s)"
|
||||
],
|
||||
"quickswap": [ "Enable quick item swapping with L and R. (default: %(default)s)" ],
|
||||
"disablemusic": [ "Disables game music including MSU-1. (default: %(default)s)" ],
|
||||
"mapshuffle": [ "Maps are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||
"compassshuffle": [ "Compasses are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||
"bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||
"retro": [
|
||||
"Keys are universal, shooting arrows costs rupees,",
|
||||
"and a few other little things make this more like Zelda-1. (default: %(default)s)"
|
||||
],
|
||||
"startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ],
|
||||
"usestartinventory": [ "Toggle usage of Starting Inventory." ],
|
||||
"custom": [ "Not supported." ],
|
||||
"customitemarray": [ "Not supported." ],
|
||||
"accessibility": [
|
||||
"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."
|
||||
],
|
||||
"hints": [ "Make telepathic tiles and storytellers give helpful hints. (default: %(default)s)" ],
|
||||
"shuffleganon": [
|
||||
"Include the Ganon's Tower and Pyramid Hole in the",
|
||||
"entrance shuffle pool. (default: %(default)s)"
|
||||
],
|
||||
"heartbeep": [
|
||||
"Select the rate at which the heart beep sound is played at",
|
||||
"low health. (default: %(default)s)"
|
||||
],
|
||||
"heartcolor": [ "Select the color of Link\\'s heart meter. (default: %(default)s)" ],
|
||||
"sprite": [
|
||||
"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."
|
||||
],
|
||||
"create_rom": [ "Create an output rom file. (default: %(default)s)" ],
|
||||
"gui": [ "Launch the GUI. (default: %(default)s)" ],
|
||||
"jsonout": [
|
||||
"Output .json patch to stdout instead of a patched rom. Used",
|
||||
"for VT site integration, do not use otherwise. (default: %(default)s)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"cli": {
|
||||
"app.title": "ALttP Puerta Aleatorizador Versión %s - Número: %d",
|
||||
"player": "Jugador",
|
||||
"shuffling.world": "Barajando el Mundo",
|
||||
"shuffling.dungeons": "Barajando Mazmorras",
|
||||
"basic.traversal": "--Recorrido Básico",
|
||||
"generating.dungeon": "Generando mazmorra",
|
||||
"shuffling.keydoors": "Barajando Puertas Clave para",
|
||||
|
||||
"keylock.detected": "Bloqueo de Teclas detectado",
|
||||
"fill.world": "Llenar el Mundo",
|
||||
"balance.doors": "-Equilibriando Puertas",
|
||||
"re-balancing": "-Reequilibriando",
|
||||
"balancing": "--Equilibriando",
|
||||
"splitting.up": "División",
|
||||
|
||||
"cannot.beat.game": "No se puede vencer el juego. Algo salió terriblemente mal.",
|
||||
"cannot.reach.items": "No se pudo llegar a los siguientes elementos: %s",
|
||||
"cannot.reach.item": "%s (Jugador %d) at %s (Jugador %d)",
|
||||
"check.item.location": "Comprobar si se requiere que %s (Jugador %d) gane el juego.",
|
||||
"check.item.location.true": "Sí, se requiere artículo.",
|
||||
"check.item.location.false": "No, no se requiere artículo.",
|
||||
|
||||
"patching.rom": "Parchear ROM",
|
||||
"calc.playthrough": "Cálculo de Juego",
|
||||
"generation.failed": "Generación Fallida",
|
||||
"enemizer.not.found": "Enemizer no encontrado en",
|
||||
|
||||
"building.collection.spheres": "Construyendo esferas de recolección.",
|
||||
"building.calculating.spheres": "Esfera calculada %i, que contiene %i de %i elementos de progreso.",
|
||||
"building.final.spheres": "Esfera final calculada %i, que contiene %i de %i elementos de progreso."
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,11 @@
|
||||
{
|
||||
"checkboxes": {
|
||||
"nobgm": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Disable Music & MSU-1"
|
||||
}
|
||||
},
|
||||
"quickswap": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "L/R Quickswapping"
|
||||
}
|
||||
}
|
||||
"nobgm": { "type": "checkbox" },
|
||||
"quickswap": { "type": "checkbox" }
|
||||
},
|
||||
"leftAdjustFrame": {
|
||||
"heartcolor": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Heart Color"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Red": "red",
|
||||
"Blue": "blue",
|
||||
@@ -37,18 +16,7 @@
|
||||
},
|
||||
"heartbeep": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Heart Beep sound rate"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Normal"
|
||||
},
|
||||
"default": "Normal",
|
||||
"options": {
|
||||
"Double": "double",
|
||||
"Normal": "normal",
|
||||
@@ -61,18 +29,7 @@
|
||||
"rightAdjustFrame": {
|
||||
"menuspeed": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Menu Speed"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Normal"
|
||||
},
|
||||
"default": "Normal",
|
||||
"options": {
|
||||
"Instant": "instant",
|
||||
"Quadruple": "quadruple",
|
||||
@@ -84,17 +41,6 @@
|
||||
},
|
||||
"owpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Overworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
@@ -103,17 +49,6 @@
|
||||
},
|
||||
"uwpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Underworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
"text": "Bow"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressivebow": {
|
||||
"type": "textbox",
|
||||
@@ -19,12 +14,7 @@
|
||||
"text": "Progressive Bow"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"boomerang": {
|
||||
"type": "textbox",
|
||||
@@ -32,12 +22,7 @@
|
||||
"text": "Blue Boomerang"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"redmerang": {
|
||||
"type": "textbox",
|
||||
@@ -45,12 +30,7 @@
|
||||
"text": "Red Boomerang"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"hookshot": {
|
||||
"type": "textbox",
|
||||
@@ -58,12 +38,7 @@
|
||||
"text": "Hookshot"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"mushroom": {
|
||||
"type": "textbox",
|
||||
@@ -71,12 +46,7 @@
|
||||
"text": "Mushroom"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"powder": {
|
||||
"type": "textbox",
|
||||
@@ -84,12 +54,7 @@
|
||||
"text": "Magic Powder"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"firerod": {
|
||||
"type": "textbox",
|
||||
@@ -97,12 +62,7 @@
|
||||
"text": "Fire Rod"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"icerod": {
|
||||
"type": "textbox",
|
||||
@@ -110,12 +70,7 @@
|
||||
"text": "Ice Rod"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"bombos": {
|
||||
"type": "textbox",
|
||||
@@ -123,12 +78,7 @@
|
||||
"text": "Bombos"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"ether": {
|
||||
"type": "textbox",
|
||||
@@ -136,12 +86,7 @@
|
||||
"text": "Ether"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"quake": {
|
||||
"type": "textbox",
|
||||
@@ -149,12 +94,7 @@
|
||||
"text": "Quake"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"lamp": {
|
||||
"type": "textbox",
|
||||
@@ -162,12 +102,7 @@
|
||||
"text": "Lamp"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"hammer": {
|
||||
"type": "textbox",
|
||||
@@ -175,12 +110,7 @@
|
||||
"text": "Hammer"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"shovel": {
|
||||
"type": "textbox",
|
||||
@@ -188,13 +118,8 @@
|
||||
"text": "Shovel"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList2": {
|
||||
"flute": {
|
||||
@@ -203,12 +128,7 @@
|
||||
"text": "Flute"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"bugnet": {
|
||||
"type": "textbox",
|
||||
@@ -216,12 +136,7 @@
|
||||
"text": "Bug Net"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"book": {
|
||||
"type": "textbox",
|
||||
@@ -229,12 +144,7 @@
|
||||
"text": "Book"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"bottle": {
|
||||
"type": "textbox",
|
||||
@@ -242,12 +152,7 @@
|
||||
"text": "Bottle"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 4
|
||||
}
|
||||
},
|
||||
"somaria": {
|
||||
"type": "textbox",
|
||||
@@ -255,12 +160,7 @@
|
||||
"text": "Cane of Somaria"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"byrna": {
|
||||
"type": "textbox",
|
||||
@@ -268,12 +168,7 @@
|
||||
"text": "Cane of Byrna"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"cape": {
|
||||
"type": "textbox",
|
||||
@@ -281,12 +176,7 @@
|
||||
"text": "Magic Cape"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"mirror": {
|
||||
"type": "textbox",
|
||||
@@ -294,12 +184,7 @@
|
||||
"text": "Magic Mirror"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"boots": {
|
||||
"type": "textbox",
|
||||
@@ -307,12 +192,7 @@
|
||||
"text": "Pegasus Boots"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"powerglove": {
|
||||
"type": "textbox",
|
||||
@@ -320,12 +200,7 @@
|
||||
"text": "Power Glove"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"titansmitt": {
|
||||
"type": "textbox",
|
||||
@@ -333,12 +208,7 @@
|
||||
"text": "Titan's Mitt"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressiveglove": {
|
||||
"type": "textbox",
|
||||
@@ -346,12 +216,7 @@
|
||||
"text": "Progressive Glove"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"flippers": {
|
||||
"type": "textbox",
|
||||
@@ -359,12 +224,7 @@
|
||||
"text": "Flippers"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"pearl": {
|
||||
"type": "textbox",
|
||||
@@ -372,12 +232,7 @@
|
||||
"text": "Moon Pearl"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"heartpiece": {
|
||||
"type": "textbox",
|
||||
@@ -385,13 +240,8 @@
|
||||
"text": "Piece of Heart"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 24
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList3": {
|
||||
"heartcontainer": {
|
||||
@@ -400,12 +250,7 @@
|
||||
"text": "Heart Container"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 10
|
||||
}
|
||||
},
|
||||
"sancheart": {
|
||||
"type": "textbox",
|
||||
@@ -413,12 +258,7 @@
|
||||
"text": "Sanctuary Heart"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"sword1": {
|
||||
"type": "textbox",
|
||||
@@ -426,12 +266,7 @@
|
||||
"text": "Fighters' Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"sword2": {
|
||||
"type": "textbox",
|
||||
@@ -439,12 +274,7 @@
|
||||
"text": "Master Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"sword3": {
|
||||
"type": "textbox",
|
||||
@@ -452,12 +282,7 @@
|
||||
"text": "Tempered Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"sword4": {
|
||||
"type": "textbox",
|
||||
@@ -465,12 +290,7 @@
|
||||
"text": "Golden Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressivesword": {
|
||||
"type": "textbox",
|
||||
@@ -478,12 +298,7 @@
|
||||
"text": "Progressive Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 4
|
||||
}
|
||||
},
|
||||
"shield1": {
|
||||
"type": "textbox",
|
||||
@@ -491,12 +306,7 @@
|
||||
"text": "Fighters' Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"shield2": {
|
||||
"type": "textbox",
|
||||
@@ -504,12 +314,7 @@
|
||||
"text": "Fire Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"shield3": {
|
||||
"type": "textbox",
|
||||
@@ -517,12 +322,7 @@
|
||||
"text": "Mirror Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressiveshield": {
|
||||
"type": "textbox",
|
||||
@@ -530,12 +330,7 @@
|
||||
"text": "Progressive Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 3
|
||||
}
|
||||
},
|
||||
"mail2": {
|
||||
"type": "textbox",
|
||||
@@ -543,12 +338,7 @@
|
||||
"text": "Blue Mail"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"mail3": {
|
||||
"type": "textbox",
|
||||
@@ -556,12 +346,7 @@
|
||||
"text": "Red Mail"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressivemail": {
|
||||
"type": "textbox",
|
||||
@@ -569,12 +354,7 @@
|
||||
"text": "Progressive Mail"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"halfmagic": {
|
||||
"type": "textbox",
|
||||
@@ -582,13 +362,8 @@
|
||||
"text": "Half Magic"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList4": {
|
||||
"quartermagic": {
|
||||
@@ -597,12 +372,7 @@
|
||||
"text": "Quarter Magic"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"bombsplus5": {
|
||||
"type": "textbox",
|
||||
@@ -610,12 +380,7 @@
|
||||
"text": "Bomb Cap +5"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"bombsplus10": {
|
||||
"type": "textbox",
|
||||
@@ -623,12 +388,7 @@
|
||||
"text": "Bomb Cap +10"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"arrowsplus5": {
|
||||
"type": "textbox",
|
||||
@@ -636,12 +396,7 @@
|
||||
"text": "Arrow Cap +5"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"arrowsplus10": {
|
||||
"type": "textbox",
|
||||
@@ -649,12 +404,7 @@
|
||||
"text": "Arrow Cap +10"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"arrow1": {
|
||||
"type": "textbox",
|
||||
@@ -662,12 +412,7 @@
|
||||
"text": "Arrow (1)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"arrow10": {
|
||||
"type": "textbox",
|
||||
@@ -675,12 +420,7 @@
|
||||
"text": "Arrow (10)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 12
|
||||
}
|
||||
},
|
||||
"bomb1": {
|
||||
"type": "textbox",
|
||||
@@ -688,12 +428,7 @@
|
||||
"text": "Bomb (1)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"bomb3": {
|
||||
"type": "textbox",
|
||||
@@ -701,12 +436,7 @@
|
||||
"text": "Bomb (3)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 16
|
||||
}
|
||||
},
|
||||
"bomb10": {
|
||||
"type": "textbox",
|
||||
@@ -714,12 +444,7 @@
|
||||
"text": "Bomb (10)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"rupee1": {
|
||||
"type": "textbox",
|
||||
@@ -727,12 +452,7 @@
|
||||
"text": "Rupee (1)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"rupee5": {
|
||||
"type": "textbox",
|
||||
@@ -740,12 +460,7 @@
|
||||
"text": "Rupee (5)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 4
|
||||
}
|
||||
},
|
||||
"rupee20": {
|
||||
"type": "textbox",
|
||||
@@ -753,12 +468,7 @@
|
||||
"text": "Rupee (20)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 28
|
||||
}
|
||||
},
|
||||
"rupee50": {
|
||||
"type": "textbox",
|
||||
@@ -766,12 +476,7 @@
|
||||
"text": "Rupee (50)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 7
|
||||
}
|
||||
},
|
||||
"rupee100": {
|
||||
"type": "textbox",
|
||||
@@ -779,13 +484,8 @@
|
||||
"text": "Rupee (100)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList5": {
|
||||
"rupee300": {
|
||||
@@ -794,12 +494,7 @@
|
||||
"text": "Rupee (300)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 5
|
||||
}
|
||||
},
|
||||
"blueclock": {
|
||||
"type": "textbox",
|
||||
@@ -807,12 +502,7 @@
|
||||
"text": "Blue Clock"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"greenclock": {
|
||||
"type": "textbox",
|
||||
@@ -820,12 +510,7 @@
|
||||
"text": "Green Clock"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"redclock": {
|
||||
"type": "textbox",
|
||||
@@ -833,12 +518,7 @@
|
||||
"text": "Red Clock"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"silversupgrade": {
|
||||
"type": "textbox",
|
||||
@@ -846,12 +526,7 @@
|
||||
"text": "Silver Arrows Upgrade"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"generickeys": {
|
||||
"type": "textbox",
|
||||
@@ -859,12 +534,7 @@
|
||||
"text": "Generic Keys"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"triforcepieces": {
|
||||
"type": "textbox",
|
||||
@@ -872,12 +542,7 @@
|
||||
"text": "Triforce Pieces"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"triforcepiecesgoal": {
|
||||
"type": "textbox",
|
||||
@@ -885,12 +550,7 @@
|
||||
"text": "Triforce Pieces Goal"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"triforce": {
|
||||
"type": "textbox",
|
||||
@@ -898,12 +558,7 @@
|
||||
"text": "Triforce (win game)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"rupoor": {
|
||||
"type": "textbox",
|
||||
@@ -911,12 +566,7 @@
|
||||
"text": "Rupoor"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"rupoorcost": {
|
||||
"type": "textbox",
|
||||
@@ -924,12 +574,7 @@
|
||||
"text": "Rupoor Cost"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
{
|
||||
"gui": {
|
||||
"adjust.nobgm": "Disable Music & MSU-1",
|
||||
"adjust.quickswap": "L/R Quickswapping",
|
||||
|
||||
"adjust.heartcolor": "Heart Color",
|
||||
"adjust.heartcolor.red": "Red",
|
||||
"adjust.heartcolor.blue": "Blue",
|
||||
"adjust.heartcolor.green": "Green",
|
||||
"adjust.heartcolor.yellow": "Yellow",
|
||||
"adjust.heartcolor.random": "Random",
|
||||
|
||||
"adjust.heartbeep": "Heart Beep sound rate",
|
||||
"adjust.heartbeep.double": "Double",
|
||||
"adjust.heartbeep.normal": "Normal",
|
||||
"adjust.heartbeep.half": "Half",
|
||||
"adjust.heartbeep.quarter": "Quarter",
|
||||
"adjust.heartbeep.off": "Off",
|
||||
|
||||
"adjust.menuspeed": "Menu Speed",
|
||||
"adjust.menuspeed.instant": "Instant",
|
||||
"adjust.menuspeed.quadruple": "Quadruple",
|
||||
"adjust.menuspeed.triple": "Triple",
|
||||
"adjust.menuspeed.double": "Double",
|
||||
"adjust.menuspeed.normal": "Normal",
|
||||
"adjust.menuspeed.half": "Half",
|
||||
|
||||
"adjust.owpalettes": "Overworld Palettes",
|
||||
"adjust.owpalettes.default": "Default",
|
||||
"adjust.owpalettes.random": "Random",
|
||||
"adjust.owpalettes.blackout": "Blackout",
|
||||
|
||||
"adjust.uwpalettes": "Underworld Palettes",
|
||||
"adjust.uwpalettes.default": "Default",
|
||||
"adjust.uwpalettes.random": "Random",
|
||||
"adjust.uwpalettes.blackout": "Blackout",
|
||||
|
||||
"adjust.sprite": "Sprite:",
|
||||
"adjust.sprite.unchanged": "(unchanged)",
|
||||
|
||||
"adjust.rom": "Rom to adjust: ",
|
||||
"adjust.rom.romfiles": "Rom Files",
|
||||
"adjust.rom.button": "Select Rom",
|
||||
"adjust.rom.go": "Adjust Rom",
|
||||
"adjust.rom.dialog.error": "Error while patching",
|
||||
"adjust.rom.dialog.success": "Success",
|
||||
"adjust.rom.dialog.success.message": "Rom patched successfully.",
|
||||
|
||||
|
||||
"randomizer.dungeon.keysanity": "Shuffle: ",
|
||||
"randomizer.dungeon.mapshuffle": "Maps",
|
||||
"randomizer.dungeon.compassshuffle": "Compasses",
|
||||
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
|
||||
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
||||
|
||||
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
|
||||
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
||||
"randomizer.dungeon.dungeondoorshuffle.basic": "Basic",
|
||||
"randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed",
|
||||
|
||||
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
||||
|
||||
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
||||
"randomizer.dungeon.dungeon_counters.default": "Auto",
|
||||
"randomizer.dungeon.dungeon_counters.off": "Off",
|
||||
"randomizer.dungeon.dungeon_counters.on": "On",
|
||||
"randomizer.dungeon.dungeon_counters.pickup": "On Compass Pickup",
|
||||
|
||||
|
||||
"randomizer.enemizer.potshuffle": "Pot Shuffle",
|
||||
|
||||
"randomizer.enemizer.enemyshuffle": "Enemy Shuffle",
|
||||
"randomizer.enemizer.enemyshuffle.none": "Vanilla",
|
||||
"randomizer.enemizer.enemyshuffle.shuffled": "Shuffled",
|
||||
"randomizer.enemizer.enemyshuffle.chaos": "Chaos",
|
||||
|
||||
"randomizer.enemizer.bossshuffle": "Boss Shuffle",
|
||||
"randomizer.enemizer.bossshuffle.none": "Vanilla",
|
||||
"randomizer.enemizer.bossshuffle.basic": "Basic",
|
||||
"randomizer.enemizer.bossshuffle.shuffled": "Shuffled",
|
||||
"randomizer.enemizer.bossshuffle.chaos": "Chaos",
|
||||
|
||||
"randomizer.enemizer.enemydamage": "Enemy Damage",
|
||||
"randomizer.enemizer.enemydamage.default": "Vanilla",
|
||||
"randomizer.enemizer.enemydamage.shuffled": "Shuffled",
|
||||
"randomizer.enemizer.enemydamage.chaos": "Chaos",
|
||||
|
||||
"randomizer.enemizer.enemyhealth": "Enemy Health",
|
||||
"randomizer.enemizer.enemyhealth.default": "Vanilla",
|
||||
"randomizer.enemizer.enemyhealth.easy": "Easy",
|
||||
"randomizer.enemizer.enemyhealth.normal": "Normal",
|
||||
"randomizer.enemizer.enemyhealth.hard": "Hard",
|
||||
"randomizer.enemizer.enemyhealth.expert": "Expert",
|
||||
|
||||
"randomizer.enemizer.enemizercli": "EnemizerCLI path: ",
|
||||
"randomizer.enemizer.enemizercli.online": "(get online)",
|
||||
|
||||
|
||||
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
|
||||
"randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool",
|
||||
|
||||
"randomizer.entrance.entranceshuffle": "Entrance Shuffle",
|
||||
"randomizer.entrance.entranceshuffle.vanilla": "Vanilla",
|
||||
"randomizer.entrance.entranceshuffle.simple": "Simple",
|
||||
"randomizer.entrance.entranceshuffle.restricted": "Restricted",
|
||||
"randomizer.entrance.entranceshuffle.full": "Full",
|
||||
"randomizer.entrance.entranceshuffle.crossed": "Crossed",
|
||||
"randomizer.entrance.entranceshuffle.insanity": "Insanity",
|
||||
"randomizer.entrance.entranceshuffle.restricted_legacy": "Restricted (Legacy)",
|
||||
"randomizer.entrance.entranceshuffle.full_legacy": "Full (Legacy)",
|
||||
"randomizer.entrance.entranceshuffle.madness_legacy": "Madness (Legacy)",
|
||||
"randomizer.entrance.entranceshuffle.insanity_legacy": "Insanity (Legacy)",
|
||||
"randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full",
|
||||
"randomizer.entrance.entranceshuffle.dungeonssimple": "Dungeons + Simple",
|
||||
|
||||
|
||||
"randomizer.gameoptions.hints": "Include Helpful Hints",
|
||||
"randomizer.gameoptions.nobgm": "Disable Music & MSU-1",
|
||||
"randomizer.gameoptions.quickswap": "L/R Quickswapping",
|
||||
|
||||
"randomizer.gameoptions.heartcolor": "Heart Color",
|
||||
"randomizer.gameoptions.heartcolor.red": "Red",
|
||||
"randomizer.gameoptions.heartcolor.blue": "Blue",
|
||||
"randomizer.gameoptions.heartcolor.green": "Green",
|
||||
"randomizer.gameoptions.heartcolor.yellow": "Yellow",
|
||||
"randomizer.gameoptions.heartcolor.random": "Random",
|
||||
|
||||
"randomizer.gameoptions.heartbeep": "Heart Beep sound rate",
|
||||
"randomizer.gameoptions.heartbeep.double": "Double",
|
||||
"randomizer.gameoptions.heartbeep.normal": "Normal",
|
||||
"randomizer.gameoptions.heartbeep.half": "Half",
|
||||
"randomizer.gameoptions.heartbeep.quarter": "Quarter",
|
||||
"randomizer.gameoptions.heartbeep.off": "Off",
|
||||
|
||||
"randomizer.gameoptions.menuspeed": "Menu Speed",
|
||||
"randomizer.gameoptions.menuspeed.instant": "Instant",
|
||||
"randomizer.gameoptions.menuspeed.quadruple": "Quadruple",
|
||||
"randomizer.gameoptions.menuspeed.triple": "Triple",
|
||||
"randomizer.gameoptions.menuspeed.double": "Double",
|
||||
"randomizer.gameoptions.menuspeed.normal": "Normal",
|
||||
"randomizer.gameoptions.menuspeed.half": "Half",
|
||||
|
||||
"randomizer.gameoptions.owpalettes": "Overworld Palettes",
|
||||
"randomizer.gameoptions.owpalettes.default": "Default",
|
||||
"randomizer.gameoptions.owpalettes.random": "Random",
|
||||
"randomizer.gameoptions.owpalettes.blackout": "Blackout",
|
||||
|
||||
"randomizer.gameoptions.uwpalettes": "Underworld Palettes",
|
||||
"randomizer.gameoptions.uwpalettes.default": "Default",
|
||||
"randomizer.gameoptions.uwpalettes.random": "Random",
|
||||
"randomizer.gameoptions.uwpalettes.blackout": "Blackout",
|
||||
|
||||
"randomizer.gameoptions.sprite": "Sprite:",
|
||||
"randomizer.gameoptions.sprite.unchanged": "(unchanged)",
|
||||
|
||||
|
||||
"randomizer.generation.createspoiler": "Create Spoiler Log",
|
||||
"randomizer.generation.createrom": "Create Patched ROM",
|
||||
"randomizer.generation.calcplaythrough": "Calculate Playthrough",
|
||||
"randomizer.generation.usestartinventory": "Use Starting Inventory",
|
||||
"randomizer.generation.usecustompool": "Use Custom Item Pool",
|
||||
|
||||
"randomizer.generation.saveonexit": "Save Settings on Exit",
|
||||
"randomizer.generation.saveonexit.ask": "Ask Me",
|
||||
"randomizer.generation.saveonexit.always": "Always",
|
||||
"randomizer.generation.saveonexit.never": "Never",
|
||||
|
||||
"randomizer.generation.rom": "Base Rom: ",
|
||||
"randomizer.generation.rom.button": "Select Rom",
|
||||
"randomizer.generation.rom.dialog.romfiles": "Rom Files",
|
||||
"randomizer.generation.rom.dialog.allfiles": "All Files",
|
||||
|
||||
|
||||
"randomizer.item.retro": "Retro mode (universal keys)",
|
||||
|
||||
"randomizer.item.worldstate": "World State",
|
||||
"randomizer.item.worldstate.standard": "Standard",
|
||||
"randomizer.item.worldstate.open": "Open",
|
||||
"randomizer.item.worldstate.inverted": "Inverted",
|
||||
"randomizer.item.worldstate.retro": "Retro",
|
||||
|
||||
"randomizer.item.logiclevel": "Logic Level",
|
||||
"randomizer.item.logiclevel.noglitches": "No Glitches",
|
||||
"randomizer.item.logiclevel.minorglitches": "Minor Glitches",
|
||||
"randomizer.item.logiclevel.nologic": "No Logic",
|
||||
|
||||
"randomizer.item.goal": "Goal",
|
||||
"randomizer.item.goal.ganon": "Defeat Ganon",
|
||||
"randomizer.item.goal.pedestal": "Master Sword Pedestal",
|
||||
"randomizer.item.goal.dungeons": "All Dungeons",
|
||||
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
||||
"randomizer.item.goal.crystals": "Crystals",
|
||||
|
||||
"randomizer.item.crystals_gt": "Crystals to open GT",
|
||||
"randomizer.item.crystals_gt.0": "0",
|
||||
"randomizer.item.crystals_gt.1": "1",
|
||||
"randomizer.item.crystals_gt.2": "2",
|
||||
"randomizer.item.crystals_gt.3": "3",
|
||||
"randomizer.item.crystals_gt.4": "4",
|
||||
"randomizer.item.crystals_gt.5": "5",
|
||||
"randomizer.item.crystals_gt.6": "6",
|
||||
"randomizer.item.crystals_gt.7": "7",
|
||||
"randomizer.item.crystals_gt.random": "Random",
|
||||
|
||||
"randomizer.item.crystals_ganon": "Crystals to harm Ganon",
|
||||
"randomizer.item.crystals_ganon.0": "0",
|
||||
"randomizer.item.crystals_ganon.1": "1",
|
||||
"randomizer.item.crystals_ganon.2": "2",
|
||||
"randomizer.item.crystals_ganon.3": "3",
|
||||
"randomizer.item.crystals_ganon.4": "4",
|
||||
"randomizer.item.crystals_ganon.5": "5",
|
||||
"randomizer.item.crystals_ganon.6": "6",
|
||||
"randomizer.item.crystals_ganon.7": "7",
|
||||
"randomizer.item.crystals_ganon.random": "Random",
|
||||
|
||||
"randomizer.item.weapons": "Weapons",
|
||||
"randomizer.item.weapons.random": "Randomized",
|
||||
"randomizer.item.weapons.assured": "Assured",
|
||||
"randomizer.item.weapons.swordless": "Swordless",
|
||||
"randomizer.item.weapons.vanilla": "Vanilla",
|
||||
|
||||
"randomizer.item.itempool": "Item Pool",
|
||||
"randomizer.item.itempool.normal": "Normal",
|
||||
"randomizer.item.itempool.hard": "Hard",
|
||||
"randomizer.item.itempool.expert": "Expert",
|
||||
|
||||
"randomizer.item.itemfunction": "Item Functionality",
|
||||
"randomizer.item.itemfunction.normal": "Normal",
|
||||
"randomizer.item.itemfunction.hard": "Hard",
|
||||
"randomizer.item.itemfunction.expert": "Expert",
|
||||
|
||||
"randomizer.item.timer": "Timer Setting",
|
||||
"randomizer.item.timer.none": "No Timer",
|
||||
"randomizer.item.timer.display": "Stopwatch",
|
||||
"randomizer.item.timer.timed": "Timed",
|
||||
"randomizer.item.timer.timed-ohko": "Timed OHKO",
|
||||
"randomizer.item.timer.ohko": "OHKO",
|
||||
"randomizer.item.timer.timed-countdown": "Timed Countdown",
|
||||
|
||||
"randomizer.item.progressives": "Progressive Items",
|
||||
"randomizer.item.progressives.on": "On",
|
||||
"randomizer.item.progressives.off": "Off",
|
||||
"randomizer.item.progressives.random": "Random",
|
||||
|
||||
"randomizer.item.accessibility": "Accessibility",
|
||||
"randomizer.item.accessibility.items": "100% Inventory",
|
||||
"randomizer.item.accessibility.locations": "100% Locations",
|
||||
"randomizer.item.accessibility.none": "Beatable",
|
||||
|
||||
"randomizer.item.sortingalgo": "Item Sorting",
|
||||
"randomizer.item.sortingalgo.freshness": "Freshness",
|
||||
"randomizer.item.sortingalgo.flood": "Flood",
|
||||
"randomizer.item.sortingalgo.vt21": "VT8.21",
|
||||
"randomizer.item.sortingalgo.vt22": "VT8.22",
|
||||
"randomizer.item.sortingalgo.vt25": "VT8.25",
|
||||
"randomizer.item.sortingalgo.vt26": "VT8.26",
|
||||
"randomizer.item.sortingalgo.balanced": "Balanced",
|
||||
|
||||
|
||||
"bottom.content.worlds": "Worlds",
|
||||
"bottom.content.names": "Player names",
|
||||
"bottom.content.seed": "Seed #",
|
||||
"bottom.content.generationcount": "Count",
|
||||
"bottom.content.go": "Generate Patched Rom",
|
||||
"bottom.content.dialog.error": "Error while creating seed",
|
||||
"bottom.content.dialog.success": "Success",
|
||||
"bottom.content.dialog.success.message": "Rom created successfully.",
|
||||
"bottom.content.outputdir": "Open Output Directory",
|
||||
"bottom.content.docs": "Open Documentation"
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,8 @@
|
||||
{
|
||||
"mapshuffle": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Maps"
|
||||
}
|
||||
},
|
||||
"compassshuffle": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Compasses"
|
||||
}
|
||||
},
|
||||
"smallkeyshuffle": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Small Keys"
|
||||
}
|
||||
},
|
||||
"bigkeyshuffle": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Big Keys"
|
||||
}
|
||||
"keysanity": {
|
||||
"mapshuffle": { "type": "checkbox" },
|
||||
"compassshuffle": { "type": "checkbox" },
|
||||
"smallkeyshuffle": { "type": "checkbox" },
|
||||
"bigkeyshuffle": { "type": "checkbox" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,24 @@
|
||||
{
|
||||
"widgets": {
|
||||
"dungeondoorshuffle": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Dungeon Door Shuffle"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Basic"
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "vanilla",
|
||||
"Basic": "basic",
|
||||
"Crossed": "crossed"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Enable Experimental Features"
|
||||
}
|
||||
"default": "basic",
|
||||
"options": [
|
||||
"vanilla",
|
||||
"basic",
|
||||
"crossed"
|
||||
]
|
||||
},
|
||||
"experimental": { "type": "checkbox" },
|
||||
"dungeon_counters": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Dungeon Chest Counters"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Auto"
|
||||
},
|
||||
"options": {
|
||||
"Auto": "default",
|
||||
"Off": "off",
|
||||
"On": "on",
|
||||
"On Compass Pickup": "pickup"
|
||||
"default": "default",
|
||||
"options": [
|
||||
"default",
|
||||
"off",
|
||||
"on",
|
||||
"pickup"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,44 @@
|
||||
{
|
||||
"checkboxes": {
|
||||
"potshuffle": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Pot Shuffle"
|
||||
}
|
||||
}
|
||||
"potshuffle": { "type": "checkbox" }
|
||||
},
|
||||
"leftEnemizerFrame": {
|
||||
"enemyshuffle": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Enemy Shuffle"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "none",
|
||||
"Shuffled": "shuffled",
|
||||
"Chaos": "chaos"
|
||||
}
|
||||
"options": [
|
||||
"none",
|
||||
"shuffled",
|
||||
"chaos"
|
||||
]
|
||||
},
|
||||
"bossshuffle": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Boss Shuffle"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "none",
|
||||
"Basic": "basic",
|
||||
"Shuffled": "shuffled",
|
||||
"Chaos": "chaos"
|
||||
}
|
||||
"options": [
|
||||
"none",
|
||||
"basic",
|
||||
"shuffled",
|
||||
"chaos"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rightEnemizerFrame": {
|
||||
"enemydamage": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Enemy Damage"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "default",
|
||||
"Shuffled": "shuffled",
|
||||
"Chaos": "chaos"
|
||||
}
|
||||
"options": [
|
||||
"default",
|
||||
"shuffled",
|
||||
"chaos"
|
||||
]
|
||||
},
|
||||
"enemyhealth": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Enemy Health"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "default",
|
||||
"Easy": "easy",
|
||||
"Normal": "normal",
|
||||
"Hard": "hard",
|
||||
"Expert": "expert"
|
||||
}
|
||||
"options": [
|
||||
"default",
|
||||
"easy",
|
||||
"normal",
|
||||
"hard",
|
||||
"expert"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,23 @@
|
||||
{
|
||||
"widgets": {
|
||||
"openpyramid": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Pre-open Pyramid Hole"
|
||||
}
|
||||
},
|
||||
"shuffleganon": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Include Ganon's Tower and Pyramid Hole in shuffle pool"
|
||||
}
|
||||
},
|
||||
"openpyramid": { "type": "checkbox" },
|
||||
"shuffleganon": { "type": "checkbox" },
|
||||
"entranceshuffle": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Entrance Shuffle"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": { "side": "left" },
|
||||
"selectbox": { "side": "right" }
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "vanilla",
|
||||
"Simple": "simple",
|
||||
"Restricted": "restricted",
|
||||
"Full": "full",
|
||||
"Crossed": "crossed",
|
||||
"Insanity": "insanity",
|
||||
"Restricted (Legacy)": "restricted_legacy",
|
||||
"Full (Legacy)": "full_legacy",
|
||||
"Madness (Legacy)": "madness_legacy",
|
||||
"Insanity (Legacy)": "insanity_legacy",
|
||||
"Dungeons + Full": "dungeonsfull",
|
||||
"Dungeons + Simple": "dungeonssimple"
|
||||
}
|
||||
"options": [
|
||||
"vanilla",
|
||||
"simple",
|
||||
"restricted",
|
||||
"full",
|
||||
"crossed",
|
||||
"insanity",
|
||||
"restricted_legacy",
|
||||
"full_legacy",
|
||||
"madness_legacy",
|
||||
"insanity_legacy",
|
||||
"dungeonsfull",
|
||||
"dungeonssimple"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,130 +2,62 @@
|
||||
"checkboxes": {
|
||||
"hints": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Include Helpful Hints"
|
||||
},
|
||||
"default": "true"
|
||||
},
|
||||
"nobgm": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Disable Music & MSU-1"
|
||||
}
|
||||
},
|
||||
"quickswap": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "L/R Quickswapping"
|
||||
}
|
||||
}
|
||||
"nobgm": { "type": "checkbox" },
|
||||
"quickswap": { "type": "checkbox" }
|
||||
},
|
||||
"leftRomOptionsFrame": {
|
||||
"heartcolor": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Heart Color"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Red": "red",
|
||||
"Blue": "blue",
|
||||
"Green": "green",
|
||||
"Yellow": "yellow",
|
||||
"Random": "random"
|
||||
}
|
||||
"options": [
|
||||
"red",
|
||||
"blue",
|
||||
"green",
|
||||
"yellow",
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"heartbeep": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Heart Beep sound rate"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Normal"
|
||||
},
|
||||
"options": {
|
||||
"Double": "double",
|
||||
"Normal": "normal",
|
||||
"Half": "half",
|
||||
"Quarter": "quarter",
|
||||
"Off": "off"
|
||||
}
|
||||
"default": "normal",
|
||||
"options": [
|
||||
"double",
|
||||
"normal",
|
||||
"half",
|
||||
"quarter",
|
||||
"off"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rightRomOptionsFrame": {
|
||||
"menuspeed": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Menu Speed"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Normal"
|
||||
},
|
||||
"options": {
|
||||
"Instant": "instant",
|
||||
"Quadruple": "quadruple",
|
||||
"Triple": "triple",
|
||||
"Double": "double",
|
||||
"Normal": "normal",
|
||||
"Half": "half"
|
||||
}
|
||||
"default": "normal",
|
||||
"options": [
|
||||
"instant",
|
||||
"quadruple",
|
||||
"triple",
|
||||
"double",
|
||||
"normal",
|
||||
"half"
|
||||
]
|
||||
},
|
||||
"owpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Overworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
"Blackout": "blackout"
|
||||
}
|
||||
"options": [
|
||||
"default",
|
||||
"random",
|
||||
"blackout"
|
||||
]
|
||||
},
|
||||
"uwpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Underworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
"Blackout": "blackout"
|
||||
}
|
||||
"options": [
|
||||
"default",
|
||||
"random",
|
||||
"blackout"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,9 @@
|
||||
{
|
||||
"spoiler": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Create Spoiler Log"
|
||||
}
|
||||
},
|
||||
"suppressrom": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Do not create patched ROM"
|
||||
}
|
||||
},
|
||||
"usestartinventory": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Use starting inventory"
|
||||
}
|
||||
},
|
||||
"usecustompool": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Use custom item pool"
|
||||
}
|
||||
},
|
||||
"saveonexit": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Save Settings on Exit"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Ask Me": "ask",
|
||||
"Always": "always",
|
||||
"Never": "never"
|
||||
}
|
||||
"checkboxes": {
|
||||
"createspoiler": { "type": "checkbox" },
|
||||
"createrom": { "type": "checkbox" },
|
||||
"calcplaythrough": { "type": "checkbox" },
|
||||
"usestartinventory": { "type": "checkbox" },
|
||||
"usecustompool": { "type": "checkbox" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"widgets": {
|
||||
"saveonexit": {
|
||||
"type": "selectbox",
|
||||
"options": [
|
||||
"ask",
|
||||
"always",
|
||||
"never"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,266 +1,116 @@
|
||||
{
|
||||
"checkboxes": {
|
||||
"retro": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Retro mode (universal keys)"
|
||||
}
|
||||
}
|
||||
"retro": { "type": "checkbox" }
|
||||
},
|
||||
"leftItemFrame": {
|
||||
"worldstate": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "World State"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Open"
|
||||
},
|
||||
"options": {
|
||||
"Standard": "standard",
|
||||
"Open": "open",
|
||||
"Inverted": "inverted"
|
||||
}
|
||||
"default": "open",
|
||||
"options": [
|
||||
"standard",
|
||||
"open",
|
||||
"inverted",
|
||||
"retro"
|
||||
]
|
||||
},
|
||||
"logiclevel": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Logic Level"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"No Glitches": "noglitches",
|
||||
"Minor Glitches": "minorglitches",
|
||||
"No Logic": "nologic"
|
||||
}
|
||||
"options": [
|
||||
"noglitches",
|
||||
"minorglitches",
|
||||
"nologic"
|
||||
]
|
||||
},
|
||||
"goal": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Goal"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Defeat Ganon": "ganon",
|
||||
"Master Sword Pedestal": "pedestal",
|
||||
"All Dungeons": "dungeons",
|
||||
"Triforce Hunt": "triforcehunt",
|
||||
"Crystals": "crystals"
|
||||
}
|
||||
"options": [
|
||||
"ganon",
|
||||
"pedestal",
|
||||
"dungeons",
|
||||
"triforcehunt",
|
||||
"crystals"
|
||||
]
|
||||
},
|
||||
"crystals_gt": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Crystals to open GT"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"0": "0",
|
||||
"1": "1",
|
||||
"2": "2",
|
||||
"3": "3",
|
||||
"4": "4",
|
||||
"5": "5",
|
||||
"6": "6",
|
||||
"7": "7",
|
||||
"Random": "random"
|
||||
}
|
||||
"options": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"crystals_ganon": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Crystals to harm Ganon"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"0": "0",
|
||||
"1": "1",
|
||||
"2": "2",
|
||||
"3": "3",
|
||||
"4": "4",
|
||||
"5": "5",
|
||||
"6": "6",
|
||||
"7": "7",
|
||||
"Random": "random"
|
||||
}
|
||||
"options": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"weapons": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Weapons"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Randomized": "random",
|
||||
"Assured": "assured",
|
||||
"Swordless": "swordless",
|
||||
"Vanilla": "vanilla"
|
||||
}
|
||||
"options": [
|
||||
"random",
|
||||
"assured",
|
||||
"swordless",
|
||||
"vanilla"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rightItemFrame": {
|
||||
"itempool": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Item Pool"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Normal": "normal",
|
||||
"Hard": "hard",
|
||||
"Expert": "expert"
|
||||
}
|
||||
"options": [
|
||||
"normal",
|
||||
"hard",
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"itemfunction": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Item Functionality"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Normal": "normal",
|
||||
"Hard": "hard",
|
||||
"Expert": "expert"
|
||||
}
|
||||
"options": [
|
||||
"normal",
|
||||
"hard",
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"timer": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Timer Setting"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"No Timer": "none",
|
||||
"Stopwatch": "display",
|
||||
"Timed": "timed",
|
||||
"Timed OHKO": "timed-ohko",
|
||||
"OHKO": "ohko",
|
||||
"Timed Countdown": "timed-countdown"
|
||||
}
|
||||
"options": [
|
||||
"none",
|
||||
"display",
|
||||
"timed",
|
||||
"timed-ohko",
|
||||
"ohko",
|
||||
"timed-countdown"
|
||||
]
|
||||
},
|
||||
"progressives": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Progressive Items"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"On": "on",
|
||||
"Off": "off",
|
||||
"Random": "random"
|
||||
}
|
||||
"options": [
|
||||
"on",
|
||||
"off",
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"accessibility": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Accessibility"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"100% Inventory": "items",
|
||||
"100% Locations": "locations",
|
||||
"Beatable": "none"
|
||||
}
|
||||
"options": [
|
||||
"items",
|
||||
"locations",
|
||||
"none"
|
||||
]
|
||||
},
|
||||
"sortingalgo": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Item Sorting"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Balanced"
|
||||
},
|
||||
"options": {
|
||||
"Freshness": "freshness",
|
||||
"Flood": "flood",
|
||||
"VT8.21": "vt21",
|
||||
"VT8.22": "vt22",
|
||||
"VT8.25": "vt25",
|
||||
"VT8.26": "vt26",
|
||||
"Balanced": "balanced"
|
||||
}
|
||||
"default": "balanced",
|
||||
"options": [
|
||||
"freshness",
|
||||
"flood",
|
||||
"vt21",
|
||||
"vt22",
|
||||
"vt25",
|
||||
"vt26",
|
||||
"balanced"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
{
|
||||
"worlds": {
|
||||
"type": "spinbox",
|
||||
"label": {
|
||||
"text": "Worlds"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"spinbox": {
|
||||
"side": "right"
|
||||
}
|
||||
}
|
||||
"widgets": {
|
||||
"worlds": { "type": "spinbox" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
aenum
|
||||
@@ -0,0 +1 @@
|
||||
#do nothing, just exist to make "resources.ci" package
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "resources.ci.common" package
|
||||
@@ -0,0 +1,137 @@
|
||||
import os # for env vars
|
||||
import stat # file statistics
|
||||
|
||||
# take number of bytes and convert to string with units measure
|
||||
def convert_bytes(num):
|
||||
for x in ["bytes","KB","MB","GB","TB","PB"]:
|
||||
if num < 1024.0:
|
||||
return "%3.1f %s" % (num,x)
|
||||
num /= 1024.0
|
||||
|
||||
# get filesize of file at path
|
||||
def file_size(file_path):
|
||||
if os.path.isfile(file_path):
|
||||
file_info = os.stat(file_path)
|
||||
return convert_bytes(file_info.st_size)
|
||||
|
||||
# prepare environment variables
|
||||
def prepare_env():
|
||||
DEFAULT_EVENT = "event"
|
||||
DEFAULT_REPO_SLUG = "miketrethewey/ALttPDoorRandomizer"
|
||||
|
||||
env = {}
|
||||
|
||||
# get app version
|
||||
APP_VERSION = ""
|
||||
APP_VERSION_FILE = os.path.join(".","resources","app","meta","manifests","app_version.txt")
|
||||
if os.path.isfile(APP_VERSION_FILE):
|
||||
with open(APP_VERSION_FILE,"r") as f:
|
||||
APP_VERSION = f.readlines()[0].strip()
|
||||
# ci data
|
||||
env["CI_SYSTEM"] = os.getenv("CI_SYSTEM","")
|
||||
# git data
|
||||
env["BRANCH"] = os.getenv("TRAVIS_BRANCH","")
|
||||
env["GITHUB_ACTOR"] = os.getenv("GITHUB_ACTOR","MegaMan.EXE")
|
||||
env["GITHUB_SHA"] = os.getenv("GITHUB_SHA","")
|
||||
env["GITHUB_RUN_ID"] = os.getenv("GITHUB_RUN_ID","")
|
||||
env["GITHUB_SHA_SHORT"] = env["GITHUB_SHA"]
|
||||
# commit data
|
||||
env["COMMIT_ID"] = os.getenv("TRAVIS_COMMIT",os.getenv("GITHUB_SHA",""))
|
||||
env["COMMIT_COMPARE"] = os.getenv("TRAVIS_COMMIT_RANGE","")
|
||||
# event data
|
||||
env["EVENT_MESSAGE"] = os.getenv("TRAVIS_COMMIT_MESSAGE","")
|
||||
env["EVENT_LOG"] = os.getenv("GITHUB_EVENT_PATH","")
|
||||
env["EVENT_TYPE"] = os.getenv("TRAVIS_EVENT_TYPE",os.getenv("GITHUB_EVENT_NAME",DEFAULT_EVENT))
|
||||
# repo data
|
||||
env["REPO_SLUG"] = os.getenv("TRAVIS_REPO_SLUG",os.getenv("GITHUB_REPOSITORY",DEFAULT_REPO_SLUG))
|
||||
env["REPO_USERNAME"] = ""
|
||||
env["REPO_NAME"] = ""
|
||||
|
||||
# repo slug
|
||||
if '/' in env["REPO_SLUG"]:
|
||||
tmp = env["REPO_SLUG"].split('/')
|
||||
env["REPO_USERNAME"] = tmp[0]
|
||||
env["REPO_NAME"] = tmp[1]
|
||||
|
||||
if not env["GITHUB_SHA"] == "":
|
||||
env["GITHUB_SHA_SHORT"] = env["GITHUB_SHA"][:7]
|
||||
|
||||
# ci data
|
||||
env["BUILD_NUMBER"] = os.getenv("TRAVIS_BUILD_NUMBER",env["GITHUB_RUN_ID"])
|
||||
|
||||
GITHUB_TAG = os.getenv("TRAVIS_TAG",os.getenv("GITHUB_TAG",""))
|
||||
OS_NAME = os.getenv("TRAVIS_OS_NAME",os.getenv("OS_NAME","")).replace("macOS","osx")
|
||||
OS_DIST = os.getenv("TRAVIS_DIST","notset")
|
||||
OS_VERSION = ""
|
||||
|
||||
if '-' in OS_NAME:
|
||||
OS_VERSION = OS_NAME[OS_NAME.find('-')+1:]
|
||||
OS_NAME = OS_NAME[:OS_NAME.find('-')]
|
||||
if OS_NAME == "linux" or OS_NAME == "ubuntu":
|
||||
if OS_VERSION == "latest":
|
||||
OS_VERSION = "bionic"
|
||||
elif OS_VERSION == "16.04":
|
||||
OS_VERSION = "xenial"
|
||||
OS_DIST = OS_VERSION
|
||||
|
||||
if OS_VERSION == "" and not OS_DIST == "" and not OS_DIST == "notset":
|
||||
OS_VERSION = OS_DIST
|
||||
|
||||
# if no tag
|
||||
if GITHUB_TAG == "":
|
||||
# if we haven't appended the build number, do it
|
||||
if env["BUILD_NUMBER"] not in GITHUB_TAG:
|
||||
GITHUB_TAG = APP_VERSION
|
||||
# if the app version didn't have the build number, add it
|
||||
# set to <app_version>.<build_number>
|
||||
if env["BUILD_NUMBER"] not in GITHUB_TAG:
|
||||
GITHUB_TAG += '.' + env["BUILD_NUMBER"]
|
||||
|
||||
env["GITHUB_TAG"] = GITHUB_TAG
|
||||
env["OS_NAME"] = OS_NAME
|
||||
env["OS_DIST"] = OS_DIST
|
||||
env["OS_VERSION"] = OS_VERSION
|
||||
|
||||
return env
|
||||
|
||||
# build filename based on metadata
|
||||
def prepare_filename(BUILD_FILENAME):
|
||||
env = prepare_env()
|
||||
|
||||
DEST_FILENAME = ""
|
||||
|
||||
# build the filename
|
||||
if not BUILD_FILENAME == "":
|
||||
os.chmod(BUILD_FILENAME,0o755)
|
||||
fileparts = os.path.splitext(BUILD_FILENAME)
|
||||
DEST_SLUG = fileparts[0]
|
||||
DEST_EXTENSION = fileparts[1]
|
||||
DEST_SLUG = DEST_SLUG + '-' + env["GITHUB_TAG"] + '-' + env["OS_NAME"]
|
||||
if not env["OS_DIST"] == "" and not env["OS_DIST"] == "notset":
|
||||
DEST_SLUG += '-' + env["OS_DIST"]
|
||||
DEST_FILENAME = DEST_SLUG + DEST_EXTENSION
|
||||
return DEST_FILENAME
|
||||
|
||||
# find a binary file if it's executable
|
||||
# failing that, assume it's over 6MB
|
||||
def find_binary(listdir):
|
||||
FILENAME_CHECKS = [ "Gui", "DungeonRandomizer" ]
|
||||
FILESIZE_CHECK = (6 * 1024 * 1024) # 6MB
|
||||
|
||||
BUILD_FILENAMES = []
|
||||
executable = stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH
|
||||
for filename in os.listdir(listdir):
|
||||
if os.path.isfile(filename):
|
||||
if os.path.splitext(filename)[1] != ".py":
|
||||
st = os.stat(filename)
|
||||
mode = st.st_mode
|
||||
big = st.st_size > FILESIZE_CHECK
|
||||
if (mode & executable) or big:
|
||||
for check in FILENAME_CHECKS:
|
||||
if check in filename:
|
||||
BUILD_FILENAMES.append(filename)
|
||||
return BUILD_FILENAMES
|
||||
|
||||
if __name__ == "__main__":
|
||||
env = prepare_env()
|
||||
print(env)
|
||||
@@ -0,0 +1,42 @@
|
||||
import common
|
||||
import os # for env vars
|
||||
import sys # for path
|
||||
import urllib.request # for downloads
|
||||
from shutil import unpack_archive
|
||||
|
||||
# only do stuff if we don't have a UPX folder
|
||||
|
||||
if not os.path.isdir(os.path.join(".","upx")):
|
||||
# get env vars
|
||||
env = common.prepare_env()
|
||||
# set up download url
|
||||
UPX_VERSION = os.getenv("UPX_VERSION") or "3.96"
|
||||
UPX_SLUG = ""
|
||||
UPX_FILE = ""
|
||||
if "windows" in env["OS_NAME"]:
|
||||
UPX_SLUG = "upx-" + UPX_VERSION + "-win64"
|
||||
UPX_FILE = UPX_SLUG + ".zip"
|
||||
else:
|
||||
UPX_SLUG = "upx-" + UPX_VERSION + "-amd64_linux"
|
||||
UPX_FILE = UPX_SLUG + ".tar.xz"
|
||||
UPX_URL = "https://github.com/upx/upx/releases/download/v" + UPX_VERSION + '/' + UPX_FILE
|
||||
|
||||
if "osx" not in env["OS_NAME"]:
|
||||
|
||||
print("Getting UPX: " + UPX_FILE)
|
||||
|
||||
with open(os.path.join(".",UPX_FILE),"wb") as upx:
|
||||
UPX_REQ = urllib.request.Request(
|
||||
UPX_URL,
|
||||
data=None
|
||||
)
|
||||
UPX_REQ = urllib.request.urlopen(UPX_REQ)
|
||||
UPX_DATA = UPX_REQ.read()
|
||||
upx.write(UPX_DATA)
|
||||
|
||||
unpack_archive(UPX_FILE,os.path.join("."))
|
||||
|
||||
os.rename(os.path.join(".",UPX_SLUG),os.path.join(".","upx"))
|
||||
os.remove(os.path.join(".",UPX_FILE))
|
||||
|
||||
print("UPX should " + ("not " if not os.path.isdir(os.path.join(".","upx")) else "") + "be available.")
|
||||
@@ -0,0 +1,29 @@
|
||||
import subprocess # do stuff at the shell level
|
||||
import os
|
||||
|
||||
def git_clean(clean_ignored=True, clean_user=False):
|
||||
excludes = [
|
||||
".vscode", # vscode IDE files
|
||||
".idea", # idea IDE files
|
||||
"*.json", # keep JSON files for that one time I just nuked all that I was working on, oops
|
||||
"*app*version.*", # keep appversion files
|
||||
"EnemizerCLI" # keep EnemizerCLI files
|
||||
]
|
||||
|
||||
if not clean_user:
|
||||
excludes.append(os.path.join("resources","user*")) # keep user resources
|
||||
|
||||
excludes = ['--exclude={0}'.format(exclude) for exclude in excludes]
|
||||
|
||||
# d: directories, f: files, x: ignored files
|
||||
switches = "df" + ("x" if clean_ignored else "")
|
||||
|
||||
# clean the git slate
|
||||
subprocess.check_call([
|
||||
"git", # run a git command
|
||||
"clean", # clean command
|
||||
"-" + switches,
|
||||
*excludes])
|
||||
|
||||
if __name__ == "__main__":
|
||||
git_clean()
|
||||
@@ -0,0 +1,3 @@
|
||||
from git_clean import git_clean
|
||||
|
||||
git_clean(clean_user=True)
|
||||
@@ -0,0 +1,27 @@
|
||||
import common
|
||||
import os # for env vars
|
||||
import subprocess # do stuff at the shell level
|
||||
|
||||
env = common.prepare_env()
|
||||
|
||||
# get executables
|
||||
# python
|
||||
# linux/windows: python
|
||||
# macosx: python3
|
||||
# pip
|
||||
# linux/macosx: pip3
|
||||
# windows: pip
|
||||
PYTHON_EXECUTABLE = "python3" if "osx" in env["OS_NAME"] else "python"
|
||||
PIP_EXECUTABLE = "pip" if "windows" in env["OS_NAME"] else "pip3"
|
||||
PIP_EXECUTABLE = "pip" if "osx" in env["OS_NAME"] and "actions" in env["CI_SYSTEM"] else PIP_EXECUTABLE
|
||||
|
||||
# upgrade pip
|
||||
subprocess.check_call([PYTHON_EXECUTABLE,"-m","pip","install","--upgrade","pip"])
|
||||
|
||||
# pip version
|
||||
subprocess.check_call([PIP_EXECUTABLE,"--version"])
|
||||
# if pip3, install wheel
|
||||
if PIP_EXECUTABLE == "pip3":
|
||||
subprocess.check_call([PIP_EXECUTABLE,"install","-U","wheel"])
|
||||
# install listed dependencies
|
||||
subprocess.check_call([PIP_EXECUTABLE,"install","-r","./resources/app/meta/manifests/pip_requirements.txt"])
|
||||
@@ -0,0 +1,20 @@
|
||||
import common
|
||||
import os # for env vars
|
||||
from shutil import copy # file manipulation
|
||||
|
||||
env = common.prepare_env()
|
||||
|
||||
# set tag to app_version.txt
|
||||
if not env["GITHUB_TAG"] == "":
|
||||
with open(os.path.join(".","resources","app","meta","manifests","app_version.txt"),"w+") as f:
|
||||
_ = f.read()
|
||||
f.seek(0)
|
||||
f.write(env["GITHUB_TAG"])
|
||||
f.truncate()
|
||||
|
||||
if not os.path.isdir(os.path.join("..","build")):
|
||||
os.mkdir(os.path.join("..","build"))
|
||||
copy(
|
||||
os.path.join(".","resources","app","meta","manifests","app_version.txt"),
|
||||
os.path.join("..","build","app_version.txt")
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
import distutils.dir_util # for copying trees
|
||||
import os # for env vars
|
||||
import stat # for file stats
|
||||
import subprocess # do stuff at the shell level
|
||||
import common
|
||||
from shutil import copy, make_archive, move, rmtree # file manipulation
|
||||
|
||||
env = common.prepare_env()
|
||||
|
||||
# make dir to put the binary in
|
||||
if not os.path.isdir(os.path.join("..","artifact")):
|
||||
os.mkdir(os.path.join("..","artifact"))
|
||||
|
||||
BUILD_FILENAME = ""
|
||||
|
||||
# list executables
|
||||
BUILD_FILENAME = common.find_binary('.')
|
||||
if BUILD_FILENAME == "":
|
||||
BUILD_FILENAME = common.find_binary(os.path.join("..","artifact"))
|
||||
|
||||
if isinstance(BUILD_FILENAME,str):
|
||||
BUILD_FILENAME = list(BUILD_FILENAME)
|
||||
|
||||
BUILD_FILENAMES = BUILD_FILENAME
|
||||
|
||||
for BUILD_FILENAME in BUILD_FILENAMES:
|
||||
DEST_FILENAME = common.prepare_filename(BUILD_FILENAME)
|
||||
|
||||
print("OS Name: " + env["OS_NAME"])
|
||||
print("OS Version: " + env["OS_VERSION"])
|
||||
print("Build Filename: " + BUILD_FILENAME)
|
||||
print("Dest Filename: " + DEST_FILENAME)
|
||||
if not BUILD_FILENAME == "":
|
||||
print("Build Filesize: " + common.file_size(BUILD_FILENAME))
|
||||
|
||||
if not BUILD_FILENAME == "":
|
||||
move(
|
||||
os.path.join(".",BUILD_FILENAME),
|
||||
os.path.join("..","artifact",BUILD_FILENAME)
|
||||
)
|
||||
@@ -0,0 +1,127 @@
|
||||
import distutils.dir_util # for copying trees
|
||||
import os # for env vars
|
||||
import stat # for file stats
|
||||
import subprocess # do stuff at the shell level
|
||||
import common
|
||||
from git_clean import git_clean
|
||||
from shutil import copy, make_archive, move, rmtree # file manipulation
|
||||
|
||||
env = common.prepare_env() # get env vars
|
||||
|
||||
dirs = [
|
||||
os.path.join("..", "artifact"), # temp dir for binary
|
||||
os.path.join("..", "build"), # temp dir for other stuff
|
||||
os.path.join("..", "deploy") # dir for archive
|
||||
]
|
||||
for dirname in dirs:
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
# make dirs for each os
|
||||
for dirname in ["linux","macos","windows"]:
|
||||
if not os.path.isdir(os.path.join("..","deploy",dirname)):
|
||||
os.mkdir(os.path.join("..","deploy",dirname))
|
||||
|
||||
# sanity check permissions for working_dirs.json
|
||||
dirpath = "."
|
||||
for dirname in ["resources","user","meta","manifests"]:
|
||||
dirpath += os.path.join(dirpath,dirname)
|
||||
if os.path.isdir(dirpath):
|
||||
os.chmod(dirpath,0o755)
|
||||
|
||||
# nuke travis file if it exists
|
||||
for travis in [ os.path.join(".", ".travis.yml"), os.path.join(".", ".travis.off") ]:
|
||||
if os.path.isfile(travis):
|
||||
os.remove(travis)
|
||||
|
||||
# nuke test suite if it exists
|
||||
if os.path.isdir(os.path.join(".","tests")):
|
||||
distutils.dir_util.remove_tree(os.path.join(".","tests"))
|
||||
|
||||
BUILD_FILENAME = ""
|
||||
ZIP_FILENAME = ""
|
||||
|
||||
# list executables
|
||||
BUILD_FILENAME = common.find_binary(os.path.join("."))
|
||||
if BUILD_FILENAME == "":
|
||||
BUILD_FILENAME = common.find_binary(os.path.join("..","artifact"))
|
||||
|
||||
if isinstance(BUILD_FILENAME,str):
|
||||
BUILD_FILENAME = list(BUILD_FILENAME)
|
||||
|
||||
BUILD_FILENAMES = BUILD_FILENAME
|
||||
|
||||
print(BUILD_FILENAMES)
|
||||
|
||||
if len(BUILD_FILENAMES) > 0:
|
||||
for BUILD_FILENAME in BUILD_FILENAMES:
|
||||
if not BUILD_FILENAME == "":
|
||||
if not "artifact" in BUILD_FILENAME:
|
||||
# move the binary to temp folder
|
||||
move(
|
||||
os.path.join(".",BUILD_FILENAME),
|
||||
os.path.join("..","artifact",BUILD_FILENAME)
|
||||
)
|
||||
|
||||
# clean the git slate
|
||||
git_clean()
|
||||
|
||||
# mv dirs from source code
|
||||
dirs = [
|
||||
os.path.join(".",".git"),
|
||||
os.path.join(".",".github"),
|
||||
os.path.join(".",".gitignore"),
|
||||
os.path.join(".","html"),
|
||||
os.path.join(".","resources","ci")
|
||||
]
|
||||
for dirname in dirs:
|
||||
if os.path.isdir(dirname):
|
||||
move(
|
||||
dirname,
|
||||
os.path.join("..", "build", dirname)
|
||||
)
|
||||
|
||||
for BUILD_FILENAME in BUILD_FILENAMES:
|
||||
if not "artifact" in BUILD_FILENAME:
|
||||
if os.path.isfile(os.path.join("..","artifact",BUILD_FILENAME)):
|
||||
# move the binary back
|
||||
move(
|
||||
os.path.join("..","artifact",BUILD_FILENAME),
|
||||
os.path.join(".",BUILD_FILENAME)
|
||||
)
|
||||
# Make Linux/Mac binary executable
|
||||
if "linux" in env["OS_NAME"] or "ubuntu" in env["OS_NAME"] or "mac" in env["OS_NAME"] or "osx" in env["OS_NAME"]:
|
||||
os.chmod(os.path.join(".",BUILD_FILENAME),0o755)
|
||||
|
||||
# .zip if windows
|
||||
# .tar.gz otherwise
|
||||
ZIP_FILENAME = os.path.join("..","deploy",env["REPO_NAME"]) if len(BUILD_FILENAMES) > 1 else os.path.join("..","deploy",os.path.splitext(BUILD_FILENAME)[0])
|
||||
if env["OS_NAME"] == "windows":
|
||||
make_archive(ZIP_FILENAME,"zip")
|
||||
ZIP_FILENAME += ".zip"
|
||||
else:
|
||||
make_archive(ZIP_FILENAME,"gztar")
|
||||
ZIP_FILENAME += ".tar.gz"
|
||||
|
||||
# mv dirs back
|
||||
for dir in dirs:
|
||||
if os.path.isdir(os.path.join("..","build",dir)):
|
||||
move(
|
||||
os.path.join("..","build",dir),
|
||||
os.path.join(".",dir)
|
||||
)
|
||||
|
||||
for BUILD_FILENAME in BUILD_FILENAMES:
|
||||
if not BUILD_FILENAME == "":
|
||||
print("Build Filename: " + BUILD_FILENAME)
|
||||
print("Build Filesize: " + common.file_size(BUILD_FILENAME))
|
||||
else:
|
||||
print("No Build to prepare: " + BUILD_FILENAME)
|
||||
|
||||
if not ZIP_FILENAME == "":
|
||||
print("Zip Filename: " + ZIP_FILENAME)
|
||||
print("Zip Filesize: " + common.file_size(ZIP_FILENAME))
|
||||
else:
|
||||
print("No Zip to prepare: " + ZIP_FILENAME)
|
||||
|
||||
print("Git tag: " + env["GITHUB_TAG"])
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "user" folder
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "source" package
|
||||
@@ -0,0 +1,112 @@
|
||||
import json
|
||||
import locale
|
||||
import os
|
||||
|
||||
class BabelFish():
|
||||
def __init__(self,subpath=["resources","app","meta"],lang=None):
|
||||
localization_string = locale.getdefaultlocale()[0] #get set localization
|
||||
self.locale = localization_string[:2] if lang is None else lang #let caller override localization
|
||||
self.langs = ["en"] #start with English
|
||||
if(not self.locale == "en"): #add localization
|
||||
self.langs.append(self.locale)
|
||||
|
||||
self.lang_defns = {} #collect translations
|
||||
self.add_translation_file() #start with default translation file
|
||||
self.add_translation_file(["resources","app","cli"]) #add help translation file
|
||||
self.add_translation_file(["resources","app","gui"]) #add gui label translation file
|
||||
self.add_translation_file(["resources","user","meta"]) #add user translation file
|
||||
|
||||
def add_translation_file(self,subpath=["resources","app","meta"]):
|
||||
if not isinstance(subpath, list):
|
||||
subpath = [subpath]
|
||||
if "lang" not in subpath:
|
||||
subpath.append("lang") #look in lang folder
|
||||
subpath = os.path.join(*subpath) #put in path separators
|
||||
key = subpath.split(os.sep)
|
||||
for check in ["resources","app","user"]:
|
||||
if check in key:
|
||||
key.remove(check)
|
||||
key = os.path.join(*key) #put in path separators
|
||||
for lang in self.langs:
|
||||
if not lang in self.lang_defns:
|
||||
self.lang_defns[lang] = {}
|
||||
langs_filename = os.path.join(subpath,lang + ".json") #get filename of translation file
|
||||
if os.path.isfile(langs_filename): #if we've got a file
|
||||
with open(langs_filename,encoding="utf-8") as f: #open it
|
||||
self.lang_defns[lang][key[:key.rfind(os.sep)].replace(os.sep,'.')] = json.load(f) #save translation definitions
|
||||
else:
|
||||
pass
|
||||
# print(langs_filename + " not found for translation!")
|
||||
|
||||
def translate(self, domain="", key="", subkey="", uselang=None): #three levels of keys
|
||||
# start with nothing
|
||||
display_text = ""
|
||||
|
||||
# exits check for not exit first and then append Exit at end
|
||||
# multiRooms check for not chest name first and then append chest name at end
|
||||
specials = {
|
||||
"exit": False,
|
||||
"multiRoom": False
|
||||
}
|
||||
|
||||
# Domain
|
||||
if os.sep in domain:
|
||||
domain = domain.replace(os.sep,'.')
|
||||
# display_text = domain
|
||||
|
||||
# Operate on Key
|
||||
if key != "":
|
||||
if display_text != "":
|
||||
display_text += '.'
|
||||
# display_text += key
|
||||
# Exits
|
||||
if "exit" in key and "gui" not in domain:
|
||||
key = key.replace("exit","")
|
||||
specials["exit"] = True
|
||||
if "Exit" in key and "gui" not in domain:
|
||||
key = key.replace("Exit","")
|
||||
specials["exit"] = True
|
||||
# Locations
|
||||
tmp = key.split(" - ")
|
||||
if len(tmp) >= 2:
|
||||
specials["multiRoom"] = tmp[len(tmp) - 1]
|
||||
tmp.pop()
|
||||
key = " - ".join(tmp)
|
||||
key = key.strip()
|
||||
|
||||
# Operate on Subkey
|
||||
if subkey != "":
|
||||
if display_text != "":
|
||||
display_text += '.'
|
||||
display_text += subkey
|
||||
# Exits
|
||||
if "exit" in subkey and "gui" not in domain:
|
||||
subkey = subkey.replace("exit","")
|
||||
specials["exit"] = True
|
||||
if "Exit" in subkey and "gui" not in domain:
|
||||
subkey = subkey.replace("Exit","")
|
||||
specials["exit"] = True
|
||||
# Locations
|
||||
tmp = subkey.split(" - ")
|
||||
if len(tmp) >= 2:
|
||||
specials["multiRoom"] = tmp[len(tmp) - 1]
|
||||
tmp.pop()
|
||||
subkey = " - ".join(tmp)
|
||||
subkey = subkey.strip()
|
||||
|
||||
my_lang = self.lang_defns[uselang if uselang is not None else self.locale ] #handle for localization
|
||||
en_lang = self.lang_defns["en"] #handle for English
|
||||
|
||||
if domain in my_lang and key in my_lang[domain] and subkey in my_lang[domain][key] and not my_lang[domain][key][subkey] == "": #get localization first
|
||||
display_text = my_lang[domain][key][subkey]
|
||||
elif domain in en_lang and key in en_lang[domain] and subkey in en_lang[domain][key] and not en_lang[domain][key][subkey] == "": #gracefully degrade to English
|
||||
display_text = en_lang[domain][key][subkey]
|
||||
elif specials["exit"]:
|
||||
specials["exit"] = False
|
||||
|
||||
if specials["exit"]:
|
||||
display_text += " Exit"
|
||||
elif specials["multiRoom"] and specials["multiRoom"] not in display_text:
|
||||
display_text += " - " + specials["multiRoom"]
|
||||
|
||||
return display_text
|
||||
@@ -0,0 +1,3 @@
|
||||
# Need a dummy class
|
||||
class Empty():
|
||||
pass
|
||||
@@ -1,4 +1,4 @@
|
||||
from tkinter import filedialog, messagebox, Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, ALL, NSEW, LEFT, BOTTOM, X, RIGHT, TOP, HORIZONTAL, EW, NS
|
||||
from tkinter import filedialog, messagebox, Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, ALL, LEFT, BOTTOM, X, RIGHT, TOP, EW, NS
|
||||
from glob import glob
|
||||
import json
|
||||
import os
|
||||
@@ -32,10 +32,13 @@ class SpriteSelector(object):
|
||||
webbrowser.open("http://alttpr.com/sprite_preview")
|
||||
|
||||
def open_unofficial_sprite_dir(_evt):
|
||||
if not os.path.isdir(self.unofficial_sprite_dir):
|
||||
os.makedirs(self.unofficial_sprite_dir)
|
||||
open_file(self.unofficial_sprite_dir)
|
||||
|
||||
# Open SpriteSomething directory for Link sprites
|
||||
def open_spritesomething_listing(_evt):
|
||||
webbrowser.open("https://artheau.github.io/SpriteSomething/?mode=zelda3/link")
|
||||
webbrowser.open("https://artheau.github.io/SpriteSomething/resources/app/snes/zelda3/link/sprites.html")
|
||||
|
||||
official_frametitle = Frame(self.window)
|
||||
official_title_text = Label(official_frametitle, text="Official Sprites")
|
||||
@@ -50,12 +53,13 @@ class SpriteSelector(object):
|
||||
unofficial_title_text.pack(side=LEFT)
|
||||
unofficial_title_link.pack(side=LEFT)
|
||||
unofficial_title_link.bind("<Button-1>", open_unofficial_sprite_dir)
|
||||
# Include hyperlink to SpriteSomething directory for Link sprites
|
||||
spritesomething_title_link = Label(unofficial_frametitle, text="(SpriteSomething)", fg="blue", cursor="hand2")
|
||||
spritesomething_title_link.pack(side=LEFT)
|
||||
spritesomething_title_link.bind("<Button-1>", open_spritesomething_listing)
|
||||
|
||||
self.icon_section(official_frametitle, self.official_sprite_dir+'/*', 'Official sprites not found. Click "Update official sprites" to download them.')
|
||||
self.icon_section(unofficial_frametitle, self.unofficial_sprite_dir+'/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.')
|
||||
self.icon_section(official_frametitle, os.path.join(self.official_sprite_dir,"*"), 'Official sprites not found. Click "Update official sprites" to download them.')
|
||||
self.icon_section(unofficial_frametitle, os.path.join(self.unofficial_sprite_dir,"*"), 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.')
|
||||
|
||||
frame = Frame(self.window)
|
||||
frame.pack(side=BOTTOM, fill=X, pady=5)
|
||||
@@ -147,10 +151,10 @@ class SpriteSelector(object):
|
||||
|
||||
try:
|
||||
task.update_status("Determining needed sprites")
|
||||
current_sprites = [os.path.basename(file) for file in glob(self.official_sprite_dir+'/*')]
|
||||
current_sprites = [os.path.basename(file) for file in glob(os.path.join(self.official_sprite_dir,"*"))]
|
||||
official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr]
|
||||
needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites]
|
||||
bundled_sprites = [os.path.basename(file) for file in glob(self.local_official_sprite_dir+'/*')]
|
||||
bundled_sprites = [os.path.basename(file) for file in glob(os.path.join(self.unofficial_sprite_dir,"*"))]
|
||||
# todo: eventually use the above list to avoid downloading any sprites that we already have cached in the bundle.
|
||||
|
||||
official_filenames = [filename for (_, filename) in official_sprites]
|
||||
@@ -227,23 +231,23 @@ class SpriteSelector(object):
|
||||
|
||||
@property
|
||||
def official_sprite_dir(self):
|
||||
if is_bundled():
|
||||
return output_path("sprites/official")
|
||||
# if is_bundled():
|
||||
# return output_path(os.path.join("sprites","official"))
|
||||
return self.local_official_sprite_dir
|
||||
|
||||
@property
|
||||
def local_official_sprite_dir(self):
|
||||
return local_path("data/sprites/official")
|
||||
return local_path(os.path.join("data","sprites","official"))
|
||||
|
||||
@property
|
||||
def unofficial_sprite_dir(self):
|
||||
if is_bundled():
|
||||
return output_path("sprites/unofficial")
|
||||
# if is_bundled():
|
||||
# return output_path(os.path.join("sprites","unofficial"))
|
||||
return self.local_unofficial_sprite_dir
|
||||
|
||||
@property
|
||||
def local_unofficial_sprite_dir(self):
|
||||
return local_path("data/sprites/unofficial")
|
||||
return local_path(os.path.join("data","sprites","unofficial"))
|
||||
|
||||
|
||||
def get_image_for_sprite(sprite):
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "source.classes" package
|
||||
@@ -1,3 +1,4 @@
|
||||
# Ordered list of items in Custom Item Pool page and Starting Inventory page
|
||||
CUSTOMITEMS = [
|
||||
"bow", "progressivebow", "boomerang", "redmerang", "hookshot",
|
||||
"mushroom", "powder", "firerod", "icerod", "bombos",
|
||||
@@ -20,11 +21,13 @@ CUSTOMITEMS = [
|
||||
"rupoorcost"
|
||||
]
|
||||
|
||||
# These can't be in the Starting Inventory page
|
||||
CANTSTARTWITH = [
|
||||
"triforcepiecesgoal", "triforce", "rupoor",
|
||||
"rupoorcost"
|
||||
]
|
||||
|
||||
# In the same order as CUSTOMITEMS, these are Pretty Labels for each option
|
||||
CUSTOMITEMLABELS = [
|
||||
"Bow", "Progressive Bow", "Blue Boomerang", "Red Boomerang", "Hookshot",
|
||||
"Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos",
|
||||
@@ -47,6 +50,8 @@ CUSTOMITEMLABELS = [
|
||||
"Rupoor Cost"
|
||||
]
|
||||
|
||||
# Stuff on each page to save, according to internal names as defined by the widgets definitions
|
||||
# and how it eventually translates to YAML/JSON weight files
|
||||
SETTINGSTOPROCESS = {
|
||||
"randomizer": {
|
||||
"item": {
|
||||
@@ -85,9 +90,6 @@ SETTINGSTOPROCESS = {
|
||||
"experimental": "experimental",
|
||||
"dungeon_counters": "dungeon_counters"
|
||||
},
|
||||
"multiworld": {
|
||||
"names": "names"
|
||||
},
|
||||
"gameoptions": {
|
||||
"hints": "hints",
|
||||
"nobgm": "disablemusic",
|
||||
@@ -99,11 +101,19 @@ SETTINGSTOPROCESS = {
|
||||
"uwpalettes": "uw_palettes"
|
||||
},
|
||||
"generation": {
|
||||
"spoiler": "create_spoiler",
|
||||
"suppressrom": "suppress_rom",
|
||||
"createspoiler": "create_spoiler",
|
||||
"createrom": "create_rom",
|
||||
"calcplaythrough": "calc_playthrough",
|
||||
"usestartinventory": "usestartinventory",
|
||||
"usecustompool": "custom",
|
||||
"saveonexit": "saveonexit"
|
||||
}
|
||||
},
|
||||
"bottom": {
|
||||
"content": {
|
||||
"names": "names",
|
||||
"seed": "seed",
|
||||
"generationcount": "count"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import platform, sys, os, subprocess
|
||||
import pkg_resources
|
||||
from datetime import datetime
|
||||
|
||||
def diagpad(str):
|
||||
return str.ljust(len("ALttP Door Randomizer Version") + 5,'.')
|
||||
|
||||
def output(APP_VERSION):
|
||||
lines = [
|
||||
"ALttP Door Randomizer Diagnostics",
|
||||
"=================================",
|
||||
diagpad("UTC Time") + str(datetime.utcnow())[:19],
|
||||
diagpad("ALttP Door Randomizer Version") + APP_VERSION,
|
||||
diagpad("Python Version") + platform.python_version()
|
||||
]
|
||||
lines.append(diagpad("OS Version") + "%s %s" % (platform.system(), platform.release()))
|
||||
if hasattr(sys, "executable"):
|
||||
lines.append(diagpad("Executable") + sys.executable)
|
||||
lines.append(diagpad("Build Date") + platform.python_build()[1])
|
||||
lines.append(diagpad("Compiler") + platform.python_compiler())
|
||||
if hasattr(sys, "api_version"):
|
||||
lines.append(diagpad("Python API") + str(sys.api_version))
|
||||
if hasattr(os, "sep"):
|
||||
lines.append(diagpad("Filepath Separator") + os.sep)
|
||||
if hasattr(os, "pathsep"):
|
||||
lines.append(diagpad("Path Env Separator") + os.pathsep)
|
||||
lines.append("")
|
||||
lines.append("Packages")
|
||||
lines.append("--------")
|
||||
'''
|
||||
#this breaks when run from the .exe
|
||||
reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
|
||||
installed_packages = [r.decode() for r in reqs.split()]
|
||||
for pkg in installed_packages:
|
||||
pkg = pkg.split("==")
|
||||
lines.append(diagpad(pkg[0]) + pkg[1])
|
||||
'''
|
||||
installed_packages = [str(d) for d in pkg_resources.working_set] #this doesn't work from the .exe either, but it doesn't crash the program
|
||||
installed_packages.sort()
|
||||
for pkg in installed_packages:
|
||||
pkg = pkg.split(' ')
|
||||
lines.append(diagpad(pkg[0]) + pkg[1])
|
||||
|
||||
return lines
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise AssertionError(f"Called main() on utility library {__file__}")
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "source.gui" package
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "source.gui.about" package
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "source.gui.adjust" package
|
||||
@@ -1,8 +1,8 @@
|
||||
from tkinter import ttk, filedialog, messagebox, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT, X, BOTTOM
|
||||
from tkinter import ttk, filedialog, messagebox, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT, X, BOTTOM
|
||||
from AdjusterMain import adjust
|
||||
from argparse import Namespace
|
||||
from classes.SpriteSelector import SpriteSelector
|
||||
import gui.widgets as widgets
|
||||
from source.classes.SpriteSelector import SpriteSelector
|
||||
import source.gui.widgets as widgets
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -19,6 +19,7 @@ def adjust_page(top, parent, settings):
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
# Adjust option frames
|
||||
self.frames["selectOptionsFrame"] = Frame(self)
|
||||
self.frames["leftAdjustFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
self.frames["rightAdjustFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
@@ -28,6 +29,8 @@ def adjust_page(top, parent, settings):
|
||||
self.frames["rightAdjustFrame"].pack(side=RIGHT)
|
||||
self.frames["bottomAdjustFrame"].pack(fill=X)
|
||||
|
||||
# Load Adjust option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
with open(os.path.join("resources","app","gui","adjust","overview","widgets.json")) as widgetDefns:
|
||||
myDict = json.load(widgetDefns)
|
||||
for framename,theseWidgets in myDict.items():
|
||||
@@ -40,6 +43,7 @@ def adjust_page(top, parent, settings):
|
||||
self.widgets[key].pack(packAttrs)
|
||||
|
||||
# Sprite Selection
|
||||
# This one's more-complicated, build it and stuff it
|
||||
self.spriteNameVar2 = StringVar()
|
||||
spriteDialogFrame2 = Frame(self.frames["leftAdjustFrame"])
|
||||
baseSpriteLabel2 = Label(spriteDialogFrame2, text='Sprite:')
|
||||
@@ -65,6 +69,8 @@ def adjust_page(top, parent, settings):
|
||||
spriteSelectButton2.pack(side=LEFT)
|
||||
spriteDialogFrame2.pack(anchor=E)
|
||||
|
||||
# Path to game file to Adjust
|
||||
# This one's more-complicated, build it and stuff it
|
||||
adjustRomFrame = Frame(self.frames["bottomAdjustFrame"])
|
||||
adjustRomLabel = Label(adjustRomFrame, text='Rom to adjust: ')
|
||||
self.romVar2 = StringVar(value=settings["rom"])
|
||||
@@ -82,6 +88,7 @@ def adjust_page(top, parent, settings):
|
||||
romSelectButton2.pack(side=LEFT)
|
||||
adjustRomFrame.pack(fill=X)
|
||||
|
||||
# These are the options to Adjust
|
||||
def adjustRom():
|
||||
options = {
|
||||
"heartbeep": "heartbeep",
|
||||
@@ -97,7 +104,7 @@ def adjust_page(top, parent, settings):
|
||||
arg = options[option]
|
||||
setattr(guiargs, arg, self.widgets[option].storageVar.get())
|
||||
guiargs.rom = self.romVar2.get()
|
||||
guiargs.baserom = top.pages["randomizer"].pages["generation"].romVar.get()
|
||||
guiargs.baserom = top.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
|
||||
guiargs.sprite = self.sprite
|
||||
try:
|
||||
adjust(args=guiargs)
|
||||
@@ -0,0 +1,275 @@
|
||||
from tkinter import ttk, messagebox, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT, X
|
||||
from argparse import Namespace
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from CLI import parse_cli
|
||||
from Fill import FillError
|
||||
from Main import main, EnemizerError
|
||||
from Utils import local_path, output_path, open_file, update_deprecated_args
|
||||
import source.classes.constants as CONST
|
||||
from source.gui.randomize.multiworld import multiworld_page
|
||||
import source.gui.widgets as widgets
|
||||
from source.classes.Empty import Empty
|
||||
|
||||
|
||||
def bottom_frame(self, parent, args=None):
|
||||
# Bottom Frame
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Bottom Frame options
|
||||
self.widgets = {}
|
||||
|
||||
mw,_ = multiworld_page(self, parent.settings)
|
||||
mw.pack(fill=X, expand=True)
|
||||
self.widgets = mw.widgets
|
||||
|
||||
# Seed input
|
||||
# widget ID
|
||||
widget = "seed"
|
||||
|
||||
# Empty object
|
||||
self.widgets[widget] = Empty()
|
||||
# pieces
|
||||
self.widgets[widget].pieces = {}
|
||||
|
||||
# frame
|
||||
self.widgets[widget].pieces["frame"] = Frame(self)
|
||||
# frame: label
|
||||
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text="Seed #")
|
||||
self.widgets[widget].pieces["frame"].label.pack(side=LEFT)
|
||||
# storagevar
|
||||
savedSeed = parent.settings["seed"]
|
||||
self.widgets[widget].storageVar = StringVar(value=savedSeed)
|
||||
# textbox
|
||||
self.widgets[widget].type = "textbox"
|
||||
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"], width=15, textvariable=self.widgets[widget].storageVar)
|
||||
self.widgets[widget].pieces["textbox"].pack(side=LEFT)
|
||||
|
||||
def saveSeed(caller,_,mode):
|
||||
savedSeed = self.widgets["seed"].storageVar.get()
|
||||
parent.settings["seed"] = int(savedSeed) if savedSeed.isdigit() else None
|
||||
self.widgets[widget].storageVar.trace_add("write",saveSeed)
|
||||
# frame: pack
|
||||
self.widgets[widget].pieces["frame"].pack(side=LEFT)
|
||||
|
||||
## Number of Generation attempts
|
||||
key = "generationcount"
|
||||
self.widgets[key] = widgets.make_widget(
|
||||
self,
|
||||
"spinbox",
|
||||
self,
|
||||
"Count",
|
||||
None,
|
||||
None,
|
||||
{"label": {"side": LEFT}, "spinbox": {"side": RIGHT}}
|
||||
)
|
||||
self.widgets[key].pack(side=LEFT)
|
||||
|
||||
def generateRom():
|
||||
guiargs = create_guiargs(parent)
|
||||
# get default values for missing parameters
|
||||
for k,v in vars(parse_cli(['--multi', str(guiargs.multi)])).items():
|
||||
if k not in vars(guiargs):
|
||||
setattr(guiargs, k, v)
|
||||
elif type(v) is dict: # use same settings for every player
|
||||
setattr(guiargs, k, {player: getattr(guiargs, k) for player in range(1, guiargs.multi + 1)})
|
||||
argsDump = vars(guiargs)
|
||||
hasEnemizer = "enemizercli" in argsDump and os.path.isfile(argsDump["enemizercli"])
|
||||
needEnemizer = False
|
||||
if not hasEnemizer:
|
||||
falsey = [ "none", "default", "vanilla", False, 0 ]
|
||||
for enemizerOption in [ "shufflepots", "shuffleenemies", "enemy_damage", "shufflebosses", "enemy_health" ]:
|
||||
if enemizerOption in argsDump:
|
||||
if isinstance(argsDump[enemizerOption], dict):
|
||||
for playerID,playerSetting in argsDump[enemizerOption].items():
|
||||
if not playerSetting in falsey:
|
||||
needEnemizer = True
|
||||
elif not argsDump[enemizerOption] in falsey:
|
||||
needEnemizer = True
|
||||
seeds = []
|
||||
if not needEnemizer or (needEnemizer and hasEnemizer):
|
||||
try:
|
||||
if guiargs.count is not None and guiargs.seed:
|
||||
seed = guiargs.seed
|
||||
for _ in range(guiargs.count):
|
||||
seeds.append(seed)
|
||||
main(seed=seed, args=guiargs, fish=parent.fish)
|
||||
seed = random.randint(0, 999999999)
|
||||
else:
|
||||
if guiargs.seed:
|
||||
seeds.append(guiargs.seed)
|
||||
else:
|
||||
random.seed(None)
|
||||
guiargs.seed = random.randint(0, 999999999)
|
||||
seeds.append(guiargs.seed)
|
||||
main(seed=guiargs.seed, args=guiargs, fish=parent.fish)
|
||||
except (FillError, EnemizerError, Exception, RuntimeError) as e:
|
||||
logging.exception(e)
|
||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||
else:
|
||||
YES = parent.fish.translate("cli","cli","yes")
|
||||
NO = parent.fish.translate("cli","cli","no")
|
||||
successMsg = ""
|
||||
made = {}
|
||||
for k in [ "rom", "playthrough", "spoiler" ]:
|
||||
made[k] = parent.fish.translate("cli","cli","made." + k)
|
||||
made["enemizer"] = parent.fish.translate("cli","cli","used.enemizer")
|
||||
for k in made:
|
||||
v = made[k]
|
||||
pattern = "([\w]+)(:)([\s]+)(.*)"
|
||||
m = re.search(pattern,made[k])
|
||||
made[k] = m.group(1) + m.group(2) + ' ' + m.group(4)
|
||||
successMsg += (made["rom"] % (YES if (guiargs.create_rom) else NO)) + "\n"
|
||||
successMsg += (made["playthrough"] % (YES if (guiargs.calc_playthrough) else NO)) + "\n"
|
||||
successMsg += (made["spoiler"] % (YES if (not guiargs.jsonout and guiargs.create_spoiler) else NO)) + "\n"
|
||||
successMsg += (made["enemizer"] % (YES if needEnemizer else NO)) + "\n"
|
||||
# FIXME: English
|
||||
successMsg += ("Seed%s: %s" % ('s' if len(seeds) > 1 else "", ','.join(str(x) for x in seeds)))
|
||||
|
||||
messagebox.showinfo(title="Success", message=successMsg)
|
||||
|
||||
## Generate Button
|
||||
# widget ID
|
||||
widget = "go"
|
||||
|
||||
# Empty object
|
||||
self.widgets[widget] = Empty()
|
||||
# pieces
|
||||
self.widgets[widget].pieces = {}
|
||||
|
||||
# button
|
||||
self.widgets[widget].type = "button"
|
||||
self.widgets[widget].pieces["button"] = Button(self, text='Generate Patched Rom', command=generateRom)
|
||||
# button: pack
|
||||
self.widgets[widget].pieces["button"].pack(side=LEFT)
|
||||
|
||||
def open_output():
|
||||
if args and args.outputpath:
|
||||
open_file(output_path(args.outputpath))
|
||||
else:
|
||||
open_file(output_path(parent.settings["outputpath"]))
|
||||
|
||||
## Output Button
|
||||
# widget ID
|
||||
widget = "outputdir"
|
||||
|
||||
# Empty object
|
||||
self.widgets[widget] = Empty()
|
||||
# pieces
|
||||
self.widgets[widget].pieces = {}
|
||||
|
||||
# storagevar
|
||||
self.widgets[widget].storageVar = StringVar(value=parent.settings["outputpath"])
|
||||
|
||||
# button
|
||||
self.widgets[widget].type = "button"
|
||||
self.widgets[widget].pieces["button"] = Button(self, text='Open Output Directory', command=open_output)
|
||||
# button: pack
|
||||
self.widgets[widget].pieces["button"].pack(side=RIGHT)
|
||||
|
||||
## Documentation Button
|
||||
# widget ID
|
||||
widget = "docs"
|
||||
|
||||
# Empty object
|
||||
self.widgets[widget] = Empty()
|
||||
# pieces
|
||||
self.widgets[widget].pieces = {}
|
||||
# button
|
||||
self.widgets[widget].type = "button"
|
||||
self.widgets[widget].selectbox = Empty()
|
||||
self.widgets[widget].selectbox.storageVar = Empty()
|
||||
if os.path.exists(local_path('README.html')):
|
||||
def open_readme():
|
||||
open_file(local_path('README.html'))
|
||||
self.widgets[widget].pieces["button"] = Button(self, text='Open Documentation', command=open_readme)
|
||||
# button: pack
|
||||
self.widgets[widget].pieces["button"].pack(side=RIGHT)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def create_guiargs(parent):
|
||||
guiargs = Namespace()
|
||||
|
||||
# set up settings to gather
|
||||
# Page::Subpage::GUI-id::param-id
|
||||
options = CONST.SETTINGSTOPROCESS
|
||||
|
||||
# Cycle through each page
|
||||
for mainpage in options:
|
||||
# Cycle through each subpage (in case of Item Randomizer)
|
||||
for subpage in options[mainpage]:
|
||||
# Cycle through each widget
|
||||
for widget in options[mainpage][subpage]:
|
||||
# Get the value and set it
|
||||
arg = options[mainpage][subpage][widget]
|
||||
setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get())
|
||||
|
||||
# Get EnemizerCLI setting
|
||||
guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].widgets["enemizercli"].storageVar.get()
|
||||
|
||||
# Get Multiworld Worlds count
|
||||
guiargs.multi = int(parent.pages["bottom"].pages["content"].widgets["worlds"].storageVar.get())
|
||||
|
||||
# Get baserom path
|
||||
guiargs.rom = parent.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
|
||||
|
||||
# Get if we're using the Custom Item Pool
|
||||
guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get())
|
||||
|
||||
# Get Seed ID
|
||||
guiargs.seed = None
|
||||
if parent.pages["bottom"].pages["content"].widgets["seed"].storageVar.get():
|
||||
guiargs.seed = parent.pages["bottom"].pages["content"].widgets["seed"].storageVar.get()
|
||||
|
||||
# Get number of generations to run
|
||||
guiargs.count = 1
|
||||
if parent.pages["bottom"].pages["content"].widgets["generationcount"].storageVar.get():
|
||||
guiargs.count = int(parent.pages["bottom"].pages["content"].widgets["generationcount"].storageVar.get())
|
||||
|
||||
# Get Adjust settings
|
||||
adjustargs = {
|
||||
"nobgm": "disablemusic",
|
||||
"quickswap": "quickswap",
|
||||
"heartcolor": "heartcolor",
|
||||
"heartbeep": "heartbeep",
|
||||
"menuspeed": "fastmenu",
|
||||
"owpalettes": "ow_palettes",
|
||||
"uwpalettes": "uw_palettes"
|
||||
}
|
||||
for adjustarg in adjustargs:
|
||||
internal = adjustargs[adjustarg]
|
||||
setattr(guiargs,"adjust." + internal, parent.pages["adjust"].content.widgets[adjustarg].storageVar.get())
|
||||
|
||||
# Get Custom Items and Starting Inventory Items
|
||||
customitems = CONST.CUSTOMITEMS
|
||||
guiargs.startinventory = []
|
||||
guiargs.customitemarray = {}
|
||||
guiargs.startinventoryarray = {}
|
||||
for customitem in customitems:
|
||||
if customitem not in CONST.CANTSTARTWITH:
|
||||
# Starting Inventory is a CSV
|
||||
amount = int(parent.pages["startinventory"].content.startingWidgets[customitem].storageVar.get())
|
||||
guiargs.startinventoryarray[customitem] = amount
|
||||
for _ in range(0, amount):
|
||||
label = CONST.CUSTOMITEMLABELS[customitems.index(customitem)]
|
||||
guiargs.startinventory.append(label)
|
||||
# Custom Item Pool is a dict of ints
|
||||
guiargs.customitemarray[customitem] = int(parent.pages["custom"].content.customWidgets[customitem].storageVar.get())
|
||||
|
||||
# Starting Inventory is a CSV
|
||||
guiargs.startinventory = ','.join(guiargs.startinventory)
|
||||
|
||||
# Get Sprite Selection (set or random)
|
||||
guiargs.sprite = parent.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"]
|
||||
guiargs.randomSprite = parent.randomSprite.get()
|
||||
|
||||
# Get output path
|
||||
guiargs.outputpath = parent.outputPath.get()
|
||||
|
||||
guiargs = update_deprecated_args(guiargs)
|
||||
|
||||
return guiargs
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "source.gui.custom" package
|
||||
@@ -1,25 +1,27 @@
|
||||
from tkinter import ttk, Frame, N, LEFT, VERTICAL, Y
|
||||
import gui.widgets as widgets
|
||||
from tkinter import ttk, Frame, N, E, W, LEFT, X, VERTICAL, Y
|
||||
import source.gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
import classes.constants as CONST
|
||||
|
||||
import source.classes.constants as CONST
|
||||
|
||||
def custom_page(top,parent):
|
||||
# Custom Item Pool
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Create uniform list columns
|
||||
def create_list_frame(parent, framename):
|
||||
parent.frames[framename] = Frame(parent)
|
||||
parent.frames[framename].pack(side=LEFT, padx=(0,0), anchor=N)
|
||||
parent.frames[framename].thisRow = 0
|
||||
parent.frames[framename].thisCol = 0
|
||||
|
||||
# Create a vertical rule to help with splitting columns visually
|
||||
def create_vertical_rule(num=1):
|
||||
for i in range(0,num):
|
||||
for _ in range(0,num):
|
||||
ttk.Separator(self, orient=VERTICAL).pack(side=LEFT, anchor=N, fill=Y)
|
||||
|
||||
# This was in here, I have no idea what it was but I left it just in case: MikeT
|
||||
def validation(P):
|
||||
if str.isdigit(P) or P == "":
|
||||
return True
|
||||
@@ -32,6 +34,7 @@ def custom_page(top, parent):
|
||||
|
||||
# Custom Item Pool option sections
|
||||
self.frames = {}
|
||||
# Create 5 columns with 2 vertical rules in between each
|
||||
create_list_frame(self, "itemList1")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self, "itemList2")
|
||||
@@ -42,6 +45,8 @@ def custom_page(top, parent):
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self, "itemList5")
|
||||
|
||||
# Load Custom option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
with open(os.path.join("resources", "app", "gui", "custom", "overview", "widgets.json")) as widgetDefns:
|
||||
myDict = json.load(widgetDefns)
|
||||
for framename,theseWidgets in myDict.items():
|
||||
@@ -49,6 +54,7 @@ def custom_page(top, parent):
|
||||
for key in dictWidgets:
|
||||
self.customWidgets[key] = dictWidgets[key]
|
||||
|
||||
# Load Custom Item Pool settings from settings file
|
||||
for key in CONST.CUSTOMITEMS:
|
||||
self.customWidgets[key].storageVar.set(top.settings["customitemarray"][key])
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
from source.classes.SpriteSelector import SpriteSelector as spriteSelector
|
||||
from source.gui.randomize.gameoptions import set_sprite
|
||||
from Rom import Sprite, get_sprite_from_name
|
||||
from Utils import update_deprecated_args
|
||||
import source.classes.constants as CONST
|
||||
from source.classes.BabelFish import BabelFish
|
||||
from source.classes.Empty import Empty
|
||||
|
||||
# Load args/settings for most tabs
|
||||
def loadcliargs(gui, args, settings=None):
|
||||
if args is not None:
|
||||
args = update_deprecated_args(args)
|
||||
args = vars(args)
|
||||
fish = BabelFish()
|
||||
for k, v in args.items():
|
||||
if isinstance(v,dict) and 1 in v:
|
||||
setattr(args, k, v[1]) # only get values for player 1 for now
|
||||
# load values from commandline args
|
||||
|
||||
# set up options to get
|
||||
# Page::Subpage::GUI-id::param-id
|
||||
options = CONST.SETTINGSTOPROCESS
|
||||
|
||||
# Cycle through each page
|
||||
for mainpage in options:
|
||||
# Cycle through each subpage (in case of Item Randomizer)
|
||||
for subpage in options[mainpage]:
|
||||
# Cycle through each widget
|
||||
for widget in options[mainpage][subpage]:
|
||||
if widget in gui.pages[mainpage].pages[subpage].widgets:
|
||||
thisType = ""
|
||||
# Get the value and set it
|
||||
arg = options[mainpage][subpage][widget]
|
||||
if args[arg] == None:
|
||||
args[arg] = ""
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
if hasattr(gui.pages[mainpage].pages[subpage].widgets[widget],"type"):
|
||||
thisType = gui.pages[mainpage].pages[subpage].widgets[widget].type
|
||||
if thisType == "checkbox":
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].checkbox.configure(text=label)
|
||||
elif thisType == "selectbox":
|
||||
theseOptions = gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||
i = 0
|
||||
for value in theseOptions["values"]:
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + '.' + str(value))
|
||||
i += 1
|
||||
for i in range(0, len(theseOptions["values"])):
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i])
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options = theseOptions
|
||||
elif thisType == "spinbox":
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
|
||||
# If we're on the Game Options page and it's not about Hints
|
||||
if subpage == "gameoptions" and not widget == "hints":
|
||||
# Check if we've got settings
|
||||
# Check if we've got the widget in Adjust settings
|
||||
hasSettings = settings is not None
|
||||
hasWidget = ("adjust." + widget) in settings if hasSettings else None
|
||||
label = fish.translate("gui","gui","adjust." + widget)
|
||||
if ("adjust." + widget) in label:
|
||||
label = fish.translate("gui","gui","randomizer.gameoptions." + widget)
|
||||
if hasattr(gui.pages["adjust"].content.widgets[widget],"type"):
|
||||
type = gui.pages["adjust"].content.widgets[widget].type
|
||||
if type == "checkbox":
|
||||
gui.pages["adjust"].content.widgets[widget].checkbox.configure(text=label)
|
||||
elif type == "selectbox":
|
||||
gui.pages["adjust"].content.widgets[widget].label.configure(text=label)
|
||||
if hasWidget is None:
|
||||
# If we've got a Game Options val and we don't have an Adjust val, use the Game Options val
|
||||
gui.pages["adjust"].content.widgets[widget].storageVar.set(args[arg])
|
||||
|
||||
# Get EnemizerCLI setting
|
||||
mainpage = "randomizer"
|
||||
subpage = "enemizer"
|
||||
widget = "enemizercli"
|
||||
setting = "enemizercli"
|
||||
# set storagevar
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[setting])
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||
# set get from web label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + ".online")
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["online"].label.configure(text=label)
|
||||
|
||||
# Get baserom path
|
||||
mainpage = "randomizer"
|
||||
subpage = "generation"
|
||||
widget = "rom"
|
||||
setting = "rom"
|
||||
# set storagevar
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[setting])
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||
# set button label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + ".button")
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||
|
||||
# Get Multiworld Worlds count
|
||||
mainpage = "bottom"
|
||||
subpage = "content"
|
||||
widget = "worlds"
|
||||
setting = "multi"
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||
if args[setting]:
|
||||
# set storagevar
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(str(args[setting]))
|
||||
|
||||
# Set Multiworld Names
|
||||
mainpage = "bottom"
|
||||
subpage = "content"
|
||||
widget = "names"
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||
|
||||
# Get Seed ID
|
||||
mainpage = "bottom"
|
||||
subpage = "content"
|
||||
widget = "seed"
|
||||
setting = "seed"
|
||||
if args[setting]:
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[setting])
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||
|
||||
# Get number of generations to run
|
||||
mainpage = "bottom"
|
||||
subpage = "content"
|
||||
widget = "generationcount"
|
||||
setting = "count"
|
||||
if args[setting]:
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(str(args[setting]))
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||
|
||||
# Set Generate button
|
||||
mainpage = "bottom"
|
||||
subpage = "content"
|
||||
widget = "go"
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||
|
||||
# Set Output Directory button
|
||||
mainpage = "bottom"
|
||||
subpage = "content"
|
||||
widget = "outputdir"
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||
# Get output path
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args["outputpath"])
|
||||
|
||||
# Set Documentation button
|
||||
mainpage = "bottom"
|
||||
subpage = "content"
|
||||
widget = "docs"
|
||||
if widget in gui.pages[mainpage].pages[subpage].widgets:
|
||||
if "button" in gui.pages[mainpage].pages[subpage].widgets[widget].pieces:
|
||||
# set textbox/frame label
|
||||
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||
|
||||
# Figure out Sprite Selection
|
||||
def sprite_setter(spriteObject):
|
||||
gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteObject"] = spriteObject
|
||||
if args["sprite"] is not None:
|
||||
sprite_obj = args.sprite if isinstance(args["sprite"], Sprite) else get_sprite_from_name(args["sprite"])
|
||||
set_sprite(sprite_obj, False, spriteSetter=sprite_setter,
|
||||
spriteNameVar=gui.pages["randomizer"].pages["gameoptions"].widgets["sprite"]["spriteNameVar"],
|
||||
randomSpriteVar=gui.randomSprite)
|
||||
|
||||
def sprite_setter_adj(spriteObject):
|
||||
gui.pages["adjust"].content.sprite = spriteObject
|
||||
if args["sprite"] is not None:
|
||||
sprite_obj = args.sprite if isinstance(args["sprite"], Sprite) else get_sprite_from_name(args["sprite"])
|
||||
set_sprite(sprite_obj, False, spriteSetter=sprite_setter_adj,
|
||||
spriteNameVar=gui.pages["adjust"].content.spriteNameVar2,
|
||||
randomSpriteVar=gui.randomSprite)
|
||||
|
||||
# Load args/settings for Adjust tab
|
||||
def loadadjustargs(gui, settings):
|
||||
options = {
|
||||
"adjust": {
|
||||
"content": {
|
||||
"nobgm": "adjust.nobgm",
|
||||
"quickswap": "adjust.quickswap",
|
||||
"heartcolor": "adjust.heartcolor",
|
||||
"heartbeep": "adjust.heartbeep",
|
||||
"menuspeed": "adjust.menuspeed",
|
||||
"owpalettes": "adjust.owpalettes",
|
||||
"uwpalettes": "adjust.uwpalettes"
|
||||
}
|
||||
}
|
||||
}
|
||||
for mainpage in options:
|
||||
for subpage in options[mainpage]:
|
||||
for widget in options[mainpage][subpage]:
|
||||
key = options[mainpage][subpage][widget]
|
||||
if key in settings:
|
||||
gui.pages[mainpage].content.widgets[widget].storageVar.set(settings[key])
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "source.gui.randomize" package
|
||||
@@ -1,5 +1,5 @@
|
||||
from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT
|
||||
import gui.widgets as widgets
|
||||
from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT
|
||||
import source.gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -19,17 +19,23 @@ def dungeon_page(parent):
|
||||
mscbLabel = Label(self.frames["keysanity"], text="Shuffle: ")
|
||||
mscbLabel.pack(side=LEFT)
|
||||
|
||||
# Load Dungeon Shuffle option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
# This first set goes in the Keysanity frame
|
||||
with open(os.path.join("resources","app","gui","randomize","dungeon","keysanity.json")) as keysanityItems:
|
||||
myDict = json.load(keysanityItems)
|
||||
myDict = myDict["keysanity"]
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["keysanity"])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
self.widgets[key].pack(side=LEFT)
|
||||
|
||||
# These get split left & right
|
||||
self.frames["widgets"] = Frame(self)
|
||||
self.frames["widgets"].pack(anchor=W)
|
||||
with open(os.path.join("resources","app","gui","randomize","dungeon","widgets.json")) as dungeonWidgets:
|
||||
myDict = json.load(dungeonWidgets)
|
||||
myDict = myDict["widgets"]
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["widgets"])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
@@ -0,0 +1,89 @@
|
||||
from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, N, E, W, LEFT, RIGHT, BOTTOM, X
|
||||
import source.gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
import webbrowser
|
||||
from source.classes.Empty import Empty
|
||||
|
||||
def enemizer_page(parent,settings):
|
||||
def open_enemizer_download(_evt):
|
||||
webbrowser.open("https://github.com/Bonta0/Enemizer/releases")
|
||||
|
||||
# Enemizer
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Enemizer options
|
||||
self.widgets = {}
|
||||
|
||||
# Enemizer option sections
|
||||
self.frames = {}
|
||||
|
||||
# Enemizer option frames
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
self.frames["selectOptionsFrame"] = Frame(self)
|
||||
self.frames["leftEnemizerFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
self.frames["rightEnemizerFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
self.frames["bottomEnemizerFrame"] = Frame(self)
|
||||
self.frames["selectOptionsFrame"].pack(fill=X)
|
||||
self.frames["leftEnemizerFrame"].pack(side=LEFT)
|
||||
self.frames["rightEnemizerFrame"].pack(side=RIGHT)
|
||||
self.frames["bottomEnemizerFrame"].pack(fill=X)
|
||||
|
||||
# Load Enemizer option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
# These get split left & right
|
||||
with open(os.path.join("resources","app","gui","randomize","enemizer","widgets.json")) as widgetDefns:
|
||||
myDict = json.load(widgetDefns)
|
||||
for framename,theseWidgets in myDict.items():
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
packAttrs = {"anchor":E}
|
||||
if self.widgets[key].type == "checkbox":
|
||||
packAttrs["anchor"] = W
|
||||
self.widgets[key].pack(packAttrs)
|
||||
|
||||
## Enemizer CLI Path
|
||||
# This one's more-complicated, build it and stuff it
|
||||
# widget ID
|
||||
widget = "enemizercli"
|
||||
|
||||
# Empty object
|
||||
self.widgets[widget] = Empty()
|
||||
# pieces
|
||||
self.widgets[widget].pieces = {}
|
||||
|
||||
# frame
|
||||
self.widgets[widget].pieces["frame"] = Frame(self.frames["bottomEnemizerFrame"])
|
||||
# frame: label
|
||||
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text="EnemizerCLI path: ")
|
||||
self.widgets[widget].pieces["frame"].label.pack(side=LEFT)
|
||||
|
||||
# get app online
|
||||
self.widgets[widget].pieces["online"] = Empty()
|
||||
# get app online: label
|
||||
self.widgets[widget].pieces["online"].label = Label(self.widgets[widget].pieces["frame"], text="(get online)", fg="blue", cursor="hand2")
|
||||
self.widgets[widget].pieces["online"].label.pack(side=LEFT)
|
||||
# get app online: open browser
|
||||
self.widgets[widget].pieces["online"].label.bind("<Button-1>", open_enemizer_download)
|
||||
# storage var
|
||||
self.widgets[widget].storageVar = StringVar(value=settings["enemizercli"])
|
||||
# textbox
|
||||
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"], textvariable=self.widgets[widget].storageVar)
|
||||
self.widgets[widget].pieces["textbox"].pack(side=LEFT, fill=X, expand=True)
|
||||
|
||||
def EnemizerSelectPath():
|
||||
path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")], initialdir=os.path.join("."))
|
||||
if path:
|
||||
self.widgets[widget].storageVar.set(path)
|
||||
settings["enemizercli"] = path
|
||||
# dialog button
|
||||
self.widgets[widget].pieces["opendialog"] = Button(self.widgets[widget].pieces["frame"], text='...', command=EnemizerSelectPath)
|
||||
self.widgets[widget].pieces["opendialog"].pack(side=LEFT)
|
||||
|
||||
# frame: pack
|
||||
self.widgets[widget].pieces["frame"].pack(fill=X)
|
||||
|
||||
return self,settings
|
||||
@@ -1,5 +1,5 @@
|
||||
from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT
|
||||
import gui.widgets as widgets
|
||||
from tkinter import ttk, Frame, E, W, LEFT, RIGHT
|
||||
import source.gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -15,6 +15,11 @@ def entrando_page(parent):
|
||||
self.frames["widgets"] = Frame(self)
|
||||
self.frames["widgets"].pack(anchor=W)
|
||||
|
||||
# Load Entrance Randomizer option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
# Checkboxes go West
|
||||
# Everything else goes East
|
||||
# They also get split left & right
|
||||
with open(os.path.join("resources","app","gui","randomize","entrando","widgets.json")) as widgetDefns:
|
||||
myDict = json.load(widgetDefns)
|
||||
for framename,theseWidgets in myDict.items():
|
||||
@@ -1,7 +1,7 @@
|
||||
from tkinter import ttk, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT
|
||||
from tkinter import ttk, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT
|
||||
from functools import partial
|
||||
import classes.SpriteSelector as spriteSelector
|
||||
import gui.widgets as widgets
|
||||
import source.classes.SpriteSelector as spriteSelector
|
||||
import source.gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -17,11 +17,17 @@ def gameoptions_page(top, parent):
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
# Game Options frames
|
||||
self.frames["leftRomOptionsFrame"] = Frame(self)
|
||||
self.frames["rightRomOptionsFrame"] = Frame(self)
|
||||
self.frames["leftRomOptionsFrame"].pack(side=LEFT)
|
||||
self.frames["rightRomOptionsFrame"].pack(side=RIGHT)
|
||||
|
||||
# Load Game Options widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
# Checkboxes go West
|
||||
# Everything else goes East
|
||||
# They also get split left & right
|
||||
with open(os.path.join("resources","app","gui","randomize","gameoptions","widgets.json")) as widgetDefns:
|
||||
myDict = json.load(widgetDefns)
|
||||
for framename,theseWidgets in myDict.items():
|
||||
@@ -34,6 +40,7 @@ def gameoptions_page(top, parent):
|
||||
self.widgets[key].pack(packAttrs)
|
||||
|
||||
## Sprite selection
|
||||
# This one's more-complicated, build it and stuff it
|
||||
spriteDialogFrame = Frame(self.frames["leftRomOptionsFrame"])
|
||||
baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:')
|
||||
|
||||
@@ -75,4 +82,3 @@ def set_sprite(sprite_param, random_sprite=False, spriteSetter=None, spriteNameV
|
||||
spriteNameVar.set(sprite_param.name)
|
||||
if randomSpriteVar:
|
||||
randomSpriteVar.set(random_sprite)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, E, W, LEFT, X
|
||||
import source.gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
from source.classes.Empty import Empty
|
||||
|
||||
def generation_page(parent,settings):
|
||||
# Generation Setup
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Generation Setup options
|
||||
self.widgets = {}
|
||||
|
||||
# Generation Setup option sections
|
||||
self.frames = {}
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
# Load Generation Setup option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
with open(os.path.join("resources","app","gui","randomize","generation","checkboxes.json")) as checkboxes:
|
||||
myDict = json.load(checkboxes)
|
||||
myDict = myDict["checkboxes"]
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["checkboxes"])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
self.widgets[key].pack(anchor=W)
|
||||
|
||||
self.frames["widgets"] = Frame(self)
|
||||
self.frames["widgets"].pack(anchor=W)
|
||||
# Load Generation Setup option widgets as defined by JSON file
|
||||
# Defns include frame name, widget type, widget options, widget placement attributes
|
||||
with open(os.path.join("resources","app","gui","randomize","generation","widgets.json")) as items:
|
||||
myDict = json.load(items)
|
||||
myDict = myDict["widgets"]
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, myDict, self.frames["widgets"])
|
||||
for key in dictWidgets:
|
||||
self.widgets[key] = dictWidgets[key]
|
||||
self.widgets[key].pack(anchor=W)
|
||||
|
||||
self.frames["baserom"] = Frame(self)
|
||||
self.frames["baserom"].pack(anchor=W, fill=X)
|
||||
## Locate base ROM
|
||||
# This one's more-complicated, build it and stuff it
|
||||
# widget ID
|
||||
widget = "rom"
|
||||
|
||||
# Empty object
|
||||
self.widgets[widget] = Empty()
|
||||
# pieces
|
||||
self.widgets[widget].pieces = {}
|
||||
|
||||
# frame
|
||||
self.widgets[widget].pieces["frame"] = Frame(self.frames["baserom"])
|
||||
# frame: label
|
||||
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text='Base Rom: ')
|
||||
# storage var
|
||||
self.widgets[widget].storageVar = StringVar()
|
||||
# textbox
|
||||
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"], textvariable=self.widgets[widget].storageVar)
|
||||
self.widgets[widget].storageVar.set(settings["rom"])
|
||||
|
||||
# FIXME: Translate these
|
||||
def RomSelect():
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")], initialdir=os.path.join("."))
|
||||
self.widgets[widget].storageVar.set(rom)
|
||||
# dialog button
|
||||
self.widgets[widget].pieces["button"] = Button(self.widgets[widget].pieces["frame"], text='Select Rom', command=RomSelect)
|
||||
|
||||
# frame label: pack
|
||||
self.widgets[widget].pieces["frame"].label.pack(side=LEFT)
|
||||
# textbox: pack
|
||||
self.widgets[widget].pieces["textbox"].pack(side=LEFT, fill=X, expand=True)
|
||||
# button: pack
|
||||
self.widgets[widget].pieces["button"].pack(side=LEFT)
|
||||
# frame: pack
|
||||
self.widgets[widget].pieces["frame"].pack(fill=X)
|
||||
|
||||
return self,settings
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user