@@ -16,7 +16,13 @@ README.html
|
||||
EnemizerCLI/
|
||||
.mypy_cache/
|
||||
RaceRom.py
|
||||
upx/
|
||||
weights/
|
||||
|
||||
settings.json
|
||||
working_dirs.json
|
||||
|
||||
*.exe
|
||||
|
||||
venv
|
||||
test
|
||||
|
||||
@@ -21,6 +21,9 @@ def adjust(args):
|
||||
else:
|
||||
raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
|
||||
|
||||
if not hasattr(args,"sprite"):
|
||||
args.sprite = None
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes)
|
||||
|
||||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
||||
|
||||
+26
-6
@@ -2,7 +2,7 @@ import copy
|
||||
from enum import Enum, unique, Flag
|
||||
import logging
|
||||
import json
|
||||
from collections import OrderedDict, deque
|
||||
from collections import OrderedDict, deque, defaultdict
|
||||
|
||||
from EntranceShuffle import door_addresses
|
||||
from _vendor.collections_extended import bag
|
||||
@@ -69,6 +69,8 @@ class World(object):
|
||||
self.dungeon_layouts = {}
|
||||
self.inaccessible_regions = {}
|
||||
self.key_logic = {}
|
||||
self.pool_adjustment = {}
|
||||
self.key_layout = defaultdict(dict)
|
||||
|
||||
for player in range(1, players + 1):
|
||||
def set_player_attr(attr, val):
|
||||
@@ -767,6 +769,16 @@ class CollectionState(object):
|
||||
else:
|
||||
self.prog_items.add(('Bow', item.player))
|
||||
changed = True
|
||||
elif 'Armor' in item.name:
|
||||
if self.has('Red Mail', item.player):
|
||||
pass
|
||||
elif self.has('Blue Mail', item.player):
|
||||
self.prog_items.add(('Red Mail', item.player))
|
||||
changed = True
|
||||
else:
|
||||
self.prog_items.add(('Blue Mail', item.player))
|
||||
changed = True
|
||||
|
||||
elif item.name.startswith('Bottle'):
|
||||
if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit:
|
||||
self.prog_items.add((item.name, item.player))
|
||||
@@ -1515,11 +1527,11 @@ class Spoiler(object):
|
||||
else:
|
||||
self.entrances[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
|
||||
|
||||
def set_door(self, entrance, exit, direction, player):
|
||||
def set_door(self, entrance, exit, direction, player, d_name):
|
||||
if self.world.players == 1:
|
||||
self.doors[(entrance, direction, player)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)])
|
||||
self.doors[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction), ('dname', d_name)])
|
||||
else:
|
||||
self.doors[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
|
||||
self.doors[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction), ('dname', d_name)])
|
||||
|
||||
def set_door_type(self, doorNames, type, player):
|
||||
if self.world.players == 1:
|
||||
@@ -1625,7 +1637,8 @@ class Spoiler(object):
|
||||
'enemy_health': self.world.enemy_health,
|
||||
'enemy_damage': self.world.enemy_damage,
|
||||
'players': self.world.players,
|
||||
'teams': self.world.teams
|
||||
'teams': self.world.teams,
|
||||
'experimental' : self.world.experimental
|
||||
}
|
||||
|
||||
def to_json(self):
|
||||
@@ -1683,9 +1696,16 @@ class Spoiler(object):
|
||||
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
|
||||
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
||||
outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
||||
outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
|
||||
if self.doors:
|
||||
outfile.write('\n\nDoors:\n\n')
|
||||
outfile.write('\n'.join(['%s%s %s %s' % ('Player {0}: '.format(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.doors.values()]))
|
||||
outfile.write('\n'.join(
|
||||
['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '',
|
||||
entry['entrance'],
|
||||
'<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>',
|
||||
entry['exit'],
|
||||
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] == 'crossed' else '') for
|
||||
entry in self.doors.values()]))
|
||||
if self.doorTypes:
|
||||
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()]))
|
||||
|
||||
@@ -183,6 +183,10 @@ def place_bosses(world, player):
|
||||
raise FillError('Could not place boss for location %s' % loc_text)
|
||||
bosses.remove(boss)
|
||||
|
||||
# GT Bosses can move dungeon - find the real dungeon to place them in
|
||||
if level:
|
||||
loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
|
||||
loc_text = loc + ' (' + level + ')'
|
||||
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
|
||||
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
|
||||
elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random
|
||||
@@ -193,5 +197,9 @@ def place_bosses(world, player):
|
||||
except IndexError:
|
||||
raise FillError('Could not place boss for location %s' % loc_text)
|
||||
|
||||
# GT Bosses can move dungeon - find the real dungeon to place them in
|
||||
if level:
|
||||
loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
|
||||
loc_text = loc + ' (' + level + ')'
|
||||
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
|
||||
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
|
||||
|
||||
@@ -0,0 +1,508 @@
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import random
|
||||
import textwrap
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from Main import main
|
||||
from Utils import is_bundled, close_console
|
||||
from Fill import FillError
|
||||
|
||||
import classes.constants as CONST
|
||||
|
||||
|
||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||
|
||||
def _get_help_string(self, action):
|
||||
return textwrap.dedent(action.help)
|
||||
|
||||
def parse_arguments(argv, no_defaults=False):
|
||||
def defval(value):
|
||||
return value if not no_defaults else None
|
||||
|
||||
# get settings
|
||||
settings = get_settings()
|
||||
|
||||
# we need to know how many players we have first
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
|
||||
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'])
|
||||
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')
|
||||
# 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):
|
||||
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
|
||||
|
||||
ret = parser.parse_args(argv)
|
||||
|
||||
if ret.keysanity:
|
||||
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
|
||||
|
||||
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)
|
||||
|
||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||
'remote_items']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
else:
|
||||
getattr(ret, name)[player] = value
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def get_settings():
|
||||
# set default settings
|
||||
settings = {
|
||||
"retro": False,
|
||||
"mode": "open",
|
||||
"logic": "noglitches",
|
||||
"goal": "ganon",
|
||||
"crystals_gt": "7",
|
||||
"crystals_ganon": "7",
|
||||
"swords": "random",
|
||||
"difficulty": "normal",
|
||||
"item_functionality": "normal",
|
||||
"timer": "none",
|
||||
"progressive": "on",
|
||||
"accessibility": "items",
|
||||
"algorithm": "balanced",
|
||||
|
||||
"openpyramid": False,
|
||||
"shuffleganon": False,
|
||||
"shuffle": "vanilla",
|
||||
|
||||
"shufflepots": False,
|
||||
"shuffleenemies": "none",
|
||||
"shufflebosses": "none",
|
||||
"enemy_damage": "default",
|
||||
"enemy_health": "default",
|
||||
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
|
||||
|
||||
"mapshuffle": False,
|
||||
"compassshuffle": False,
|
||||
"keyshuffle": False,
|
||||
"bigkeyshuffle": False,
|
||||
"keysanity": False,
|
||||
"door_shuffle": "basic",
|
||||
"experimental": 0,
|
||||
"dungeon_counters": "off",
|
||||
|
||||
"multi": 1,
|
||||
"names": "",
|
||||
|
||||
"hints": True,
|
||||
"disablemusic": False,
|
||||
"quickswap": False,
|
||||
"heartcolor": "red",
|
||||
"heartbeep": "normal",
|
||||
"sprite": None,
|
||||
"fastmenu": "normal",
|
||||
"ow_palettes": "default",
|
||||
"uw_palettes": "default",
|
||||
|
||||
"create_spoiler": False,
|
||||
"skip_playthrough": False,
|
||||
"suppress_rom": False,
|
||||
"usestartinventory": False,
|
||||
"custom": False,
|
||||
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
|
||||
|
||||
"seed": None,
|
||||
"count": None,
|
||||
"startinventory": "",
|
||||
"beemizer": 0,
|
||||
"remote_items": False,
|
||||
"race": False,
|
||||
"customitemarray": {
|
||||
"bow": 0,
|
||||
"progressivebow": 2,
|
||||
"boomerang": 1,
|
||||
"redmerang": 1,
|
||||
"hookshot": 1,
|
||||
"mushroom": 1,
|
||||
"powder": 1,
|
||||
"firerod": 1,
|
||||
"icerod": 1,
|
||||
"bombos": 1,
|
||||
"ether": 1,
|
||||
"quake": 1,
|
||||
"lamp": 1,
|
||||
"hammer": 1,
|
||||
"shovel": 1,
|
||||
"flute": 1,
|
||||
"bugnet": 1,
|
||||
"book": 1,
|
||||
"bottle": 4,
|
||||
"somaria": 1,
|
||||
"byrna": 1,
|
||||
"cape": 1,
|
||||
"mirror": 1,
|
||||
"boots": 1,
|
||||
"powerglove": 0,
|
||||
"titansmitt": 0,
|
||||
"progressiveglove": 2,
|
||||
"flippers": 1,
|
||||
"pearl": 1,
|
||||
"heartpiece": 24,
|
||||
"heartcontainer": 10,
|
||||
"sancheart": 1,
|
||||
"sword1": 0,
|
||||
"sword2": 0,
|
||||
"sword3": 0,
|
||||
"sword4": 0,
|
||||
"progressivesword": 4,
|
||||
"shield1": 0,
|
||||
"shield2": 0,
|
||||
"shield3": 0,
|
||||
"progressiveshield": 3,
|
||||
"mail2": 0,
|
||||
"mail3": 0,
|
||||
"progressivemail": 2,
|
||||
"halfmagic": 1,
|
||||
"quartermagic": 0,
|
||||
"bombsplus5": 0,
|
||||
"bombsplus10": 0,
|
||||
"arrowsplus5": 0,
|
||||
"arrowsplus10": 0,
|
||||
"arrow1": 1,
|
||||
"arrow10": 12,
|
||||
"bomb1": 0,
|
||||
"bomb3": 16,
|
||||
"bomb10": 1,
|
||||
"rupee1": 2,
|
||||
"rupee5": 4,
|
||||
"rupee20": 28,
|
||||
"rupee50": 7,
|
||||
"rupee100": 1,
|
||||
"rupee300": 5,
|
||||
"blueclock": 0,
|
||||
"greenclock": 0,
|
||||
"redclock": 0,
|
||||
"silversupgrade": 0,
|
||||
"generickeys": 0,
|
||||
"triforcepieces": 0,
|
||||
"triforcepiecesgoal": 0,
|
||||
"triforce": 0,
|
||||
"rupoor": 0,
|
||||
"rupoorcost": 10
|
||||
},
|
||||
"randomSprite": False,
|
||||
"outputpath": os.path.join("."),
|
||||
"saveonexit": "ask",
|
||||
"startinventoryarray": {}
|
||||
}
|
||||
|
||||
if sys.platform.lower().find("windows"):
|
||||
settings["enemizercli"] += ".exe"
|
||||
|
||||
# read saved settings file if it exists and set these
|
||||
settings_path = os.path.join(".", "resources", "user", "settings.json")
|
||||
if os.path.exists(settings_path):
|
||||
with(open(settings_path)) as json_file:
|
||||
data = json.load(json_file)
|
||||
for k, v in data.items():
|
||||
settings[k] = v
|
||||
return settings
|
||||
|
||||
|
||||
def 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["cli"] = cli_args
|
||||
|
||||
args["load"] = args["settings"]
|
||||
if args["gui"] is not None:
|
||||
for k in args["gui"]:
|
||||
if k not in args["load"] or args["load"][k] != args["gui"]:
|
||||
args["load"][k] = args["gui"][k]
|
||||
|
||||
if args["cli"] is None:
|
||||
args["cli"] = {}
|
||||
cli = vars(parse_arguments(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
|
||||
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
|
||||
if load_doesnt_have_key or different_val:
|
||||
if not cli_has_empty_dict:
|
||||
args["load"][k] = args["cli"][k]
|
||||
|
||||
return args
|
||||
+240
-69
@@ -1,6 +1,5 @@
|
||||
import random
|
||||
import collections
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, deque
|
||||
import logging
|
||||
import operator as op
|
||||
import time
|
||||
@@ -11,7 +10,7 @@ from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBa
|
||||
from Regions import key_only_locations
|
||||
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions
|
||||
from Dungeons import dungeon_regions, region_starts, split_region_starts, flexible_starts
|
||||
from Dungeons import drop_entrances, dungeon_bigs, dungeon_keys
|
||||
from Dungeons import drop_entrances, dungeon_bigs, dungeon_keys, dungeon_hints
|
||||
from Items import ItemFactory
|
||||
from RoomData import DoorKind, PairedDoor
|
||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, validate_tr
|
||||
@@ -54,8 +53,6 @@ def link_doors(world, player):
|
||||
within_dungeon(world, player)
|
||||
elif world.doorShuffle[player] == 'crossed':
|
||||
cross_dungeon(world, player)
|
||||
elif world.doorShuffle[player] == 'experimental':
|
||||
experiment(world, player)
|
||||
else:
|
||||
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||
@@ -69,7 +66,7 @@ def mark_regions(world, player):
|
||||
# traverse dungeons and make sure dungeon property is assigned
|
||||
player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
|
||||
for dungeon in player_dungeons:
|
||||
queue = collections.deque(dungeon.regions)
|
||||
queue = deque(dungeon.regions)
|
||||
while len(queue) > 0:
|
||||
region = world.get_region(queue.popleft(), player)
|
||||
if region.name not in dungeon.regions:
|
||||
@@ -87,31 +84,42 @@ def mark_regions(world, player):
|
||||
|
||||
def create_door_spoiler(world, player):
|
||||
logger = logging.getLogger('')
|
||||
queue = collections.deque((door for door in world.doors if door.player == player))
|
||||
|
||||
queue = deque(world.dungeon_layouts[player].values())
|
||||
while len(queue) > 0:
|
||||
door_a = queue.popleft()
|
||||
if door_a.type in [DoorType.Normal, DoorType.SpiralStairs]:
|
||||
door_b = door_a.dest
|
||||
if door_b is not None:
|
||||
logger.debug('spoiler: %s connected to %s', door_a.name, door_b.name)
|
||||
if not door_a.blocked and not door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'both', player)
|
||||
elif door_a.blocked:
|
||||
world.spoiler.set_door(door_b.name, door_a.name, 'entrance', player)
|
||||
elif door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'entrance', player)
|
||||
else:
|
||||
logger.warning('This is a bug')
|
||||
if door_b in queue:
|
||||
queue.remove(door_b)
|
||||
else:
|
||||
logger.debug('Door not found in queue: %s connected to %s', door_b.name, door_a.name)
|
||||
else:
|
||||
logger.warning('Door not connected: %s', door_a.name)
|
||||
builder = queue.popleft()
|
||||
done = set()
|
||||
start_regions = set(convert_regions(builder.layout_starts, world, player)) # todo: set all_entrances for basic
|
||||
reg_queue = deque(start_regions)
|
||||
visited = set(start_regions)
|
||||
while len(reg_queue) > 0:
|
||||
next = reg_queue.pop()
|
||||
for ext in next.exits:
|
||||
door_a = ext.door
|
||||
connect = ext.connected_region
|
||||
if door_a and door_a.type in [DoorType.Normal, DoorType.SpiralStairs] and door_a not in done:
|
||||
done.add(door_a)
|
||||
door_b = door_a.dest
|
||||
if door_b:
|
||||
done.add(door_b)
|
||||
if not door_a.blocked and not door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'both', player, builder.name)
|
||||
elif door_a.blocked:
|
||||
world.spoiler.set_door(door_b.name, door_a.name, 'entrance', player, builder.name)
|
||||
elif door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'entrance', player, builder.name)
|
||||
else:
|
||||
logger.warning('This is a bug during door spoiler')
|
||||
else:
|
||||
logger.warning('Door not connected: %s', door_a.name)
|
||||
if connect and connect.type == RegionType.Dungeon and connect not in visited:
|
||||
visited.add(connect)
|
||||
reg_queue.append(connect)
|
||||
|
||||
|
||||
def vanilla_key_logic(world, player):
|
||||
builders = []
|
||||
world.dungeon_layouts[player] = {}
|
||||
for dungeon in [dungeon for dungeon in world.dungeons if dungeon.player == player]:
|
||||
sector = Sector()
|
||||
sector.name = dungeon.name
|
||||
@@ -119,12 +127,13 @@ def vanilla_key_logic(world, player):
|
||||
builder = simple_dungeon_builder(sector.name, [sector])
|
||||
builder.master_sector = sector
|
||||
builders.append(builder)
|
||||
world.dungeon_layouts[player][builder.name] = builder
|
||||
|
||||
overworld_prep(world, player)
|
||||
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
||||
|
||||
enabled_entrances = {}
|
||||
sector_queue = collections.deque(builders)
|
||||
sector_queue = deque(builders)
|
||||
last_key = None
|
||||
while len(sector_queue) > 0:
|
||||
builder = sector_queue.popleft()
|
||||
@@ -149,8 +158,9 @@ def vanilla_key_logic(world, player):
|
||||
world.key_logic[player] = {}
|
||||
analyze_dungeon(key_layout, world, player)
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
log_key_logic(builder.name, key_layout.key_logic)
|
||||
last_key = None
|
||||
if world.shuffle[player] == 'vanilla':
|
||||
if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items':
|
||||
validate_vanilla_key_logic(world, player)
|
||||
|
||||
|
||||
@@ -187,8 +197,7 @@ def connect_simple_door(world, exit_name, region_name, player):
|
||||
d.dest = region
|
||||
|
||||
|
||||
def connect_door_only(world, exit_name, region_name, player):
|
||||
region = world.get_region(region_name, player)
|
||||
def connect_door_only(world, exit_name, region, player):
|
||||
d = world.check_for_door(exit_name, player)
|
||||
if d is not None:
|
||||
d.dest = region
|
||||
@@ -313,6 +322,7 @@ def within_dungeon(world, player):
|
||||
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)
|
||||
smooth_door_pairs(world, player)
|
||||
|
||||
|
||||
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map):
|
||||
@@ -334,7 +344,7 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
||||
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
|
||||
entrances_map, potentials, connections = connections_tuple
|
||||
enabled_entrances = {}
|
||||
sector_queue = collections.deque(dungeon_builders.values())
|
||||
sector_queue = deque(dungeon_builders.values())
|
||||
last_key = None
|
||||
while len(sector_queue) > 0:
|
||||
builder = sector_queue.popleft()
|
||||
@@ -425,7 +435,7 @@ def find_new_entrances(sector, connections, potentials, enabled, world, player):
|
||||
for potential in potentials.pop(new_region):
|
||||
enabled[potential] = (region.name, region.dungeon)
|
||||
# see if this unexplored region connects elsewhere
|
||||
queue = collections.deque(new_region.exits)
|
||||
queue = deque(new_region.exits)
|
||||
visited = set()
|
||||
while len(queue) > 0:
|
||||
ext = queue.popleft()
|
||||
@@ -671,6 +681,33 @@ def cross_dungeon(world, player):
|
||||
gt = world.get_dungeon('Ganons Tower', player)
|
||||
del gt.dungeon_items[0] # removes map
|
||||
|
||||
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)
|
||||
|
||||
# Re-assign dungeon bosses
|
||||
gt = world.get_dungeon('Ganons Tower', player)
|
||||
for name, builder in dungeon_builders.items():
|
||||
reassign_boss('GT Ice Armos', 'bottom', builder, gt, world, player)
|
||||
reassign_boss('GT Lanmolas 2', 'middle', builder, gt, world, player)
|
||||
reassign_boss('GT Moldorm', 'top', builder, gt, world, player)
|
||||
|
||||
if world.hints[player]:
|
||||
refine_hints(dungeon_builders)
|
||||
|
||||
|
||||
def assign_cross_keys(dungeon_builders, world, player):
|
||||
start = time.process_time()
|
||||
total_keys = remaining = 29
|
||||
total_candidates = 0
|
||||
@@ -688,6 +725,7 @@ def cross_dungeon(world, player):
|
||||
total_candidates += builder.key_doors_num
|
||||
start_regions_map[name] = start_regions
|
||||
|
||||
|
||||
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
||||
for name, builder in dungeon_builders.items():
|
||||
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
||||
@@ -717,7 +755,7 @@ def cross_dungeon(world, player):
|
||||
# Step 4: Try to assign remaining keys
|
||||
builder_order = [x for x in dungeon_builders.values() if x.flex > 0]
|
||||
builder_order.sort(key=lambda b: b.combo_size)
|
||||
queue = collections.deque(builder_order)
|
||||
queue = deque(builder_order)
|
||||
logger = logging.getLogger('')
|
||||
while len(queue) > 0 and remaining > 0:
|
||||
builder = queue.popleft()
|
||||
@@ -731,7 +769,7 @@ def cross_dungeon(world, player):
|
||||
if builder.flex > 0:
|
||||
builder.combo_size = ncr(len(builder.candidates), builder.key_doors_num)
|
||||
queue.append(builder)
|
||||
queue = collections.deque(sorted(queue, key=lambda b: b.combo_size))
|
||||
queue = deque(sorted(queue, key=lambda b: b.combo_size))
|
||||
else:
|
||||
logger.info('Cross Dungeon: Increase failed for %s', name)
|
||||
builder.key_doors_num -= 1
|
||||
@@ -739,22 +777,17 @@ def cross_dungeon(world, player):
|
||||
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
||||
|
||||
# Last Step: Adjust Small Key Dungeon Pool
|
||||
for name, builder in dungeon_builders.items():
|
||||
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if actual_chest_keys == 0:
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||
if not world.retro[player]:
|
||||
for name, builder in dungeon_builders.items():
|
||||
reassign_key_doors(builder, world, player)
|
||||
log_key_logic(builder.name, world.key_logic[player][builder.name])
|
||||
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if actual_chest_keys == 0:
|
||||
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)
|
||||
# todo: pair down paired doors - excessive rom writes ATM
|
||||
|
||||
# Re-assign dungeon bosses
|
||||
gt = world.get_dungeon('Ganons Tower', player)
|
||||
for name, builder in dungeon_builders.items():
|
||||
reassign_boss('GT Ice Armos', 'bottom', builder, gt, world, player)
|
||||
reassign_boss('GT Lanmolas 2', 'middle', builder, gt, world, player)
|
||||
reassign_boss('GT Moldorm', 'top', builder, gt, world, player)
|
||||
|
||||
|
||||
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||
@@ -765,9 +798,12 @@ def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||
new_dungeon.bosses[boss_key] = gt_boss
|
||||
|
||||
|
||||
def experiment(world, player):
|
||||
# print_wiki_doors(dungeon_regions, world, player)
|
||||
cross_dungeon(world, player)
|
||||
def refine_hints(dungeon_builders):
|
||||
for name, builder in dungeon_builders.items():
|
||||
for region in builder.master_sector.regions:
|
||||
for location in region.locations:
|
||||
if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
|
||||
location.hint_text = dungeon_hints[name]
|
||||
|
||||
|
||||
def convert_to_sectors(region_names, world, player):
|
||||
@@ -817,7 +853,7 @@ def convert_to_sectors(region_names, world, player):
|
||||
# those with split region starts like Desert/Skull combine for key layouts
|
||||
def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
||||
for recombine in recombinant_builders.values():
|
||||
queue = collections.deque(dungeon_builders.values())
|
||||
queue = deque(dungeon_builders.values())
|
||||
while len(queue) > 0:
|
||||
builder = queue.pop()
|
||||
if builder.name.startswith(recombine.name):
|
||||
@@ -825,8 +861,11 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
||||
if recombine.master_sector is None:
|
||||
recombine.master_sector = builder.master_sector
|
||||
recombine.master_sector.name = recombine.name
|
||||
recombine.pre_open_stonewall = builder.pre_open_stonewall
|
||||
else:
|
||||
recombine.master_sector.regions.extend(builder.master_sector.regions)
|
||||
if builder.pre_open_stonewall:
|
||||
recombine.pre_open_stonewall = builder.pre_open_stonewall
|
||||
recombine.layout_starts = list(entrances_map[recombine.name])
|
||||
dungeon_builders[recombine.name] = recombine
|
||||
|
||||
@@ -859,14 +898,16 @@ def shuffle_key_doors(builder, world, player):
|
||||
builder.key_doors_num = num_key_doors
|
||||
find_small_key_door_candidates(builder, start_regions, world, player)
|
||||
find_valid_combination(builder, start_regions, world, player)
|
||||
reassign_key_doors(builder, world, player)
|
||||
log_key_logic(builder.name, world.key_logic[player][builder.name])
|
||||
|
||||
|
||||
def find_current_key_doors(builder, world, player):
|
||||
def find_current_key_doors(builder):
|
||||
current_doors = []
|
||||
for region in builder.master_sector.regions:
|
||||
for ext in region.exits:
|
||||
d = world.check_for_door(ext.name, player)
|
||||
if d is not None and d.smallKey:
|
||||
d = ext.door
|
||||
if d and d.smallKey:
|
||||
current_doors.append(d)
|
||||
return current_doors
|
||||
|
||||
@@ -948,9 +989,9 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
|
||||
if player not in world.key_logic.keys():
|
||||
world.key_logic[player] = {}
|
||||
analyze_dungeon(key_layout, world, player)
|
||||
reassign_key_doors(builder, proposal, world, player)
|
||||
log_key_logic(builder.name, key_layout.key_logic)
|
||||
builder.key_door_proposal = proposal
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
world.key_layout[player][builder.name] = key_layout
|
||||
return True
|
||||
|
||||
|
||||
@@ -972,11 +1013,18 @@ def log_key_logic(d_name, key_logic):
|
||||
if rule.alternate_small_key is not None:
|
||||
for loc in rule.alternate_big_key_loc:
|
||||
logger.debug('---BK Loc %s', loc.name)
|
||||
logger.debug('Placement rules for %s', d_name)
|
||||
for rule in key_logic.placement_rules:
|
||||
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]))
|
||||
|
||||
|
||||
def build_pair_list(flat_list):
|
||||
paired_list = []
|
||||
queue = collections.deque(flat_list)
|
||||
queue = deque(flat_list)
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
if d.dest in queue and d.type != DoorType.SpiralStairs:
|
||||
@@ -1002,11 +1050,13 @@ def find_key_door_candidates(region, checked, world, player):
|
||||
dungeon = region.dungeon
|
||||
candidates = []
|
||||
checked_doors = list(checked)
|
||||
queue = collections.deque([(region, None, None)])
|
||||
queue = deque([(region, None, None)])
|
||||
while len(queue) > 0:
|
||||
current, last_door, last_region = queue.pop()
|
||||
for ext in current.exits:
|
||||
d = world.check_for_door(ext.name, player)
|
||||
d = ext.door
|
||||
if d and d.controller:
|
||||
d = d.controller
|
||||
if d is not None and not d.blocked and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
|
||||
valid = False
|
||||
if 0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]:
|
||||
@@ -1025,9 +1075,11 @@ def find_key_door_candidates(region, checked, world, player):
|
||||
okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable,
|
||||
DoorKind.Dashable, DoorKind.DungeonChanger]
|
||||
valid = kind in okay_normals and kind_b in okay_normals
|
||||
if valid and 0 <= d2.doorListPos < 4:
|
||||
candidates.append(d2)
|
||||
else:
|
||||
valid = True
|
||||
if valid:
|
||||
if valid and d not in candidates:
|
||||
candidates.append(d)
|
||||
if ext.connected_region.type != RegionType.Dungeon or ext.connected_region.dungeon == dungeon:
|
||||
queue.append((ext.connected_region, d, current))
|
||||
@@ -1058,10 +1110,12 @@ def ncr(n, r):
|
||||
return numerator / denominator
|
||||
|
||||
|
||||
def reassign_key_doors(builder, proposal, world, player):
|
||||
def reassign_key_doors(builder, world, player):
|
||||
logger = logging.getLogger('')
|
||||
logger.debug('Key doors for %s', builder.name)
|
||||
proposal = builder.key_door_proposal
|
||||
flat_proposal = flatten_pair_list(proposal)
|
||||
queue = collections.deque(find_current_key_doors(builder, world, player))
|
||||
queue = deque(find_current_key_doors(builder))
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
if d.type is DoorType.SpiralStairs and d not in proposal:
|
||||
@@ -1070,7 +1124,7 @@ def reassign_key_doors(builder, proposal, world, player):
|
||||
room.delete(d.doorListPos)
|
||||
else:
|
||||
if len(room.doorList) > 1:
|
||||
room.mirror(d.doorListPos) # todo: I don't think this works for crossed - maybe it will
|
||||
room.mirror(d.doorListPos) # I think this works for crossed now
|
||||
else:
|
||||
room.delete(d.doorListPos)
|
||||
d.smallKey = False
|
||||
@@ -1129,6 +1183,104 @@ def change_door_to_small_key(d, world, player):
|
||||
room.change(d.doorListPos, DoorKind.SmallKey)
|
||||
|
||||
|
||||
def smooth_door_pairs(world, player):
|
||||
all_doors = [x for x in world.doors if x.player == player]
|
||||
skip = set()
|
||||
for door in all_doors:
|
||||
if door.type in [DoorType.Normal, DoorType.Interior] and door not in skip:
|
||||
partner = door.dest
|
||||
skip.add(partner)
|
||||
room_a = world.get_room(door.roomIndex, player)
|
||||
room_b = world.get_room(partner.roomIndex, player)
|
||||
type_a = room_a.kind(door)
|
||||
type_b = room_b.kind(partner)
|
||||
valid_pair = stateful_door(door, type_a) and stateful_door(partner, type_b)
|
||||
if door.type == DoorType.Normal:
|
||||
if type_a == DoorKind.SmallKey or type_b == DoorKind.SmallKey:
|
||||
if valid_pair:
|
||||
if type_a != DoorKind.SmallKey:
|
||||
room_a.change(door.doorListPos, DoorKind.SmallKey)
|
||||
if type_b != DoorKind.SmallKey:
|
||||
room_b.change(partner.doorListPos, DoorKind.SmallKey)
|
||||
add_pair(door, partner, world, player)
|
||||
else:
|
||||
if type_a == DoorKind.SmallKey:
|
||||
remove_pair(door, world, player)
|
||||
if type_b == DoorKind.SmallKey:
|
||||
remove_pair(door, world, player)
|
||||
elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||
if valid_pair:
|
||||
if type_a == type_b:
|
||||
add_pair(door, partner, world, player)
|
||||
spoiler_type = 'Bomb Door' if type_a == DoorKind.Bombable else 'Dash Door'
|
||||
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
|
||||
else:
|
||||
new_type = DoorKind.Dashable if type_a == DoorKind.Dashable or type_b == DoorKind.Dashable else DoorKind.Bombable
|
||||
if type_a != new_type:
|
||||
room_a.change(door.doorListPos, new_type)
|
||||
if type_b != new_type:
|
||||
room_b.change(partner.doorListPos, new_type)
|
||||
add_pair(door, partner, world, player)
|
||||
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
|
||||
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
|
||||
else:
|
||||
if type_a in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||
room_a.change(door.doorListPos, DoorKind.Normal)
|
||||
remove_pair(door, world, player)
|
||||
elif type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||
room_b.change(partner.doorListPos, DoorKind.Normal)
|
||||
remove_pair(partner, world, player)
|
||||
elif world.experimental[player] and valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey:
|
||||
random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b)
|
||||
world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original]
|
||||
|
||||
|
||||
def add_pair(door_a, door_b, world, player):
|
||||
pair_a, pair_b = None, None
|
||||
for paired_door in world.paired_doors[player]:
|
||||
if paired_door.door_a == door_a.name and paired_door.door_b == door_b.name:
|
||||
paired_door.pair = True
|
||||
return
|
||||
if paired_door.door_a == door_b.name and paired_door.door_b == door_a.name:
|
||||
paired_door.pair = True
|
||||
return
|
||||
if paired_door.door_a == door_a.name or paired_door.door_b == door_a.name:
|
||||
pair_a = paired_door
|
||||
if paired_door.door_a == door_b.name or paired_door.door_b == door_b.name:
|
||||
pair_b = paired_door
|
||||
if pair_a:
|
||||
pair_a.pair = False
|
||||
if pair_b:
|
||||
pair_b.pair = False
|
||||
world.paired_doors[player].append(PairedDoor(door_a, door_b))
|
||||
|
||||
|
||||
def remove_pair(door, world, player):
|
||||
for paired_door in world.paired_doors[player]:
|
||||
if paired_door.door_a == door.name or paired_door.door_b == door.name:
|
||||
paired_door.pair = False
|
||||
break
|
||||
|
||||
|
||||
def stateful_door(door, kind):
|
||||
if 0 <= door.doorListPos < 4:
|
||||
return kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable] #, DoorKind.BigKey]
|
||||
return False
|
||||
|
||||
|
||||
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]
|
||||
if r_kind != DoorKind.Normal:
|
||||
if door.type == DoorType.Normal:
|
||||
add_pair(door, partner, world, player)
|
||||
if type_a != r_kind:
|
||||
room_a.change(door.doorListPos, r_kind)
|
||||
if type_b != r_kind:
|
||||
room_b.change(partner.doorListPos, r_kind)
|
||||
spoiler_type = 'Bomb Door' if r_kind == DoorKind.Bombable else 'Dash Door'
|
||||
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
|
||||
|
||||
|
||||
def determine_required_paths(world, player):
|
||||
paths = {
|
||||
'Hyrule Castle': ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby'],
|
||||
@@ -1168,13 +1320,18 @@ def find_inaccessible_regions(world, player):
|
||||
regs = convert_regions(start_regions, world, player)
|
||||
all_regions = set([r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon])
|
||||
visited_regions = set()
|
||||
queue = collections.deque(regs)
|
||||
queue = deque(regs)
|
||||
while len(queue) > 0:
|
||||
next_region = queue.popleft()
|
||||
visited_regions.add(next_region)
|
||||
if next_region.name == 'Inverted Dark Sanctuary': # special spawn point in cave
|
||||
for ent in next_region.entrances:
|
||||
parent = ent.parent_region
|
||||
if parent and parent.type is not RegionType.Dungeon and parent not in queue and parent not in visited_regions:
|
||||
queue.append(parent)
|
||||
for ext in next_region.exits:
|
||||
connect = ext.connected_region
|
||||
if connect is not None and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions:
|
||||
if connect and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions:
|
||||
queue.append(connect)
|
||||
world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)])
|
||||
if world.mode[player] == 'standard':
|
||||
@@ -1194,8 +1351,8 @@ def add_inaccessible_doors(world, player):
|
||||
# todo: ignore standard mode hyrule castle ledge?
|
||||
for inaccessible_region in world.inaccessible_regions[player]:
|
||||
region = world.get_region(inaccessible_region, player)
|
||||
for exit in region.exits:
|
||||
create_door(world, player, exit.name, region.name)
|
||||
for ext in region.exits:
|
||||
create_door(world, player, ext.name, region.name)
|
||||
|
||||
|
||||
def create_door(world, player, entName, region_name):
|
||||
@@ -1301,7 +1458,9 @@ def check_for_pinball_fix(state, bad_region, world, player):
|
||||
|
||||
@unique
|
||||
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
|
||||
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
|
||||
|
||||
# DATA GOES DOWN HERE
|
||||
@@ -1312,7 +1471,13 @@ logical_connections = [
|
||||
('Eastern Hint Tile Push Block', 'Eastern Hint Tile'),
|
||||
('Eastern Map Balcony Hook Path', 'Eastern Map Room'),
|
||||
('Eastern Map Room Drop Down', 'Eastern Map Balcony'),
|
||||
('Desert Main Lobby Left Path', 'Desert Left Alcove'),
|
||||
('Desert Main Lobby Right Path', 'Desert Right Alcove'),
|
||||
('Desert Left Alcove Path', 'Desert Main Lobby'),
|
||||
('Desert Right Alcove Path', 'Desert Main Lobby'),
|
||||
('Hera Big Chest Landing Exit', 'Hera 4F'),
|
||||
('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'),
|
||||
('PoD Pit Room Block Path S', 'PoD Pit Room'),
|
||||
('PoD Arena Bonk Path', 'PoD Arena Bridge'),
|
||||
('PoD Arena Main Crystal Path', 'PoD Arena Crystal'),
|
||||
('PoD Arena Crystal Path', 'PoD Arena Main'),
|
||||
@@ -1321,6 +1486,8 @@ logical_connections = [
|
||||
('PoD Arena Bridge Drop Down', 'PoD Arena Main'),
|
||||
('PoD Map Balcony Drop Down', 'PoD Sexy Statue'),
|
||||
('PoD Basement Ledge Drop Down', 'PoD Stalfos Basement'),
|
||||
('PoD Falling Bridge Path N', 'PoD Falling Bridge Ledge'),
|
||||
('PoD Falling Bridge Path S', 'PoD Falling Bridge'),
|
||||
('Swamp Lobby Moat', 'Swamp Entrance'),
|
||||
('Swamp Entrance Moat', 'Swamp Lobby'),
|
||||
('Swamp Trench 1 Approach Dry', 'Swamp Trench 1 Nexus'),
|
||||
@@ -1389,6 +1556,8 @@ logical_connections = [
|
||||
('Mire Map Spot Blue Barrier', 'Mire Crystal Dead End'),
|
||||
('Mire Crystal Dead End Left Barrier', 'Mire Map Spot'),
|
||||
('Mire Crystal Dead End Right Barrier', 'Mire Map Spike Side'),
|
||||
('Mire Hidden Shooters Block Path S', 'Mire Hidden Shooters'),
|
||||
('Mire Hidden Shooters Block Path N', 'Mire Hidden Shooters Blocked'),
|
||||
('Mire Left Bridge Hook Path', 'Mire Right Bridge'),
|
||||
('Mire Crystal Right Orange Barrier', 'Mire Crystal Mid'),
|
||||
('Mire Crystal Mid Orange Barrier', 'Mire Crystal Right'),
|
||||
@@ -1409,6 +1578,8 @@ logical_connections = [
|
||||
('TR Crystal Maze Blue Path', 'TR Crystal Maze'),
|
||||
('TR Crystal Maze Cane Path', 'TR Crystal Maze'),
|
||||
('GT Blocked Stairs Block Path', 'GT Big Chest'),
|
||||
('GT Speed Torch South Path', 'GT Speed Torch'),
|
||||
('GT Speed Torch North Path', 'GT Speed Torch Upper'),
|
||||
('GT Hookshot East-North Path', 'GT Hookshot North Platform'),
|
||||
('GT Hookshot East-South Path', 'GT Hookshot South Platform'),
|
||||
('GT Hookshot North-East Path', 'GT Hookshot East Platform'),
|
||||
|
||||
@@ -182,6 +182,10 @@ def create_doors(world, player):
|
||||
create_door(player, 'Desert Main Lobby N Edge', Open).dir(No, 0x84, None, High),
|
||||
create_door(player, 'Desert Main Lobby NE Edge', Open).dir(No, 0x84, None, High),
|
||||
create_door(player, 'Desert Main Lobby E Edge', Open).dir(Ea, 0x84, None, High),
|
||||
create_door(player, 'Desert Main Lobby Left Path', Lgcl),
|
||||
create_door(player, 'Desert Main Lobby Right Path', Lgcl),
|
||||
create_door(player, 'Desert Left Alcove Path', Lgcl),
|
||||
create_door(player, 'Desert Right Alcove Path', Lgcl),
|
||||
create_door(player, 'Desert Dead End Edge', Open).dir(So, 0x74, None, High),
|
||||
create_door(player, 'Desert East Wing W Edge', Open).dir(We, 0x85, None, High),
|
||||
create_door(player, 'Desert East Wing N Edge', Open).dir(No, 0x85, None, High),
|
||||
@@ -321,6 +325,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'PoD Pit Room NE', Nrml).dir(No, 0x3a, Right, High).pos(2),
|
||||
create_door(player, 'PoD Pit Room Freefall', Hole),
|
||||
create_door(player, 'PoD Pit Room Bomb Hole', Hole),
|
||||
create_door(player, 'PoD Pit Room Block Path N', Lgcl),
|
||||
create_door(player, 'PoD Pit Room Block Path S', Lgcl),
|
||||
create_door(player, 'PoD Big Key Landing Hole', Hole),
|
||||
create_door(player, 'PoD Big Key Landing Down Stairs', Sprl).dir(Dn, 0x3a, 0, HTH).ss(A, 0x11, 0x00).kill(),
|
||||
create_door(player, 'PoD Basement Ledge Up Stairs', Sprl).dir(Up, 0x0a, 0, HTH).ss(A, 0x1a, 0xec).small_key().pos(0),
|
||||
@@ -354,6 +360,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'PoD Falling Bridge SW', Nrml).dir(So, 0x1a, Left, High).small_key().pos(3),
|
||||
create_door(player, 'PoD Falling Bridge WN', Nrml).dir(We, 0x1a, Top, High).small_key().pos(1),
|
||||
create_door(player, 'PoD Falling Bridge EN', Intr).dir(Ea, 0x1a, Top, High).pos(4),
|
||||
create_door(player, 'PoD Falling Bridge Path N', Lgcl),
|
||||
create_door(player, 'PoD Falling Bridge Path S', Lgcl),
|
||||
create_door(player, 'PoD Big Chest Balcony W', Nrml).dir(We, 0x1a, Mid, High).pos(2),
|
||||
create_door(player, 'PoD Dark Maze EN', Nrml).dir(Ea, 0x19, Top, High).small_key().pos(1),
|
||||
create_door(player, 'PoD Dark Maze E', Nrml).dir(Ea, 0x19, Mid, High).pos(0),
|
||||
@@ -658,7 +666,7 @@ 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(1),
|
||||
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 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(),
|
||||
@@ -735,6 +743,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'Mire Hidden Shooters ES', Nrml).dir(Ea, 0xb2, Bot, High).pos(7),
|
||||
create_door(player, 'Mire Hidden Shooters WS', Intr).dir(We, 0xb2, Bot, High).pos(1),
|
||||
create_door(player, 'Mire Cross ES', Intr).dir(Ea, 0xb2, Bot, High).pos(1),
|
||||
create_door(player, 'Mire Hidden Shooters Block Path S', Lgcl),
|
||||
create_door(player, 'Mire Hidden Shooters Block Path N', Lgcl),
|
||||
create_door(player, 'Mire Hidden Shooters NE', Intr).dir(No, 0xb2, Right, High).pos(2),
|
||||
create_door(player, 'Mire Minibridge SE', Intr).dir(So, 0xb2, Right, High).pos(2),
|
||||
create_door(player, 'Mire Cross SW', Nrml).dir(So, 0xb2, Left, High).pos(5),
|
||||
@@ -912,6 +922,8 @@ def create_doors(world, player):
|
||||
create_door(player, 'GT Tile Room EN', Intr).dir(Ea, 0x8d, Top, High).small_key().pos(1),
|
||||
create_door(player, 'GT Speed Torch WN', Intr).dir(We, 0x8d, Top, High).small_key().pos(1),
|
||||
create_door(player, 'GT Speed Torch NE', Nrml).dir(No, 0x8d, Right, High).pos(3),
|
||||
create_door(player, 'GT Speed Torch South Path', Lgcl),
|
||||
create_door(player, 'GT Speed Torch North Path', Lgcl),
|
||||
create_door(player, 'GT Speed Torch WS', Intr).dir(We, 0x8d, Bot, High).pos(4),
|
||||
create_door(player, 'GT Pots n Blocks ES', Intr).dir(Ea, 0x8d, Bot, High).pos(4),
|
||||
create_door(player, 'GT Speed Torch SE', Nrml).dir(So, 0x8d, Right, High).trap(0x4).pos(0),
|
||||
@@ -1098,6 +1110,8 @@ def create_doors(world, player):
|
||||
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
|
||||
world.get_door('PoD Sexy Statue W', player).c_switch()
|
||||
world.get_door('PoD Sexy Statue NW', player).c_switch()
|
||||
world.get_door('PoD Map Balcony WS', player).c_switch()
|
||||
world.get_door('PoD Map Balcony South Stairs', player).c_switch()
|
||||
world.get_door('PoD Bow Statue SW', player).c_switch()
|
||||
world.get_door('PoD Bow Statue Down Ladder', player).c_switch()
|
||||
world.get_door('PoD Dark Pegs Up Ladder', player).c_switch()
|
||||
@@ -1211,44 +1225,44 @@ def create_doors(world, player):
|
||||
|
||||
def create_paired_doors(world, player):
|
||||
world.paired_doors[player] = [
|
||||
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N'),
|
||||
PairedDoor('TR Pokey 2 ES', 'TR Lava Island WS'), # TR Pokey Key
|
||||
PairedDoor('TR Dodgers NE', 'TR Lava Escape SE'), # TR Big key door by pipes
|
||||
PairedDoor('PoD Falling Bridge WN', 'PoD Dark Maze EN'), # Pod Dark maze door
|
||||
PairedDoor('PoD Dark Maze E', 'PoD Big Chest Balcony W'), # PoD Bombable by Big Chest
|
||||
PairedDoor('PoD Arena Main NW', 'PoD Falling Bridge SW'), # Pod key door by bridge
|
||||
PairedDoor('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S'),
|
||||
PairedDoor('Swamp Hub WN', 'Swamp Crystal Switch EN'), # Swamp key door crystal switch
|
||||
PairedDoor('Swamp Hub North Ledge N', 'Swamp Push Statue S'), # Swamp key door above big chest
|
||||
PairedDoor('PoD Map Balcony WS', 'PoD Arena Ledge ES'), # Pod bombable by arena
|
||||
PairedDoor('Swamp Hub Dead Ledge EN', 'Swamp Hammer Switch WN'), # Swamp bombable to random pots
|
||||
PairedDoor('Swamp Pot Row WN', 'Swamp Map Ledge EN'), # Swamp bombable to map chest
|
||||
PairedDoor('Swamp Pot Row WS', 'Swamp Trench 1 Approach ES'), # Swamp key door early room $38
|
||||
PairedDoor('PoD Middle Cage N', 'PoD Pit Room S'),
|
||||
PairedDoor('GT Crystal Circles SW', 'GT Left Moldorm Ledge NW'), # GT moldorm key door
|
||||
PairedDoor('Ice Conveyor SW', 'Ice Bomb Jump NW'), # Ice BJ key door
|
||||
PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE'),
|
||||
PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW'), # Skull 3 key door
|
||||
PairedDoor('Skull 1 Lobby WS', 'Skull Pot Prison ES'), # Skull 1 key door - pot prison to big chest
|
||||
PairedDoor('Skull Map Room SE', 'Skull Pinball NE'), # Skull 1 - pinball key door
|
||||
PairedDoor('GT Dash Hall NE', 'GT Hidden Spikes SE'), # gt main big key door
|
||||
PairedDoor('Ice Spike Cross ES', 'Ice Spike Room WS'), # ice door to spike chest
|
||||
PairedDoor('GT Conveyor Star Pits EN', 'GT Falling Bridge WN'), # gt right side key door to cape bridge
|
||||
PairedDoor('GT Warp Maze (Rails) WS', 'GT Randomizer Room ES'), # gt bombable to rando room
|
||||
PairedDoor('Ice Tall Hint SE', 'Ice Lonely Freezor NE'), # ice's big icy room key door to lonely freezor
|
||||
PairedDoor('Eastern Courtyard N', 'Eastern Darkness S'),
|
||||
PairedDoor('Mire Fishbone SE', 'Mire Spike Barrier NE'), # mire fishbone key door
|
||||
PairedDoor('Mire BK Door Room N', 'Mire Left Bridge S'), # mire big key door to bridges
|
||||
PairedDoor('Eastern Big Key NE', 'Eastern Hint Tile Blocked Path SE'),
|
||||
PairedDoor('TR Hub NW', 'TR Pokey 1 SW'), # TR somaria hub to pokey
|
||||
PairedDoor('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN'),
|
||||
PairedDoor('Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW'), # TT random bomb to pots
|
||||
PairedDoor('Thieves BK Corner NE', 'Thieves Hallway SE'), # TT big key door
|
||||
PairedDoor('Ice Switch Room ES', 'Ice Refill WS'), # Ice last key door to crystal switch
|
||||
PairedDoor('Mire Hub WS', 'Mire Conveyor Crystal ES'), # mire hub key door to attic
|
||||
PairedDoor('Mire Hub Right EN', 'Mire Map Spot WN'), # mire hub key door to map
|
||||
PairedDoor('TR Dash Bridge WS', 'TR Crystal Maze ES'), # tr last key door to switch maze
|
||||
PairedDoor('Thieves Ambush E', 'Thieves Rail Ledge W') # TT dashable above
|
||||
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N', True),
|
||||
PairedDoor('TR Pokey 2 ES', 'TR Lava Island WS', True), # TR Pokey Key
|
||||
PairedDoor('TR Dodgers NE', 'TR Lava Escape SE', True), # TR Big key door by pipes
|
||||
PairedDoor('PoD Falling Bridge WN', 'PoD Dark Maze EN', True), # Pod Dark maze door
|
||||
PairedDoor('PoD Dark Maze E', 'PoD Big Chest Balcony W', True), # PoD Bombable by Big Chest
|
||||
PairedDoor('PoD Arena Main NW', 'PoD Falling Bridge SW', True), # Pod key door by bridge
|
||||
PairedDoor('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S', True),
|
||||
PairedDoor('Swamp Hub WN', 'Swamp Crystal Switch EN', True), # Swamp key door crystal switch
|
||||
PairedDoor('Swamp Hub North Ledge N', 'Swamp Push Statue S', True), # Swamp key door above big chest
|
||||
PairedDoor('PoD Map Balcony WS', 'PoD Arena Ledge ES', True), # Pod bombable by arena
|
||||
PairedDoor('Swamp Hub Dead Ledge EN', 'Swamp Hammer Switch WN', True), # Swamp bombable to random pots
|
||||
PairedDoor('Swamp Pot Row WN', 'Swamp Map Ledge EN', True), # Swamp bombable to map chest
|
||||
PairedDoor('Swamp Pot Row WS', 'Swamp Trench 1 Approach ES', True), # Swamp key door early room $38
|
||||
PairedDoor('PoD Middle Cage N', 'PoD Pit Room S', True),
|
||||
PairedDoor('GT Crystal Circles SW', 'GT Left Moldorm Ledge NW', True), # GT moldorm key door
|
||||
PairedDoor('Ice Conveyor SW', 'Ice Bomb Jump NW', True), # Ice BJ key door
|
||||
PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE', True),
|
||||
PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW', True), # Skull 3 key door
|
||||
PairedDoor('Skull 1 Lobby WS', 'Skull Pot Prison ES', True), # Skull 1 key door - pot prison to big chest
|
||||
PairedDoor('Skull Map Room SE', 'Skull Pinball NE', True), # Skull 1 - pinball key door
|
||||
PairedDoor('GT Dash Hall NE', 'GT Hidden Spikes SE', True), # gt main big key door
|
||||
PairedDoor('Ice Spike Cross ES', 'Ice Spike Room WS', True), # ice door to spike chest
|
||||
PairedDoor('GT Conveyor Star Pits EN', 'GT Falling Bridge WN', True), # gt right side key door to cape bridge
|
||||
PairedDoor('GT Warp Maze (Rails) WS', 'GT Randomizer Room ES', True), # gt bombable to rando room
|
||||
PairedDoor('Ice Tall Hint SE', 'Ice Lonely Freezor NE', True), # ice's big icy room key door to lonely freezor
|
||||
PairedDoor('Eastern Courtyard N', 'Eastern Darkness S', True),
|
||||
PairedDoor('Mire Fishbone SE', 'Mire Spike Barrier NE', True), # mire fishbone key door
|
||||
PairedDoor('Mire BK Door Room N', 'Mire Left Bridge S', True), # mire big key door to bridges
|
||||
PairedDoor('Eastern Big Key NE', 'Eastern Hint Tile Blocked Path SE', True),
|
||||
PairedDoor('TR Hub NW', 'TR Pokey 1 SW', True), # TR somaria hub to pokey
|
||||
PairedDoor('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN', True),
|
||||
PairedDoor('Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW', True), # TT random bomb to pots
|
||||
PairedDoor('Thieves BK Corner NE', 'Thieves Hallway SE', True), # TT big key door
|
||||
PairedDoor('Ice Switch Room ES', 'Ice Refill WS', True), # Ice last key door to crystal switch
|
||||
PairedDoor('Mire Hub WS', 'Mire Conveyor Crystal ES', True), # mire hub key door to attic
|
||||
PairedDoor('Mire Hub Right EN', 'Mire Map Spot WN', True), # mire hub key door to map
|
||||
PairedDoor('TR Dash Bridge WS', 'TR Crystal Maze ES', True), # tr last key door to switch maze
|
||||
PairedDoor('Thieves Ambush E', 'Thieves Rail Ledge W', True) # TT dashable above
|
||||
]
|
||||
|
||||
|
||||
|
||||
+116
-16
@@ -1,5 +1,6 @@
|
||||
import random
|
||||
import collections
|
||||
import itertools
|
||||
from collections import defaultdict, deque
|
||||
from enum import Enum, unique
|
||||
import logging
|
||||
@@ -447,9 +448,12 @@ def stonewall_valid(stonewall):
|
||||
if bad_door.blocked:
|
||||
return True # great we're done with this one
|
||||
loop_region = stonewall.entrance.parent_region
|
||||
start_region = bad_door.entrance.parent_region
|
||||
queue = deque([start_region])
|
||||
visited = {start_region}
|
||||
start_regions = [bad_door.entrance.parent_region]
|
||||
if bad_door.dependents:
|
||||
for dep in bad_door.dependents:
|
||||
start_regions.append(dep.entrance.parent_region)
|
||||
queue = deque(start_regions)
|
||||
visited = set(start_regions)
|
||||
while len(queue) > 0:
|
||||
region = queue.popleft()
|
||||
if region == loop_region:
|
||||
@@ -1027,6 +1031,7 @@ class DungeonBuilder(object):
|
||||
self.key_doors_num = None
|
||||
self.combo_size = None
|
||||
self.flex = 0
|
||||
self.key_door_proposal = None
|
||||
|
||||
if name in dungeon_dead_end_allowance.keys():
|
||||
self.allowance = dungeon_dead_end_allowance[name]
|
||||
@@ -1463,20 +1468,32 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
|
||||
problem_builders = identify_branching_issues_2(problem_builders)
|
||||
|
||||
# step 5: assign randomly until gone - must maintain connectedness, neutral polarity, branching, lack, etc.
|
||||
comb_w_replace = len(dungeon_map) ** len(neutral_choices)
|
||||
combinations = None
|
||||
if comb_w_replace <= 1000:
|
||||
combinations = list(itertools.product(dungeon_map.keys(), repeat=len(neutral_choices)))
|
||||
random.shuffle(combinations)
|
||||
tries = 0
|
||||
while len(polarized_sectors) > 0:
|
||||
if tries > 100:
|
||||
if tries > 1000 or (combinations and tries >= len(combinations)):
|
||||
raise Exception('No valid assignment found. Ref: %s' % next(iter(dungeon_map.keys())))
|
||||
choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices))
|
||||
valid = []
|
||||
if combinations:
|
||||
choices = combinations[tries]
|
||||
else:
|
||||
choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices))
|
||||
chosen_sectors = defaultdict(list)
|
||||
for i, choice in enumerate(choices):
|
||||
builder = dungeon_map[choice]
|
||||
if valid_assignment(builder, neutral_choices[i]):
|
||||
chosen_sectors[choice].extend(neutral_choices[i])
|
||||
all_valid = True
|
||||
for name, sector_list in chosen_sectors.items():
|
||||
if not valid_assignment(dungeon_map[name], sector_list):
|
||||
all_valid = False
|
||||
break
|
||||
if all_valid:
|
||||
for i, choice in enumerate(choices):
|
||||
builder = dungeon_map[choice]
|
||||
for sector in neutral_choices[i]:
|
||||
assign_sector(sector, builder, polarized_sectors, global_pole)
|
||||
valid.append(neutral_choices[i])
|
||||
for c in valid:
|
||||
neutral_choices.remove(c)
|
||||
tries += 1
|
||||
|
||||
|
||||
@@ -1927,6 +1944,7 @@ def resolve_equations(builder, sector_list):
|
||||
# negative benefit transforms (dead end)
|
||||
def find_priority_equation(equations, current_access):
|
||||
flex = calc_flex(equations, current_access)
|
||||
required = calc_required(equations, current_access)
|
||||
best_profit = None
|
||||
triplet_candidates = []
|
||||
local_profit_map = {}
|
||||
@@ -1947,14 +1965,17 @@ def find_priority_equation(equations, current_access):
|
||||
else:
|
||||
triplet_candidates.append((eq, eq_list, sector))
|
||||
local_profit_map[sector] = best_local_profit
|
||||
if len(triplet_candidates) == 0:
|
||||
filtered_candidates = filter_requirements(triplet_candidates, equations, required, current_access)
|
||||
if len(filtered_candidates) == 0:
|
||||
filtered_candidates = triplet_candidates
|
||||
if len(filtered_candidates) == 0:
|
||||
return None, None, None # can't pay for anything
|
||||
if len(triplet_candidates) == 1:
|
||||
return triplet_candidates[0]
|
||||
if len(filtered_candidates) == 1:
|
||||
return filtered_candidates[0]
|
||||
|
||||
required_candidates = [x for x in triplet_candidates if x[0].required]
|
||||
required_candidates = [x for x in filtered_candidates if x[0].required]
|
||||
if len(required_candidates) == 0:
|
||||
required_candidates = triplet_candidates
|
||||
required_candidates = filtered_candidates
|
||||
if len(required_candidates) == 1:
|
||||
return required_candidates[0]
|
||||
|
||||
@@ -1970,6 +1991,46 @@ def find_priority_equation(equations, current_access):
|
||||
return good_local_candidates[0] # just pick one I guess
|
||||
|
||||
|
||||
def calc_required(equations, current_access):
|
||||
ttl = 0
|
||||
for num in current_access.values():
|
||||
ttl += num
|
||||
local_profit_map = {}
|
||||
for sector, eq_list in equations.items():
|
||||
best_local_profit = None
|
||||
for eq in eq_list:
|
||||
profit = eq.profit()
|
||||
if best_local_profit is None or profit > best_local_profit:
|
||||
best_local_profit = profit
|
||||
local_profit_map[sector] = best_local_profit
|
||||
ttl += best_local_profit
|
||||
if ttl == 0:
|
||||
new_lists = {}
|
||||
for sector, eq_list in equations.items():
|
||||
if len(eq_list) > 1:
|
||||
rem_list = []
|
||||
for eq in eq_list:
|
||||
if eq.profit() < local_profit_map[sector]:
|
||||
rem_list.append(eq)
|
||||
if len(rem_list) > 0:
|
||||
new_lists[sector] = [x for x in eq_list if x not in rem_list]
|
||||
for sector, eq_list in new_lists.items():
|
||||
if len(eq_list) <= 1:
|
||||
for eq in eq_list:
|
||||
eq.required = True
|
||||
equations[sector] = eq_list
|
||||
required_costs = defaultdict(int)
|
||||
required_benefits = defaultdict(int)
|
||||
for sector, eq_list in equations.items():
|
||||
for eq in eq_list:
|
||||
if eq.required:
|
||||
for key, door_list in eq.cost.items():
|
||||
required_costs[key] += len(door_list)
|
||||
for key, door_list in eq.benefit.items():
|
||||
required_benefits[key] += len(door_list)
|
||||
return required_costs, required_benefits
|
||||
|
||||
|
||||
def calc_flex(equations, current_access):
|
||||
flex_spending = defaultdict(int)
|
||||
required_costs = defaultdict(int)
|
||||
@@ -1983,6 +2044,45 @@ def calc_flex(equations, current_access):
|
||||
return flex_spending
|
||||
|
||||
|
||||
def filter_requirements(triplet_candidates, equations, required, current_access):
|
||||
r_costs, r_exits = required
|
||||
valid_candidates = []
|
||||
for cand, cand_list, cand_sector in triplet_candidates:
|
||||
valid = True
|
||||
if not cand.required:
|
||||
potential_benefit = defaultdict(int)
|
||||
potential_costs = defaultdict(int)
|
||||
for h_type, benefit in current_access.items():
|
||||
cur_cost = len(cand.cost[h_type])
|
||||
if benefit - cur_cost > 0:
|
||||
potential_benefit[h_type] += benefit - cur_cost
|
||||
for h_type, benefit_list in cand.benefit.items():
|
||||
potential_benefit[h_type] += len(benefit_list)
|
||||
for sector, eq_list in equations.items():
|
||||
if sector == cand_sector:
|
||||
affected_doors = [d for x in cand.benefit.values() for d in x] + [d for x in cand.cost.values() for d in x]
|
||||
adj_list = [x for x in eq_list if x.door not in affected_doors]
|
||||
else:
|
||||
adj_list = eq_list
|
||||
for eq in adj_list:
|
||||
for h_type, benefit_list in eq.benefit.items():
|
||||
potential_benefit[h_type] += len(benefit_list)
|
||||
for h_type, cost_list in eq.cost.items():
|
||||
potential_costs[h_type] += len(cost_list)
|
||||
for h_type, requirement in r_costs.items():
|
||||
if requirement > 0 and potential_benefit[h_type] < requirement:
|
||||
valid = False
|
||||
break
|
||||
if valid:
|
||||
for h_type, requirement in r_exits.items():
|
||||
if requirement > 0 and potential_costs[h_type] < requirement:
|
||||
valid = False
|
||||
break
|
||||
if valid:
|
||||
valid_candidates.append((cand, cand_list, cand_sector))
|
||||
return valid_candidates
|
||||
|
||||
|
||||
def resolve_equation(equation, eq_list, sector, current_access, reached_doors, equations):
|
||||
for key, door_list in equation.cost.items():
|
||||
if current_access[key] - len(door_list) < 0:
|
||||
|
||||
+7
-302
@@ -8,312 +8,12 @@ import textwrap
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from CLI import parse_arguments
|
||||
from Main import main
|
||||
from Rom import get_sprite_from_name
|
||||
from Utils import is_bundled, close_console
|
||||
from Fill import FillError
|
||||
|
||||
|
||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||
|
||||
def _get_help_string(self, action):
|
||||
return textwrap.dedent(action.help)
|
||||
|
||||
def parse_arguments(argv, no_defaults=False):
|
||||
def defval(value):
|
||||
return value if not no_defaults else None
|
||||
|
||||
# we need to know how many players we have first
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--multi', default=defval(1), type=lambda value: min(max(int(value), 1), 255))
|
||||
multiargs, _ = parser.parse_known_args(argv)
|
||||
|
||||
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
|
||||
parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'],
|
||||
help='''\
|
||||
Select Enforcement of Item Requirements. (default: %(default)s)
|
||||
No Glitches:
|
||||
Minor Glitches: May require Fake Flippers, Bunny Revival
|
||||
and Dark Room Navigation.
|
||||
No Logic: Distribute items without regard for
|
||||
item requirements.
|
||||
''')
|
||||
parser.add_argument('--mode', default=defval('open'), const='open', nargs='?', choices=['standard', 'open', 'inverted'],
|
||||
help='''\
|
||||
Select game mode. (default: %(default)s)
|
||||
Open: World starts with Zelda rescued.
|
||||
Standard: Fixes Hyrule Castle Secret Entrance and Front Door
|
||||
but may lead to weird rain state issues if you exit
|
||||
through the Hyrule Castle side exits before rescuing
|
||||
Zelda in a full shuffle.
|
||||
Inverted: Starting locations are Dark Sanctuary in West Dark
|
||||
World or at Link's House, which is shuffled freely.
|
||||
Requires the moon pearl to be Link in the Light World
|
||||
instead of a bunny.
|
||||
''')
|
||||
parser.add_argument('--swords', default=defval('random'), const='random', nargs='?', choices= ['random', 'assured', 'swordless', 'vanilla'],
|
||||
help='''\
|
||||
Select sword placement. (default: %(default)s)
|
||||
Random: All swords placed randomly.
|
||||
Assured: Start game with a sword already.
|
||||
Swordless: No swords. Curtains in Skull Woods and Agahnim\'s
|
||||
Tower are removed, Agahnim\'s Tower barrier can be
|
||||
destroyed with hammer. Misery Mire and Turtle Rock
|
||||
can be opened without a sword. Hammer damages Ganon.
|
||||
Ether and Bombos Tablet can be activated with Hammer
|
||||
(and Book). Bombos pads have been added in Ice
|
||||
Palace, to allow for an alternative to firerod.
|
||||
Vanilla: Swords are in vanilla locations.
|
||||
''')
|
||||
parser.add_argument('--goal', default=defval('ganon'), 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('normal'), const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
||||
help='''\
|
||||
Select game difficulty. Affects available itempool. (default: %(default)s)
|
||||
Normal: Normal difficulty.
|
||||
Hard: A harder setting with less equipment and reduced health.
|
||||
Expert: A harder yet setting with minimum equipment and health.
|
||||
''')
|
||||
parser.add_argument('--item_functionality', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
||||
help='''\
|
||||
Select limits on item functionality to increase difficulty. (default: %(default)s)
|
||||
Normal: Normal functionality.
|
||||
Hard: Reduced functionality.
|
||||
Expert: Greatly reduced functionality.
|
||||
''')
|
||||
parser.add_argument('--timer', default=defval('none'), 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('on'), 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('balanced'), 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('vanilla'), 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('basic'), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed', 'experimental'],
|
||||
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.
|
||||
Experimental: Experimental mixes live here. Use at your own risk.
|
||||
''')
|
||||
parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
||||
help='''\
|
||||
How many crystals are needed to defeat ganon. Any other
|
||||
requirements for ganon for the selected goal still apply.
|
||||
This setting does not apply when the all dungeons goal is
|
||||
selected. (default: %(default)s)
|
||||
Random: Picks a random value between 0 and 7 (inclusive).
|
||||
0-7: Number of crystals needed
|
||||
''')
|
||||
parser.add_argument('--crystals_gt', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
||||
help='''\
|
||||
How many crystals are needed to open GT. For inverted mode
|
||||
this applies to the castle tower door instead. (default: %(default)s)
|
||||
Random: Picks a random value between 0 and 7 (inclusive).
|
||||
0-7: Number of crystals needed
|
||||
''')
|
||||
parser.add_argument('--openpyramid', default=defval(False), help='''\
|
||||
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it
|
||||
''', action='store_true')
|
||||
parser.add_argument('--rom', default=defval('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'), help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||
parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
||||
parser.add_argument('--seed', help='Define seed number to generate.', type=int)
|
||||
parser.add_argument('--count', help='''\
|
||||
Use to batch generate multiple seeds with same settings.
|
||||
If --seed is provided, it will be used for the first seed, then
|
||||
used to derive the next seed (i.e. generating 10 seeds with
|
||||
--seed given will produce the same 10 (different) roms each
|
||||
time).
|
||||
''', type=int)
|
||||
parser.add_argument('--fastmenu', default=defval('normal'), const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
||||
help='''\
|
||||
Select the rate at which the menu opens and closes.
|
||||
(default: %(default)s)
|
||||
''')
|
||||
parser.add_argument('--quickswap', default=defval(False), help='Enable quick item swapping with L and R.', action='store_true')
|
||||
parser.add_argument('--disablemusic', default=defval(False), help='Disables game music.', action='store_true')
|
||||
parser.add_argument('--mapshuffle', default=defval(False), help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--compassshuffle', default=defval(False), help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--keyshuffle', default=defval(False), help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--bigkeyshuffle', default=defval(False), help='Big Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
||||
parser.add_argument('--keysanity', default=defval(False), help=argparse.SUPPRESS, action='store_true')
|
||||
parser.add_argument('--retro', default=defval(False), 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(''), help='Specifies a list of items that will be in your starting inventory (separated by commas)')
|
||||
parser.add_argument('--custom', default=defval(False), help='Not supported.')
|
||||
parser.add_argument('--customitemarray', default=defval(False), help='Not supported.')
|
||||
parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
|
||||
Select Item/Location Accessibility. (default: %(default)s)
|
||||
Items: You can reach all unique inventory items. No guarantees about
|
||||
reaching all locations or all keys.
|
||||
Locations: You will be able to reach every location in the game.
|
||||
None: You will be able to reach enough locations to beat the game.
|
||||
''')
|
||||
parser.add_argument('--hints', default=defval(False), help='''\
|
||||
Make telepathic tiles and storytellers give helpful hints.
|
||||
''', action='store_true')
|
||||
# included for backwards compatibility
|
||||
parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=defval(True))
|
||||
parser.add_argument('--no-shuffleganon', help='''\
|
||||
If set, the Pyramid Hole and Ganon's Tower are not
|
||||
included entrance shuffle pool.
|
||||
''', action='store_false', dest='shuffleganon')
|
||||
parser.add_argument('--heartbeep', default=defval('normal'), const='normal', nargs='?', choices=['double', 'normal', 'half', 'quarter', 'off'],
|
||||
help='''\
|
||||
Select the rate at which the heart beep sound is played at
|
||||
low health. (default: %(default)s)
|
||||
''')
|
||||
parser.add_argument('--heartcolor', default=defval('red'), const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow', 'random'],
|
||||
help='Select the color of Link\'s heart meter. (default: %(default)s)')
|
||||
parser.add_argument('--ow_palettes', default=defval('default'), choices=['default', 'random', 'blackout'])
|
||||
parser.add_argument('--uw_palettes', default=defval('default'), choices=['default', 'random', 'blackout'])
|
||||
parser.add_argument('--sprite', help='''\
|
||||
Path to a sprite sheet to use for Link. Needs to be in
|
||||
binary format and have a length of 0x7000 (28672) bytes,
|
||||
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', 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(False))
|
||||
parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core'))
|
||||
parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos'])
|
||||
parser.add_argument('--shuffleenemies', default=defval('none'), choices=['none', 'shuffled', 'chaos'])
|
||||
parser.add_argument('--enemy_health', default=defval('default'), choices=['default', 'easy', 'normal', 'hard', 'expert'])
|
||||
parser.add_argument('--enemy_damage', default=defval('default'), choices=['default', 'shuffled', 'chaos'])
|
||||
parser.add_argument('--shufflepots', default=defval(False), action='store_true')
|
||||
parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4))
|
||||
parser.add_argument('--remote_items', default=defval(False), action='store_true')
|
||||
parser.add_argument('--multi', default=defval(1), type=lambda value: min(max(int(value), 1), 255))
|
||||
parser.add_argument('--names', default=defval(''))
|
||||
parser.add_argument('--teams', default=defval(1), type=lambda value: max(int(value), 1))
|
||||
parser.add_argument('--outputpath')
|
||||
parser.add_argument('--race', default=defval(False), action='store_true')
|
||||
parser.add_argument('--outputname')
|
||||
|
||||
if multiargs.multi:
|
||||
for player in range(1, multiargs.multi + 1):
|
||||
parser.add_argument(f'--p{player}', default=defval(''), help=argparse.SUPPRESS)
|
||||
|
||||
ret = parser.parse_args(argv)
|
||||
if ret.keysanity:
|
||||
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
|
||||
|
||||
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)
|
||||
|
||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||
'retro', 'accessibility', 'hints', 'beemizer',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||
'remote_items']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
else:
|
||||
getattr(ret, name)[player] = value
|
||||
|
||||
return ret
|
||||
|
||||
def start():
|
||||
args = parse_arguments(None)
|
||||
|
||||
@@ -359,7 +59,12 @@ def start():
|
||||
seed = random.randint(0, 999999999)
|
||||
for fail in failures:
|
||||
logger.info('%s seed failed with: %s', fail[1], fail[0])
|
||||
logger.info('Generation fail rate: %f%%', 100*len(failures)/args.count)
|
||||
fail_rate = 100 * len(failures) / args.count
|
||||
success_rate = 100 * (args.count - len(failures)) / args.count
|
||||
fail_rate = str(fail_rate).split('.')
|
||||
success_rate = str(success_rate).split('.')
|
||||
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)
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
console = True
|
||||
|
||||
def recurse_for_py_files(names_so_far):
|
||||
returnvalue = []
|
||||
for name in os.listdir(os.path.join(*names_so_far)):
|
||||
if name != "__pycache__":
|
||||
subdir_name = os.path.join(*names_so_far, name)
|
||||
if os.path.isdir(subdir_name):
|
||||
new_name_list = names_so_far + [name]
|
||||
for filename in os.listdir(os.path.join(*new_name_list)):
|
||||
base_file,file_extension = os.path.splitext(filename)
|
||||
if file_extension == ".py":
|
||||
new_name = ".".join(new_name_list+[base_file])
|
||||
if not new_name in returnvalue:
|
||||
returnvalue.append(new_name)
|
||||
returnvalue.extend(recurse_for_py_files(new_name_list))
|
||||
returnvalue.append("PIL._tkinter_finder") #Linux needs this
|
||||
return returnvalue
|
||||
|
||||
hiddenimports = []
|
||||
|
||||
a = Analysis(['DungeonRandomizer.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
|
||||
# https://stackoverflow.com/questions/17034434/how-to-remove-exclude-modules-and-files-from-pyinstaller
|
||||
excluded_binaries = [
|
||||
'VCRUNTIME140.dll',
|
||||
'msvcp140.dll',
|
||||
'mfc140u.dll']
|
||||
a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries])
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='DungeonRandomizer',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
runtime_tmpdir=None,
|
||||
console=console ) # <--- change this to True to enable command prompt when the app runs
|
||||
+40
-21
@@ -182,11 +182,12 @@ eastern_regions = [
|
||||
]
|
||||
|
||||
desert_regions = [
|
||||
'Desert Main Lobby', 'Desert Dead End', 'Desert East Lobby', 'Desert East Wing', 'Desert Compass Room',
|
||||
'Desert Cannonball', 'Desert Arrow Pot Corner', 'Desert Trap Room', 'Desert North Hall', 'Desert Map Room',
|
||||
'Desert Sandworm Corner', 'Desert Bonk Torch', 'Desert Circle of Pots', 'Desert Big Chest Room', 'Desert West Wing',
|
||||
'Desert West Lobby', 'Desert Fairy Fountain', 'Desert Back Lobby', 'Desert Tiles 1',
|
||||
'Desert Bridge', 'Desert Four Statues', 'Desert Beamos Hall', 'Desert Tiles 2', 'Desert Wall Slide', 'Desert Boss',
|
||||
'Desert Main Lobby', 'Desert Left Alcove', 'Desert Right Alcove', 'Desert Dead End', 'Desert East Lobby',
|
||||
'Desert East Wing', 'Desert Compass Room', 'Desert Cannonball', 'Desert Arrow Pot Corner', 'Desert Trap Room',
|
||||
'Desert North Hall', 'Desert Map Room', 'Desert Sandworm Corner', 'Desert Bonk Torch', 'Desert Circle of Pots',
|
||||
'Desert Big Chest Room', 'Desert West Wing', 'Desert West Lobby', 'Desert Fairy Fountain', 'Desert Back Lobby',
|
||||
'Desert Tiles 1', 'Desert Bridge', 'Desert Four Statues', 'Desert Beamos Hall', 'Desert Tiles 2',
|
||||
'Desert Wall Slide', 'Desert Boss'
|
||||
]
|
||||
|
||||
hera_regions = [
|
||||
@@ -203,12 +204,13 @@ tower_regions = [
|
||||
]
|
||||
|
||||
pod_regions = [
|
||||
'PoD Lobby', 'PoD Left Cage', 'PoD Middle Cage', 'PoD Shooter Room', 'PoD Pit Room', 'PoD Arena Main',
|
||||
'PoD Arena North', 'PoD Arena Crystal', 'PoD Arena Bridge', 'PoD Arena Ledge', 'PoD Sexy Statue', 'PoD Map Balcony',
|
||||
'PoD Conveyor', 'PoD Mimics 1', 'PoD Jelly Hall', 'PoD Warp Hint', 'PoD Warp Room', 'PoD Stalfos Basement',
|
||||
'PoD Basement Ledge', 'PoD Big Key Landing', 'PoD Falling Bridge', 'PoD Dark Maze', 'PoD Big Chest Balcony',
|
||||
'PoD Compass Room', 'PoD Dark Basement', 'PoD Harmless Hellway', 'PoD Mimics 2', 'PoD Bow Statue', 'PoD Dark Pegs',
|
||||
'PoD Lonely Turtle', 'PoD Turtle Party', 'PoD Dark Alley', 'PoD Callback', 'PoD Boss'
|
||||
'PoD Lobby', 'PoD Left Cage', 'PoD Middle Cage', 'PoD Shooter Room', 'PoD Pit Room', 'PoD Pit Room Blocked',
|
||||
'PoD Arena Main', 'PoD Arena North', 'PoD Arena Crystal', 'PoD Arena Bridge', 'PoD Arena Ledge', 'PoD Sexy Statue',
|
||||
'PoD Map Balcony', 'PoD Conveyor', 'PoD Mimics 1', 'PoD Jelly Hall', 'PoD Warp Hint', 'PoD Warp Room',
|
||||
'PoD Stalfos Basement', 'PoD Basement Ledge', 'PoD Big Key Landing', 'PoD Falling Bridge',
|
||||
'PoD Falling Bridge Ledge', 'PoD Dark Maze', 'PoD Big Chest Balcony', 'PoD Compass Room', 'PoD Dark Basement',
|
||||
'PoD Harmless Hellway', 'PoD Mimics 2', 'PoD Bow Statue', 'PoD Dark Pegs', 'PoD Lonely Turtle', 'PoD Turtle Party',
|
||||
'PoD Dark Alley', 'PoD Callback', 'PoD Boss'
|
||||
]
|
||||
|
||||
swamp_regions = [
|
||||
@@ -255,14 +257,15 @@ 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 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', 'Mire Block X', 'Mire Tall Dark and Roomy',
|
||||
'Mire Crystal Right', 'Mire Crystal Mid', 'Mire Crystal Left', 'Mire Crystal Top', 'Mire Shooter Rupees',
|
||||
'Mire Falling Foes', 'Mire Firesnake Skip', 'Mire Antechamber', 'Mire Boss'
|
||||
'Mire 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',
|
||||
'Mire Block X', 'Mire Tall Dark and Roomy', 'Mire Crystal Right', 'Mire Crystal Mid', 'Mire Crystal Left',
|
||||
'Mire Crystal Top', 'Mire Shooter Rupees', 'Mire Falling Foes', 'Mire Firesnake Skip', 'Mire Antechamber',
|
||||
'Mire Boss'
|
||||
]
|
||||
|
||||
tr_regions = [
|
||||
@@ -276,8 +279,8 @@ tr_regions = [
|
||||
|
||||
gt_regions = [
|
||||
'GT Lobby', 'GT Bob\'s Torch', 'GT Hope Room', 'GT Big Chest', 'GT Blocked Stairs', 'GT Bob\'s Room',
|
||||
'GT Tile Room', 'GT Speed Torch', 'GT Pots n Blocks', 'GT Crystal Conveyor', 'GT Compass Room',
|
||||
'GT Invisible Bridges', 'GT Invisible Catwalk', 'GT Conveyor Cross', 'GT Hookshot East Platform',
|
||||
'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor',
|
||||
'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', 'GT Conveyor Cross', 'GT Hookshot East Platform',
|
||||
'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Map Room',
|
||||
'GT Double Switch Entry', 'GT Double Switch Switches', 'GT Double Switch Transition', 'GT Double Switch Key Spot',
|
||||
'GT Double Switch Exit', 'GT Spike Crystals', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section',
|
||||
@@ -380,3 +383,19 @@ dungeon_bigs = {
|
||||
'Ganons Tower': 'Big Key (Ganons Tower)'
|
||||
}
|
||||
|
||||
dungeon_hints = {
|
||||
'Hyrule Castle': 'in Hyrule Castle',
|
||||
'Eastern Palace': 'in Eastern Palace',
|
||||
'Desert Palace': 'in Desert Palace',
|
||||
'Tower of Hera': 'in Tower of Hera',
|
||||
'Agahnims Tower': 'in Castle Tower',
|
||||
'Palace of Darkness': 'in Palace of Darkness',
|
||||
'Swamp Palace': 'in Swamp Palace)',
|
||||
'Skull Woods': 'in Skull Woods',
|
||||
'Thieves Town': 'in Thieves\' Town)',
|
||||
'Ice Palace': 'in Ice Palace',
|
||||
'Misery Mire': 'in Misery Mire',
|
||||
'Turtle Rock': 'in Turtle Rock',
|
||||
'Ganons Tower': 'in Ganon\'s Tower'
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -276,6 +276,8 @@ def link_entrances(world, player):
|
||||
if world.mode[player] == 'standard':
|
||||
# must connect front of hyrule castle to do escape
|
||||
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
|
||||
elif world.doorShuffle[player] != 'vanilla':
|
||||
lw_entrances.append('Hyrule Castle Entrance (South)')
|
||||
else:
|
||||
caves.append(tuple(random.sample(['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'],3)))
|
||||
lw_entrances.append('Hyrule Castle Entrance (South)')
|
||||
@@ -312,6 +314,10 @@ def link_entrances(world, player):
|
||||
if world.mode[player] == 'standard':
|
||||
# rest of hyrule castle must be in light world
|
||||
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
|
||||
# in full, Sanc must be in light world, so must all of HC if door shuffle is on
|
||||
elif world.doorShuffle[player] != 'vanilla':
|
||||
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')], player)
|
||||
|
||||
|
||||
# place old man, has limited options
|
||||
# exit has to come from specific set of doors, the entrance is free to move about
|
||||
@@ -2292,8 +2298,6 @@ Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
|
||||
'Death Mountain Return Cave (East)',
|
||||
'Death Mountain Return Cave (West)',
|
||||
'Spectacle Rock Cave Peak',
|
||||
'Spectacle Rock Cave',
|
||||
'Spectacle Rock Cave (Bottom)',
|
||||
'Paradox Cave (Bottom)',
|
||||
'Paradox Cave (Middle)',
|
||||
'Paradox Cave (Top)',
|
||||
|
||||
@@ -201,7 +201,8 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
|
||||
else:
|
||||
test_state = maximum_exploration_state
|
||||
if (not single_player_placement or location.player == item_to_place.player)\
|
||||
and location.can_fill(test_state, item_to_place, perform_access_check):
|
||||
and location.can_fill(test_state, item_to_place, perform_access_check)\
|
||||
and valid_key_placement(item_to_place, location, itempool, world):
|
||||
spot_to_fill = location
|
||||
break
|
||||
elif item_to_place.smallkey or item_to_place.bigkey:
|
||||
@@ -217,11 +218,42 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
|
||||
raise FillError('No more spots to place %s' % item_to_place)
|
||||
|
||||
world.push_item(spot_to_fill, item_to_place, False)
|
||||
track_outside_keys(item_to_place, spot_to_fill, world)
|
||||
locations.remove(spot_to_fill)
|
||||
spot_to_fill.event = True
|
||||
|
||||
itempool.extend(unplaced_items)
|
||||
|
||||
|
||||
def valid_key_placement(item, location, itempool, world):
|
||||
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player]:
|
||||
return True
|
||||
dungeon = location.parent_region.dungeon
|
||||
if dungeon:
|
||||
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
|
||||
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)
|
||||
else:
|
||||
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
|
||||
or (item.bigkey and not world.bigkeyshuffle[item.player]))
|
||||
return not inside_dungeon_item
|
||||
|
||||
|
||||
def track_outside_keys(item, location, world):
|
||||
if not item.smallkey:
|
||||
return
|
||||
item_dungeon = item.name.split('(')[1][:-1]
|
||||
if item_dungeon == 'Escape':
|
||||
item_dungeon = 'Hyrule Castle'
|
||||
if location.player == item.player:
|
||||
loc_dungeon = location.parent_region.dungeon
|
||||
if loc_dungeon and loc_dungeon.name == item_dungeon:
|
||||
return # this is an inside key
|
||||
world.key_logic[item.player][item_dungeon].outside_keys += 1
|
||||
|
||||
|
||||
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
|
||||
# If not passed in, then get a shuffled list of locations to fill in
|
||||
if not fill_locations:
|
||||
@@ -236,7 +268,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||
|
||||
# fill in gtower locations with trash first
|
||||
for player in range(1, world.players + 1):
|
||||
if not gftower_trash or not world.ganonstower_vanilla[player]:
|
||||
if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed':
|
||||
continue
|
||||
|
||||
gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15))
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
console = True
|
||||
|
||||
def recurse_for_py_files(names_so_far):
|
||||
returnvalue = []
|
||||
for name in os.listdir(os.path.join(*names_so_far)):
|
||||
if name != "__pycache__":
|
||||
subdir_name = os.path.join(*names_so_far, name)
|
||||
if os.path.isdir(subdir_name):
|
||||
new_name_list = names_so_far + [name]
|
||||
for filename in os.listdir(os.path.join(*new_name_list)):
|
||||
base_file,file_extension = os.path.splitext(filename)
|
||||
if file_extension == ".py":
|
||||
new_name = ".".join(new_name_list+[base_file])
|
||||
if not new_name in returnvalue:
|
||||
returnvalue.append(new_name)
|
||||
returnvalue.extend(recurse_for_py_files(new_name_list))
|
||||
returnvalue.append("PIL._tkinter_finder") #Linux needs this
|
||||
return returnvalue
|
||||
|
||||
hiddenimports = []
|
||||
|
||||
a = Analysis(['Gui.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
|
||||
# https://stackoverflow.com/questions/17034434/how-to-remove-exclude-modules-and-files-from-pyinstaller
|
||||
excluded_binaries = [
|
||||
'VCRUNTIME140.dll',
|
||||
'msvcp140.dll',
|
||||
'mfc140u.dll']
|
||||
a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries])
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='Gui',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
runtime_tmpdir=None,
|
||||
console=console ) # <--- change this to True to enable command prompt when the app runs
|
||||
+121
-100
@@ -9,6 +9,8 @@ from EntranceShuffle import connect_entrance
|
||||
from Fill import FillError, fill_restrictive
|
||||
from Items import ItemFactory
|
||||
|
||||
import 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.
|
||||
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
||||
@@ -58,7 +60,7 @@ difficulties = {
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
triforce_pieces_required = 20,
|
||||
retro = ['Small Key (Universal)'] * 17 + ['Rupees (20)'] * 10,
|
||||
retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10,
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 4,
|
||||
progressive_shield_limit = 3,
|
||||
@@ -85,7 +87,7 @@ difficulties = {
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
triforce_pieces_required = 20,
|
||||
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
||||
retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15,
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 3,
|
||||
progressive_shield_limit = 2,
|
||||
@@ -112,7 +114,7 @@ difficulties = {
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
triforce_pieces_required = 20,
|
||||
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
||||
retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15,
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 2,
|
||||
progressive_shield_limit = 1,
|
||||
@@ -124,6 +126,58 @@ difficulties = {
|
||||
),
|
||||
}
|
||||
|
||||
def get_custom_array_key(item):
|
||||
label_switcher = {
|
||||
"silverarrow": "silversupgrade",
|
||||
"blueboomerang": "boomerang",
|
||||
"redboomerang": "redmerang",
|
||||
"ocarina": "flute",
|
||||
"bugcatchingnet": "bugnet",
|
||||
"bookofmudora": "book",
|
||||
"pegasusboots": "boots",
|
||||
"titansmitts": "titansmitt",
|
||||
"pieceofheart": "heartpiece",
|
||||
"bossheartcontainer": "heartcontainer",
|
||||
"sanctuaryheartcontainer": "sancheart",
|
||||
"mastersword": "sword2",
|
||||
"temperedsword": "sword3",
|
||||
"goldensword": "sword4",
|
||||
"blueshield": "shield1",
|
||||
"redshield": "shield2",
|
||||
"mirrorshield": "shield3",
|
||||
"bluemail": "mail2",
|
||||
"redmail": "mail3",
|
||||
"progressivearmor": "progressivemail",
|
||||
"splus12": "halfmagic",
|
||||
"splus14": "quartermagic",
|
||||
"singlearrow": "arrow1",
|
||||
"singlebomb": "bomb1",
|
||||
"triforcepiece": "triforcepieces"
|
||||
}
|
||||
key = item.lower()
|
||||
trans = {
|
||||
" ": "",
|
||||
'(': "",
|
||||
'/': "",
|
||||
')': "",
|
||||
'+': "",
|
||||
"magic": "",
|
||||
"caneof": "",
|
||||
"upgrade": "splus",
|
||||
"arrows": "arrow",
|
||||
"arrowplus": "arrowsplus",
|
||||
"bombs": "bomb",
|
||||
"bombplus": "bombsplus",
|
||||
"rupees": "rupee"
|
||||
}
|
||||
for check in trans:
|
||||
repl = trans[check]
|
||||
key = key.replace(check,repl)
|
||||
if key in label_switcher:
|
||||
key = label_switcher.get(key)
|
||||
return key
|
||||
|
||||
|
||||
def generate_itempool(world, player):
|
||||
if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals']
|
||||
or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
|
||||
@@ -200,13 +254,21 @@ def generate_itempool(world, player):
|
||||
world.get_location('Zelda Drop Off', player).event = True
|
||||
world.get_location('Zelda Drop Off', player).locked = True
|
||||
|
||||
|
||||
# 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[69], 9999)
|
||||
world.rupoor_cost = min(world.customitemarray["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])
|
||||
(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):
|
||||
pool.remove('Rupees (20)')
|
||||
elif amt > 0:
|
||||
for i in range(0, amt):
|
||||
pool.append('Rupees (20)')
|
||||
|
||||
for item in precollected_items:
|
||||
world.push_precollected(ItemFactory(item, player))
|
||||
@@ -259,9 +321,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[30] == 0):
|
||||
if world.difficulty[player] in ['normal', 'hard'] and not (world.custom and world.customitemarray["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[29] < 4):
|
||||
elif world.difficulty[player] in ['expert'] and not (world.custom and world.customitemarray["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
|
||||
@@ -345,6 +407,7 @@ def set_up_take_anys(world, player):
|
||||
|
||||
world.initialize_regions()
|
||||
|
||||
|
||||
def create_dynamic_shop_locations(world, player):
|
||||
for shop in world.shops:
|
||||
if shop.region.player == player:
|
||||
@@ -397,8 +460,10 @@ def set_up_shops(world, player):
|
||||
if world.retro[player]:
|
||||
rss = world.get_region('Red Shield Shop', player).shop
|
||||
if not rss.locked:
|
||||
rss.custom = True
|
||||
rss.add_inventory(2, 'Single Arrow', 80)
|
||||
for shop in random.sample([s for s in world.shops if s.custom and not s.locked and s.region.player == player], 5):
|
||||
for shop in random.sample([s for s in world.shops if not s.locked and s.region.player == player], 5):
|
||||
shop.custom = True
|
||||
shop.locked = True
|
||||
shop.add_inventory(0, 'Single Arrow', 80)
|
||||
shop.add_inventory(1, 'Small Key (Universal)', 100)
|
||||
@@ -406,7 +471,7 @@ def set_up_shops(world, player):
|
||||
rss.locked = True
|
||||
|
||||
|
||||
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro):
|
||||
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, door_shuffle):
|
||||
pool = []
|
||||
placed_items = {}
|
||||
precollected_items = []
|
||||
@@ -525,8 +590,11 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
||||
pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool]
|
||||
pool.extend(diff.retro)
|
||||
if mode == 'standard':
|
||||
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||
place_item(key_location, 'Small Key (Universal)')
|
||||
if door_shuffle == 'vanilla':
|
||||
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||
place_item(key_location, 'Small Key (Universal)')
|
||||
else:
|
||||
pool.extend(['Small Key (Universal)'])
|
||||
else:
|
||||
pool.extend(['Small Key (Universal)'])
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
@@ -544,80 +612,31 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
||||
placed_items[loc] = item
|
||||
|
||||
# Correct for insanely oversized item counts and take initial steps to handle undersized pools.
|
||||
for x in range(0, 66):
|
||||
if customitemarray[x] > total_items_to_place:
|
||||
customitemarray[x] = total_items_to_place
|
||||
if customitemarray[68] > total_items_to_place:
|
||||
customitemarray[68] = total_items_to_place
|
||||
itemtotal = 0
|
||||
for x in range(0, 66):
|
||||
itemtotal = itemtotal + customitemarray[x]
|
||||
itemtotal = itemtotal + customitemarray[68]
|
||||
itemtotal = itemtotal + customitemarray[70]
|
||||
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
|
||||
for x in [*range(0, 66 + 1), 68, 69]:
|
||||
key = CONST.CUSTOMITEMS[x]
|
||||
if customitemarray[key] > total_items_to_place:
|
||||
customitemarray[key] = total_items_to_place
|
||||
|
||||
pool.extend(['Bow'] * customitemarray[0])
|
||||
pool.extend(['Silver Arrows']* customitemarray[1])
|
||||
pool.extend(['Blue Boomerang'] * customitemarray[2])
|
||||
pool.extend(['Red Boomerang'] * customitemarray[3])
|
||||
pool.extend(['Hookshot'] * customitemarray[4])
|
||||
pool.extend(['Mushroom'] * customitemarray[5])
|
||||
pool.extend(['Magic Powder'] * customitemarray[6])
|
||||
pool.extend(['Fire Rod'] * customitemarray[7])
|
||||
pool.extend(['Ice Rod'] * customitemarray[8])
|
||||
pool.extend(['Bombos'] * customitemarray[9])
|
||||
pool.extend(['Ether'] * customitemarray[10])
|
||||
pool.extend(['Quake'] * customitemarray[11])
|
||||
pool.extend(['Lamp'] * customitemarray[12])
|
||||
pool.extend(['Hammer'] * customitemarray[13])
|
||||
pool.extend(['Shovel'] * customitemarray[14])
|
||||
pool.extend(['Ocarina'] * customitemarray[15])
|
||||
pool.extend(['Bug Catching Net'] * customitemarray[16])
|
||||
pool.extend(['Book of Mudora'] * customitemarray[17])
|
||||
pool.extend(['Cane of Somaria'] * customitemarray[19])
|
||||
pool.extend(['Cane of Byrna'] * customitemarray[20])
|
||||
pool.extend(['Cape'] * customitemarray[21])
|
||||
pool.extend(['Pegasus Boots'] * customitemarray[23])
|
||||
pool.extend(['Power Glove'] * customitemarray[24])
|
||||
pool.extend(['Titans Mitts'] * customitemarray[25])
|
||||
pool.extend(['Progressive Glove'] * customitemarray[26])
|
||||
pool.extend(['Flippers'] * customitemarray[27])
|
||||
pool.extend(['Piece of Heart'] * customitemarray[29])
|
||||
pool.extend(['Boss Heart Container'] * customitemarray[30])
|
||||
pool.extend(['Sanctuary Heart Container'] * customitemarray[31])
|
||||
pool.extend(['Master Sword'] * customitemarray[33])
|
||||
pool.extend(['Tempered Sword'] * customitemarray[34])
|
||||
pool.extend(['Golden Sword'] * customitemarray[35])
|
||||
pool.extend(['Blue Shield'] * customitemarray[37])
|
||||
pool.extend(['Red Shield'] * customitemarray[38])
|
||||
pool.extend(['Mirror Shield'] * customitemarray[39])
|
||||
pool.extend(['Progressive Shield'] * customitemarray[40])
|
||||
pool.extend(['Blue Mail'] * customitemarray[41])
|
||||
pool.extend(['Red Mail'] * customitemarray[42])
|
||||
pool.extend(['Progressive Armor'] * customitemarray[43])
|
||||
pool.extend(['Magic Upgrade (1/2)'] * customitemarray[44])
|
||||
pool.extend(['Magic Upgrade (1/4)'] * customitemarray[45])
|
||||
pool.extend(['Bomb Upgrade (+5)'] * customitemarray[46])
|
||||
pool.extend(['Bomb Upgrade (+10)'] * customitemarray[47])
|
||||
pool.extend(['Arrow Upgrade (+5)'] * customitemarray[48])
|
||||
pool.extend(['Arrow Upgrade (+10)'] * customitemarray[49])
|
||||
pool.extend(['Single Arrow'] * customitemarray[50])
|
||||
pool.extend(['Arrows (10)'] * customitemarray[51])
|
||||
pool.extend(['Single Bomb'] * customitemarray[52])
|
||||
pool.extend(['Bombs (3)'] * customitemarray[53])
|
||||
pool.extend(['Rupee (1)'] * customitemarray[54])
|
||||
pool.extend(['Rupees (5)'] * customitemarray[55])
|
||||
pool.extend(['Rupees (20)'] * customitemarray[56])
|
||||
pool.extend(['Rupees (50)'] * customitemarray[57])
|
||||
pool.extend(['Rupees (100)'] * customitemarray[58])
|
||||
pool.extend(['Rupees (300)'] * customitemarray[59])
|
||||
pool.extend(['Rupoor'] * customitemarray[60])
|
||||
pool.extend(['Blue Clock'] * customitemarray[61])
|
||||
pool.extend(['Green Clock'] * customitemarray[62])
|
||||
pool.extend(['Red Clock'] * customitemarray[63])
|
||||
pool.extend(['Progressive Bow'] * customitemarray[64])
|
||||
pool.extend(['Bombs (10)'] * customitemarray[65])
|
||||
pool.extend(['Triforce Piece'] * customitemarray[66])
|
||||
pool.extend(['Triforce'] * customitemarray[68])
|
||||
# Triforce
|
||||
if customitemarray["triforce"] > total_items_to_place:
|
||||
customitemarray["triforce"] = total_items_to_place
|
||||
|
||||
itemtotal = 0
|
||||
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
|
||||
for x in [*range(0, 66 + 1), 68, 69]:
|
||||
key = CONST.CUSTOMITEMS[x]
|
||||
itemtotal = itemtotal + customitemarray[key]
|
||||
# Triforce
|
||||
itemtotal = itemtotal + customitemarray["triforce"]
|
||||
# Generic Keys
|
||||
itemtotal = itemtotal + customitemarray["generickeys"]
|
||||
|
||||
customitems = [
|
||||
"Bow", "Silver Arrows", "Blue Boomerang", "Red Boomerang", "Hookshot", "Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos", "Ether", "Quake", "Lamp", "Hammer", "Shovel", "Ocarina", "Bug Catching Net", "Book of Mudora", "Cane of Somaria", "Cane of Byrna", "Cape", "Pegasus Boots", "Power Glove", "Titans Mitts", "Progressive Glove", "Flippers", "Piece of Heart", "Boss Heart Container", "Sanctuary Heart Container", "Master Sword", "Tempered Sword", "Golden Sword", "Blue Shield", "Red Shield", "Mirror Shield", "Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)", "Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)", "Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)", "Rupoor", "Blue Clock", "Green Clock", "Red Clock", "Progressive Bow", "Bombs (10)", "Triforce Piece", "Triforce"
|
||||
]
|
||||
for customitem in customitems:
|
||||
pool.extend([customitem] * customitemarray[get_custom_array_key(customitem)])
|
||||
|
||||
diff = difficulties[difficulty]
|
||||
|
||||
@@ -627,17 +646,17 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
||||
# all bottles, since only one bottle is available
|
||||
if diff.same_bottle:
|
||||
thisbottle = random.choice(diff.bottles)
|
||||
for _ in range(customitemarray[18]):
|
||||
for _ in range(customitemarray["bottle"]):
|
||||
if not diff.same_bottle:
|
||||
thisbottle = random.choice(diff.bottles)
|
||||
pool.append(thisbottle)
|
||||
|
||||
if customitemarray[66] > 0 or customitemarray[67] > 0:
|
||||
treasure_hunt_count = max(min(customitemarray[67], 99), 1) #To display, count must be between 1 and 99.
|
||||
if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0:
|
||||
treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], 99), 1) #To display, count must be between 1 and 99.
|
||||
treasure_hunt_icon = 'Triforce Piece'
|
||||
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
||||
if (customitemarray[66] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray[68] == 0):
|
||||
extrapieces = treasure_hunt_count - customitemarray[66]
|
||||
if (customitemarray["triforcepieces"] < treasure_hunt_count) and (goal == 'triforcehunt') and (customitemarray["triforce"] == 0):
|
||||
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
|
||||
pool.extend(['Triforce Piece'] * extrapieces)
|
||||
itemtotal = itemtotal + extrapieces
|
||||
|
||||
@@ -656,28 +675,30 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
||||
if retro:
|
||||
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||
place_item(key_location, 'Small Key (Universal)')
|
||||
pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0))
|
||||
pool.extend(['Small Key (Universal)'] * max((customitemarray["generickeys"] - 1), 0))
|
||||
else:
|
||||
pool.extend(['Small Key (Universal)'] * customitemarray[70])
|
||||
pool.extend(['Small Key (Universal)'] * customitemarray["generickeys"])
|
||||
else:
|
||||
pool.extend(['Small Key (Universal)'] * customitemarray[70])
|
||||
pool.extend(['Small Key (Universal)'] * customitemarray["generickeys"])
|
||||
|
||||
pool.extend(['Fighter Sword'] * customitemarray[32])
|
||||
pool.extend(['Progressive Sword'] * customitemarray[36])
|
||||
pool.extend(['Fighter Sword'] * customitemarray["sword1"])
|
||||
pool.extend(['Progressive Sword'] * customitemarray["progressivesword"])
|
||||
|
||||
if shuffle == 'insanity_legacy':
|
||||
place_item('Link\'s House', 'Magic Mirror')
|
||||
place_item('Sanctuary', 'Moon Pearl')
|
||||
pool.extend(['Magic Mirror'] * max((customitemarray[22] -1 ), 0))
|
||||
pool.extend(['Moon Pearl'] * max((customitemarray[28] - 1), 0))
|
||||
pool.extend(['Magic Mirror'] * max((customitemarray["mirror"] -1 ), 0))
|
||||
pool.extend(['Moon Pearl'] * max((customitemarray["pearl"] - 1), 0))
|
||||
else:
|
||||
pool.extend(['Magic Mirror'] * customitemarray[22])
|
||||
pool.extend(['Moon Pearl'] * customitemarray[28])
|
||||
pool.extend(['Magic Mirror'] * customitemarray["mirror"])
|
||||
pool.extend(['Moon Pearl'] * customitemarray["pearl"])
|
||||
|
||||
if retro:
|
||||
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode
|
||||
if itemtotal < total_items_to_place:
|
||||
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
|
||||
nothings = total_items_to_place - itemtotal
|
||||
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)
|
||||
|
||||
|
||||
+240
-21
@@ -21,6 +21,7 @@ class KeyLayout(object):
|
||||
self.max_drops = None
|
||||
self.all_chest_locations = {}
|
||||
self.big_key_special = False
|
||||
self.all_locations = set()
|
||||
|
||||
# bk special?
|
||||
# bk required? True if big chests or big doors exists
|
||||
@@ -44,6 +45,14 @@ class KeyLogic(object):
|
||||
self.bk_chests = set()
|
||||
self.logic_min = {}
|
||||
self.logic_max = {}
|
||||
self.placement_rules = []
|
||||
self.outside_keys = 0
|
||||
|
||||
def check_placement(self, unplaced_keys):
|
||||
for rule in self.placement_rules:
|
||||
if not rule.is_satisfiable(self.outside_keys, unplaced_keys):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class DoorRules(object):
|
||||
@@ -59,6 +68,38 @@ class DoorRules(object):
|
||||
self.small_location = None
|
||||
|
||||
|
||||
class PlacementRule(object):
|
||||
|
||||
def __init__(self):
|
||||
self.door_reference = None
|
||||
self.small_key = None
|
||||
self.bk_conditional_set = None # the location that means
|
||||
self.needed_keys_w_bk = None
|
||||
self.needed_keys_wo_bk = None
|
||||
self.check_locations_w_bk = None
|
||||
self.check_locations_wo_bk = None
|
||||
|
||||
def is_satisfiable(self, outside_keys, unplaced_keys):
|
||||
bk_blocked = False
|
||||
if self.bk_conditional_set:
|
||||
for loc in self.bk_conditional_set:
|
||||
if loc.item and loc.item.bigkey:
|
||||
bk_blocked = True
|
||||
break
|
||||
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:
|
||||
empty_chests += 1
|
||||
elif loc.item and loc.item.name == self.small_key:
|
||||
available_keys += 1
|
||||
place_able_keys = min(empty_chests, unplaced_keys)
|
||||
available_keys += place_able_keys
|
||||
return available_keys >= threshold
|
||||
|
||||
|
||||
class KeyCounter(object):
|
||||
|
||||
def __init__(self, max_chests):
|
||||
@@ -106,8 +147,10 @@ def analyze_dungeon(key_layout, world, player):
|
||||
key_layout.key_counters = create_key_counters(key_layout, world, player)
|
||||
key_logic = key_layout.key_logic
|
||||
|
||||
find_bk_locked_sections(key_layout, world)
|
||||
find_bk_locked_sections(key_layout, world, player)
|
||||
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
|
||||
if world.retro[player] and world.mode[player] != 'standard':
|
||||
return
|
||||
|
||||
original_key_counter = find_counter({}, False, key_layout)
|
||||
queue = deque([(None, original_key_counter)])
|
||||
@@ -147,13 +190,15 @@ def analyze_dungeon(key_layout, world, player):
|
||||
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)
|
||||
if not rule.is_valid:
|
||||
logging.getLogger('').warning('Key logic for door %s requires too many chests. Seed may be beatable anyway.', child.name)
|
||||
# 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)
|
||||
@@ -176,6 +221,65 @@ def analyze_dungeon(key_layout, world, player):
|
||||
rule.small_key_num, rule.alternate_small_key = rule.alternate_small_key, rule.small_key_num
|
||||
|
||||
|
||||
def create_placement_rule(key_layout, door, odd_ctr, current_ctr, 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))
|
||||
important_found = False
|
||||
for loc in blocked_others:
|
||||
if important_location(loc, world, player):
|
||||
important_found = True
|
||||
break
|
||||
if not important_found:
|
||||
rule.needed_keys_w_bk -= 1
|
||||
|
||||
|
||||
def count_key_drops(sector):
|
||||
cnt = 0
|
||||
@@ -200,16 +304,18 @@ def queue_sorter_2(queue_item):
|
||||
return 1 if door.bigKey else 0
|
||||
|
||||
|
||||
def find_bk_locked_sections(key_layout, world):
|
||||
def find_bk_locked_sections(key_layout, world, player):
|
||||
if key_layout.big_key_special:
|
||||
return
|
||||
key_counters = key_layout.key_counters
|
||||
key_logic = key_layout.key_logic
|
||||
|
||||
bk_key_not_required = set()
|
||||
big_chest_allowed_big_key = world.accessibility != 'locations'
|
||||
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)
|
||||
if counter.big_key_opened and counter.important_location:
|
||||
big_chest_allowed_big_key = False
|
||||
if not counter.big_key_opened:
|
||||
@@ -240,6 +346,28 @@ def relative_empty_counter(odd_counter, key_counter):
|
||||
return True
|
||||
|
||||
|
||||
def relative_empty_counter_2(odd_counter, key_counter):
|
||||
if len(set(odd_counter.key_only_locations).difference(key_counter.key_only_locations)) > 0:
|
||||
return False
|
||||
if len(set(odd_counter.free_locations).difference(key_counter.free_locations)) > 0:
|
||||
return False
|
||||
for child in odd_counter.child_doors:
|
||||
if unique_child_door_2(child, key_counter):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def progressive_ctr(new_counter, last_counter):
|
||||
if len(set(new_counter.key_only_locations).difference(last_counter.key_only_locations)) > 0:
|
||||
return True
|
||||
if len(set(new_counter.free_locations).difference(last_counter.free_locations)) > 0:
|
||||
return True
|
||||
for child in new_counter.child_doors:
|
||||
if unique_child_door_2(child, last_counter):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def unique_child_door(child, key_counter):
|
||||
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
|
||||
return False
|
||||
@@ -250,6 +378,14 @@ def unique_child_door(child, key_counter):
|
||||
return True
|
||||
|
||||
|
||||
def unique_child_door_2(child, key_counter):
|
||||
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
|
||||
return False
|
||||
if child in key_counter.open_doors or child.dest in key_counter.child_doors:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def find_best_counter(door, odd_counter, key_counter, key_layout, world, player, skip_bk, empty_flag): # try to waste as many keys as possible?
|
||||
ignored_doors = {door, door.dest} if door is not None else {}
|
||||
finished = False
|
||||
@@ -279,7 +415,34 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, player,
|
||||
return last_counter
|
||||
|
||||
|
||||
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
||||
def find_worst_counter(door, odd_counter, key_counter, key_layout, skip_bk): # try to waste as many keys as possible?
|
||||
ignored_doors = {door, door.dest} if door is not None else {}
|
||||
finished = False
|
||||
opened_doors = dict(key_counter.open_doors)
|
||||
bk_opened = key_counter.big_key_opened
|
||||
# new_counter = key_counter
|
||||
last_counter = key_counter
|
||||
while not finished:
|
||||
door_set = find_potential_open_doors(last_counter, ignored_doors, key_layout, skip_bk, 0)
|
||||
if door_set is None or len(door_set) == 0:
|
||||
finished = True
|
||||
continue
|
||||
for new_door in door_set:
|
||||
proposed_doors = {**opened_doors, **dict.fromkeys([new_door, new_door.dest])}
|
||||
bk_open = bk_opened or new_door.bigKey
|
||||
new_counter = find_counter(proposed_doors, bk_open, key_layout)
|
||||
bk_open = new_counter.big_key_opened
|
||||
if not new_door.bigKey and progressive_ctr(new_counter, last_counter) and relative_empty_counter_2(odd_counter, new_counter):
|
||||
ignored_doors.add(new_door)
|
||||
else:
|
||||
last_counter = new_counter
|
||||
opened_doors = proposed_doors
|
||||
bk_opened = bk_open
|
||||
# this means the new_door invalidates the door / leads to the same stuff
|
||||
return last_counter
|
||||
|
||||
|
||||
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk, reserve=1):
|
||||
small_doors = []
|
||||
big_doors = []
|
||||
for other in key_counter.child_doors:
|
||||
@@ -292,7 +455,7 @@ def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
||||
if key_layout.big_key_special:
|
||||
big_key_available = key_counter.big_key_opened
|
||||
else:
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 0
|
||||
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
|
||||
return None
|
||||
return small_doors + big_doors
|
||||
@@ -366,7 +529,7 @@ def create_rule(key_counter, prev_counter, key_layout, world, player):
|
||||
|
||||
|
||||
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player):
|
||||
if world.accessibility != 'locations':
|
||||
if world.accessibility[player] != 'locations':
|
||||
counter = find_inverted_counter(door, parent_counter, key_layout, world, player)
|
||||
if not self_lock_possible(counter):
|
||||
return
|
||||
@@ -488,6 +651,27 @@ def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_la
|
||||
# key_layout.key_logic.bk_restricted.update(unique_loc)
|
||||
|
||||
|
||||
def find_worst_counter_wo_bk(small_key_num, accessible_set, door, odd_ctr, key_counter, key_layout):
|
||||
if key_counter.big_key_opened:
|
||||
return None, None, None
|
||||
worst_counter = find_worst_counter(door, odd_ctr, key_counter, key_layout, True)
|
||||
bk_rule_num = worst_counter.used_keys + 1
|
||||
bk_access_set = set()
|
||||
bk_access_set.update(worst_counter.free_locations)
|
||||
bk_access_set.update(worst_counter.key_only_locations)
|
||||
if bk_rule_num == small_key_num and len(bk_access_set ^ accessible_set) == 0:
|
||||
return None, None, None
|
||||
door_open = find_next_counter(door, worst_counter, key_layout)
|
||||
ignored_doors = dict_intersection(worst_counter.child_doors, door_open.child_doors)
|
||||
dest_ignored = []
|
||||
for door in ignored_doors.keys():
|
||||
if door.dest not in ignored_doors:
|
||||
dest_ignored.append(door.dest)
|
||||
ignored_doors = {**ignored_doors, **dict.fromkeys(dest_ignored)}
|
||||
post_counter = open_some_counter(door_open, key_layout, ignored_doors.keys())
|
||||
return worst_counter, post_counter, bk_rule_num
|
||||
|
||||
|
||||
def open_a_door(door, child_state, flat_proposal):
|
||||
if door.bigKey:
|
||||
child_state.big_key_opened = True
|
||||
@@ -521,7 +705,7 @@ def unique_doors(doors):
|
||||
def count_unique_sm_doors(doors):
|
||||
unique_d_set = set()
|
||||
for d in doors:
|
||||
if d not in unique_d_set and d.dest not in unique_d_set and not d.bigKey:
|
||||
if d not in unique_d_set and (d.dest not in unique_d_set or d.type == DoorType.SpiralStairs) and not d.bigKey:
|
||||
unique_d_set.add(d)
|
||||
return len(unique_d_set)
|
||||
|
||||
@@ -534,7 +718,8 @@ def count_unique_small_doors(key_counter, proposal):
|
||||
if door in proposal and door not in counted:
|
||||
cnt += 1
|
||||
counted.add(door)
|
||||
counted.add(door.dest)
|
||||
if door.type != DoorType.SpiralStairs:
|
||||
counted.add(door.dest)
|
||||
return cnt
|
||||
|
||||
|
||||
@@ -806,7 +991,8 @@ def reduce_rules(small_rules, collected, collected_alt):
|
||||
|
||||
# Soft lock stuff
|
||||
def validate_key_layout(key_layout, world, player):
|
||||
if world.retro[player]: # retro is all good - don't care how the doors are laid out
|
||||
# retro is all good - except for hyrule castle in standard mode
|
||||
if world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle'):
|
||||
return True
|
||||
flat_proposal = key_layout.flat_prop
|
||||
state = ExplorationState(dungeon=key_layout.sector.name)
|
||||
@@ -815,7 +1001,7 @@ def validate_key_layout(key_layout, world, player):
|
||||
for region in key_layout.start_regions:
|
||||
state.visit_region(region, key_checks=True)
|
||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||
return validate_key_layout_sub_loop(key_layout, state, {}, flat_proposal, None, None, world, player)
|
||||
return validate_key_layout_sub_loop(key_layout, state, {}, flat_proposal, None, 0, world, player)
|
||||
|
||||
|
||||
def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposal, prev_state, prev_avail, world, player):
|
||||
@@ -831,6 +1017,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
||||
if invalid_self_locking_key(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):
|
||||
if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||
return False
|
||||
else:
|
||||
@@ -882,15 +1070,6 @@ def invalid_self_locking_key(state, prev_state, prev_avail, world, player):
|
||||
return prev_avail - 1 == 0
|
||||
|
||||
|
||||
# does not allow dest doors
|
||||
def count_unique_sm_doors(doors):
|
||||
unique_d_set = set()
|
||||
for d in doors:
|
||||
if d not in unique_d_set and d.dest not in unique_d_set and not d.bigKey:
|
||||
unique_d_set.add(d)
|
||||
return len(unique_d_set)
|
||||
|
||||
|
||||
def enough_small_locations(state, avail_small_loc):
|
||||
unique_d_set = set()
|
||||
for exp_door in state.small_doors:
|
||||
@@ -1232,3 +1411,43 @@ def val_rule(rule, skn, allow=False, loc=None, askn=None, setCheck=None):
|
||||
assert len(setCheck) == len(rule.alternate_big_key_loc)
|
||||
for loc in rule.alternate_big_key_loc:
|
||||
assert loc.name in setCheck
|
||||
|
||||
|
||||
# Soft lock stuff
|
||||
def validate_key_placement(key_layout, world, player):
|
||||
if world.retro[player] or world.accessibility[player] == 'none':
|
||||
return True # Can't keylock in retro. Expected if beatable only.
|
||||
max_counter = find_max_counter(key_layout)
|
||||
keys_outside = 0
|
||||
big_key_outside = False
|
||||
dungeon = world.get_dungeon(key_layout.sector.name, player)
|
||||
smallkey_name = 'Small Key (%s)' % (key_layout.sector.name if key_layout.sector.name != 'Hyrule Castle' else 'Escape')
|
||||
if world.keyshuffle[player]:
|
||||
keys_outside = key_layout.max_chests - sum(1 for i in max_counter.free_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player)
|
||||
if world.bigkeyshuffle[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():
|
||||
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
|
||||
if counter.big_key_opened and not big_found:
|
||||
continue # Can't get to this state
|
||||
found_locations = set(i for i in counter.free_locations if big_found or "- Big Chest" not in i.name)
|
||||
found_keys = sum(1 for i in found_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player) + \
|
||||
len(counter.key_only_locations) + keys_outside
|
||||
can_progress = (not counter.big_key_opened and big_found and any(d.bigKey for d in counter.child_doors)) or \
|
||||
found_keys > counter.used_keys and any(not d.bigKey for d in counter.child_doors)
|
||||
if not can_progress:
|
||||
missing_locations = set(max_counter.free_locations.keys()).difference(found_locations)
|
||||
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name]
|
||||
# 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: ")
|
||||
for i in missing_locations:
|
||||
logging.getLogger('').error(i)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import zlib
|
||||
|
||||
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
||||
from Items import ItemFactory
|
||||
from KeyDoorShuffle import validate_key_placement
|
||||
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions
|
||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||
@@ -23,7 +24,7 @@ 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.12pre'
|
||||
__version__ = '0.0.18.2d'
|
||||
|
||||
|
||||
def main(args, seed=None):
|
||||
@@ -56,6 +57,8 @@ def main(args, seed=None):
|
||||
world.enemy_health = args.enemy_health.copy()
|
||||
world.enemy_damage = args.enemy_damage.copy()
|
||||
world.beemizer = args.beemizer.copy()
|
||||
world.experimental = args.experimental.copy()
|
||||
world.dungeon_counters = args.dungeon_counters.copy()
|
||||
|
||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||
|
||||
@@ -132,6 +135,11 @@ def main(args, seed=None):
|
||||
else:
|
||||
fill_dungeons(world)
|
||||
|
||||
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))
|
||||
|
||||
logger.info('Fill the world.')
|
||||
|
||||
if args.algorithm == 'flood':
|
||||
@@ -177,9 +185,13 @@ def main(args, seed=None):
|
||||
patch_rom(world, rom, player, team, use_enemizer)
|
||||
|
||||
if use_enemizer and (args.enemizercli or not args.jsonout):
|
||||
patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit)
|
||||
if not args.jsonout:
|
||||
rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000)
|
||||
if os.path.exists(args.enemizercli):
|
||||
patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit)
|
||||
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.")
|
||||
|
||||
if args.race:
|
||||
patch_race_rom(rom)
|
||||
|
||||
+2
-1
@@ -148,6 +148,7 @@ def roll_settings(weights):
|
||||
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
|
||||
door_shuffle = get_choice('door_shuffle')
|
||||
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
||||
ret.experimental = get_choice('experimental') == 'on'
|
||||
|
||||
goal = get_choice('goals')
|
||||
ret.goal = {'ganon': 'ganon',
|
||||
@@ -156,7 +157,7 @@ def roll_settings(weights):
|
||||
'pedestal': 'pedestal',
|
||||
'triforce-hunt': 'triforcehunt'
|
||||
}[goal]
|
||||
ret.openpyramid = goal == 'fast_ganon'
|
||||
ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False
|
||||
|
||||
ret.crystals_gt = get_choice('tower_open')
|
||||
|
||||
|
||||
@@ -36,10 +36,6 @@ Doors are shuffled between dungeons as well.
|
||||
|
||||
Doors are not shuffled.
|
||||
|
||||
### Experimental
|
||||
|
||||
Used for development testing. This will be removed in a future version. Use at your own risk. Might play like a plando.
|
||||
|
||||
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)
|
||||
|
||||
These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon.
|
||||
|
||||
+20
-15
@@ -277,8 +277,9 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'Eastern Boss', 'Eastern Palace', ['Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Boss SE']),
|
||||
|
||||
# Desert Palace
|
||||
# note for later: pots in desert keep out the bunny - logically (though not practically b/c of dungeon bunny revival)
|
||||
create_dungeon_region(player, 'Desert Main Lobby', 'Desert Palace', None, ['Desert Palace Exit (South)','Desert Main Lobby NW Edge', 'Desert Main Lobby N Edge', 'Desert Main Lobby NE Edge', 'Desert Main Lobby E Edge']),
|
||||
create_dungeon_region(player, 'Desert Main Lobby', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Main Lobby N Edge', 'Desert Main Lobby Left Path', 'Desert Main Lobby Right Path']),
|
||||
create_dungeon_region(player, 'Desert Left Alcove', 'Desert Palace', None, ['Desert Main Lobby NW Edge', 'Desert Left Alcove Path']),
|
||||
create_dungeon_region(player, 'Desert Right Alcove', 'Desert Palace', None, ['Desert Main Lobby NE Edge', 'Desert Main Lobby E Edge', 'Desert Right Alcove Path']),
|
||||
create_dungeon_region(player, 'Desert Dead End', 'Desert Palace', None, ['Desert Dead End Edge']),
|
||||
create_dungeon_region(player, 'Desert East Lobby', 'Desert Palace', None, ['Desert East Lobby WS', 'Desert Palace Exit (East)']),
|
||||
create_dungeon_region(player, 'Desert East Wing', 'Desert Palace', None, ['Desert East Wing ES', 'Desert East Wing Key Door EN', 'Desert East Wing W Edge', 'Desert East Wing N Edge']),
|
||||
@@ -344,7 +345,8 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'PoD Left Cage', 'Palace of Darkness', None, ['PoD Left Cage SW', 'PoD Left Cage Down Stairs']),
|
||||
create_dungeon_region(player, 'PoD Middle Cage', 'Palace of Darkness', None, ['PoD Middle Cage S', 'PoD Middle Cage SE', 'PoD Middle Cage N', 'PoD Middle Cage Down Stairs']),
|
||||
create_dungeon_region(player, 'PoD Shooter Room', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['PoD Shooter Room Up Stairs']),
|
||||
create_dungeon_region(player, 'PoD Pit Room', 'Palace of Darkness', None, ['PoD Pit Room S', 'PoD Pit Room NW', 'PoD Pit Room NE', 'PoD Pit Room Freefall', 'PoD Pit Room Bomb Hole']),
|
||||
create_dungeon_region(player, 'PoD Pit Room', 'Palace of Darkness', None, ['PoD Pit Room S', 'PoD Pit Room NW', 'PoD Pit Room Bomb Hole', 'PoD Pit Room Block Path N']),
|
||||
create_dungeon_region(player, 'PoD Pit Room Blocked', 'Palace of Darkness', None, ['PoD Pit Room NE', 'PoD Pit Room Freefall', 'PoD Pit Room Block Path S']),
|
||||
create_dungeon_region(player, 'PoD Arena Main', 'Palace of Darkness', None, ['PoD Arena Main SW', 'PoD Arena Main Crystal Path', 'PoD Arena Main Orange Barrier', 'PoD Arena Bonk Path']),
|
||||
create_dungeon_region(player, 'PoD Arena North', 'Palace of Darkness', None, ['PoD Arena Main NW', 'PoD Arena Main NE', 'PoD Arena North Drop Down']),
|
||||
create_dungeon_region(player, 'PoD Arena Crystal', 'Palace of Darkness', None, ['PoD Arena Crystals E', 'PoD Arena Crystal Path']),
|
||||
@@ -360,7 +362,8 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'PoD Stalfos Basement', 'Palace of Darkness', ['Palace of Darkness - Stalfos Basement'], ['PoD Stalfos Basement Warp']),
|
||||
create_dungeon_region(player, 'PoD Basement Ledge', 'Palace of Darkness', None, ['PoD Basement Ledge Drop Down', 'PoD Basement Ledge Up Stairs']),
|
||||
create_dungeon_region(player, 'PoD Big Key Landing', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest'], ['PoD Big Key Landing Down Stairs', 'PoD Big Key Landing Hole']),
|
||||
create_dungeon_region(player, 'PoD Falling Bridge', 'Palace of Darkness', None, ['PoD Falling Bridge SW', 'PoD Falling Bridge WN', 'PoD Falling Bridge EN']),
|
||||
create_dungeon_region(player, 'PoD Falling Bridge Ledge', 'Palace of Darkness', None, ['PoD Falling Bridge WN', 'PoD Falling Bridge EN', 'PoD Falling Bridge Path S']),
|
||||
create_dungeon_region(player, 'PoD Falling Bridge', 'Palace of Darkness', None, ['PoD Falling Bridge SW', 'PoD Falling Bridge Path N']),
|
||||
create_dungeon_region(player, 'PoD Dark Maze', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom'], ['PoD Dark Maze EN', 'PoD Dark Maze E']),
|
||||
create_dungeon_region(player, 'PoD Big Chest Balcony', 'Palace of Darkness', ['Palace of Darkness - Big Chest'], ['PoD Big Chest Balcony W']),
|
||||
create_dungeon_region(player, 'PoD Compass Room', 'Palace of Darkness', ['Palace of Darkness - Compass Chest'], ['PoD Compass Room SE', 'PoD Compass Room WN', 'PoD Compass Room W Down Stairs', 'PoD Compass Room E Down Stairs']),
|
||||
@@ -544,7 +547,8 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'Mire Map Spike Side', 'Misery Mire', None, ['Mire Map Spike Side EN', 'Mire Map Spike Side Drop Down', 'Mire Map Spike Side Blue Barrier']),
|
||||
create_dungeon_region(player, 'Mire Map Spot', 'Misery Mire', ['Misery Mire - Map Chest'], ['Mire Map Spot WN', 'Mire Map Spot Blue Barrier']),
|
||||
create_dungeon_region(player, 'Mire Crystal Dead End', 'Misery Mire', None, ['Mire Crystal Dead End Left Barrier', 'Mire Crystal Dead End Right Barrier', 'Mire Crystal Dead End NW']),
|
||||
create_dungeon_region(player, 'Mire Hidden Shooters', 'Misery Mire', None, ['Mire Hidden Shooters SE', 'Mire Hidden Shooters WS', 'Mire Hidden Shooters ES', 'Mire Hidden Shooters NE']),
|
||||
create_dungeon_region(player, 'Mire Hidden Shooters', 'Misery Mire', None, ['Mire Hidden Shooters SE', 'Mire Hidden Shooters WS', 'Mire Hidden Shooters ES', 'Mire Hidden Shooters Block Path N']),
|
||||
create_dungeon_region(player, 'Mire Hidden Shooters Blocked', 'Misery Mire', None, ['Mire Hidden Shooters NE', 'Mire Hidden Shooters Block Path S']),
|
||||
create_dungeon_region(player, 'Mire Cross', 'Misery Mire', None, ['Mire Cross ES', 'Mire Cross SW']),
|
||||
create_dungeon_region(player, 'Mire Minibridge', 'Misery Mire', None, ['Mire Minibridge SE', 'Mire Minibridge NE']),
|
||||
create_dungeon_region(player, 'Mire BK Door Room', 'Misery Mire', None, ['Mire BK Door Room EN', 'Mire BK Door Room N']),
|
||||
@@ -633,7 +637,8 @@ def create_dungeon_regions(world, player):
|
||||
create_dungeon_region(player, 'GT Blocked Stairs', 'Ganon\'s Tower', None, ['GT Blocked Stairs Down Stairs', 'GT Blocked Stairs Block Path']),
|
||||
create_dungeon_region(player, 'GT Bob\'s Room', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest'], ['GT Bob\'s Room SE', 'GT Bob\'s Room Hole']),
|
||||
create_dungeon_region(player, 'GT Tile Room', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['GT Tile Room WN', 'GT Tile Room EN']),
|
||||
create_dungeon_region(player, 'GT Speed Torch', 'Ganon\'s Tower', None, ['GT Speed Torch WN', 'GT Speed Torch NE', 'GT Speed Torch WS', 'GT Speed Torch SE']),
|
||||
create_dungeon_region(player, 'GT Speed Torch', 'Ganon\'s Tower', None, ['GT Speed Torch WS', 'GT Speed Torch SE', 'GT Speed Torch North Path']),
|
||||
create_dungeon_region(player, 'GT Speed Torch Upper', 'Ganon\'s Tower', None, ['GT Speed Torch WN', 'GT Speed Torch NE', 'GT Speed Torch South Path']),
|
||||
create_dungeon_region(player, 'GT Pots n Blocks', 'Ganon\'s Tower', None, ['GT Pots n Blocks ES']),
|
||||
create_dungeon_region(player, 'GT Crystal Conveyor', 'Ganon\'s Tower', None, ['GT Crystal Conveyor NE', 'GT Crystal Conveyor WN']),
|
||||
create_dungeon_region(player, 'GT Compass Room', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right'],
|
||||
@@ -826,15 +831,15 @@ def create_shops(world, player):
|
||||
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
||||
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||
shop_table = {
|
||||
'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults),
|
||||
'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, True, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
|
||||
'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||
'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||
'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||
'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||
'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, False, False, _basic_shop_defaults),
|
||||
'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, False, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
|
||||
'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults),
|
||||
'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults),
|
||||
'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults),
|
||||
'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, False, False, _dark_world_shop_defaults),
|
||||
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults),
|
||||
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults),
|
||||
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, False, False, _basic_shop_defaults),
|
||||
'Potion Shop': (0x0109, ShopType.Shop, 0xFF, False, True, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
||||
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = 'c06e14396839bc443a6918e736f1e5a7'
|
||||
RANDOMIZERBASEHASH = '5e01caffabb4509a0987ef2f2f0bcd56'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -591,7 +591,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
patch_shuffled_dark_sanc(world, rom, player)
|
||||
|
||||
# patch doors
|
||||
dr_flags = DROptions.Eternal_Mini_Bosses
|
||||
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)
|
||||
rom.write_byte(0x151f1, 2)
|
||||
@@ -618,6 +618,8 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW':
|
||||
dr_flags |= DROptions.Open_Desert_Wall
|
||||
rom.write_byte(0x139006, dr_flags.value)
|
||||
if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted':
|
||||
rom.write_byte(0x139008, 1)
|
||||
|
||||
# fix skull woods exit, if not fixed during exit patching
|
||||
if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla':
|
||||
@@ -979,8 +981,8 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
startingstate = CollectionState(world)
|
||||
|
||||
if startingstate.has('Bow', player):
|
||||
equip[0x340] = 1
|
||||
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
|
||||
equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
|
||||
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
|
||||
if not world.retro[player]:
|
||||
equip[0x38E] |= 0x80
|
||||
if startingstate.has('Silver Arrows', player):
|
||||
@@ -1155,9 +1157,11 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld
|
||||
|
||||
# compasses showing dungeon count
|
||||
if world.clock_mode != 'off':
|
||||
if world.clock_mode != 'off' or world.dungeon_counters[player] == 'off':
|
||||
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
|
||||
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla':
|
||||
elif world.dungeon_counters[player] == 'on':
|
||||
rom.write_byte(0x18003C, 0x02) # always on
|
||||
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup':
|
||||
rom.write_byte(0x18003C, 0x01) # show on pickup
|
||||
else:
|
||||
rom.write_byte(0x18003C, 0x00)
|
||||
@@ -1676,10 +1680,13 @@ def write_strings(rom, world, player, team):
|
||||
|
||||
# Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable.
|
||||
locations_to_hint = InconvenientLocations.copy()
|
||||
if world.doorShuffle[player] != 'crossed':
|
||||
locations_to_hint.extend(InconvenientDungeonLocations)
|
||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
locations_to_hint.extend(InconvenientVanillaLocations)
|
||||
random.shuffle(locations_to_hint)
|
||||
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5
|
||||
hint_count -= 2 if world.doorShuffle[player] == 'crossed' else 0
|
||||
del locations_to_hint[hint_count:]
|
||||
for location in locations_to_hint:
|
||||
if location == 'Swamp Left':
|
||||
@@ -1733,20 +1740,17 @@ def write_strings(rom, world, player, team):
|
||||
items_to_hint.extend(BigKeys)
|
||||
random.shuffle(items_to_hint)
|
||||
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
|
||||
hint_count += 2 if world.doorShuffle[player] == 'crossed' else 0
|
||||
while hint_count > 0:
|
||||
this_item = items_to_hint.pop(0)
|
||||
this_location = world.find_items_not_key_only(this_item, player)
|
||||
random.shuffle(this_location)
|
||||
#This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool.
|
||||
if this_location:
|
||||
if this_location[0].name == 'Skull Woods - Pinball Room':
|
||||
this_location.pop(0)
|
||||
if this_location:
|
||||
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
|
||||
tt[hint_locations.pop(0)] = this_hint
|
||||
hint_count -= 1
|
||||
|
||||
# Adding a hint for the Thieves' Town Attic location in Crossed Doorshufle.
|
||||
# Adding a hint for the Thieves' Town Attic location in Crossed door shuffle.
|
||||
if world.doorShuffle[player] in ['crossed']:
|
||||
attic_hint = world.get_location("Thieves' Town - Attic", player).parent_region.dungeon.name
|
||||
this_hint = 'A cracked floor can be found in ' + attic_hint + '.'
|
||||
@@ -2300,7 +2304,7 @@ HintLocations = ['telepathic_tile_eastern_palace',
|
||||
'telepathic_tile_castle_tower',
|
||||
'telepathic_tile_ice_large_room',
|
||||
'telepathic_tile_turtle_rock',
|
||||
'telepathic_tile_ice_entrace',
|
||||
'telepathic_tile_ice_entrance',
|
||||
'telepathic_tile_ice_stalfos_knights_room',
|
||||
'telepathic_tile_tower_of_hera_entrance',
|
||||
'telepathic_tile_south_east_darkworld_cave',
|
||||
@@ -2313,15 +2317,16 @@ HintLocations = ['telepathic_tile_eastern_palace',
|
||||
InconvenientLocations = ['Spike Cave',
|
||||
'Sahasrahla',
|
||||
'Purple Chest',
|
||||
'Swamp Left',
|
||||
'Mire Left',
|
||||
'Tower of Hera - Big Key Chest',
|
||||
'Eastern Palace - Big Key Chest',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Ice Palace - Big Chest',
|
||||
'Ganons Tower - Big Chest',
|
||||
'Magic Bat']
|
||||
|
||||
InconvenientDungeonLocations = ['Swamp Left',
|
||||
'Mire Left',
|
||||
'Eastern Palace - Big Key Chest',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Ice Palace - Big Chest',
|
||||
'Ganons Tower - Big Chest']
|
||||
|
||||
InconvenientVanillaLocations = ['Graveyard Cave',
|
||||
'Mimic Cave']
|
||||
|
||||
|
||||
+5
-1
@@ -251,6 +251,9 @@ class Room(object):
|
||||
self.doorList = []
|
||||
self.modified = False
|
||||
|
||||
def kind(self, door):
|
||||
return self.doorList[door.doorListPos][1]
|
||||
|
||||
def door(self, pos, kind):
|
||||
self.doorList.append((pos, kind))
|
||||
return self
|
||||
@@ -299,10 +302,11 @@ class Room(object):
|
||||
|
||||
|
||||
class PairedDoor(object):
|
||||
def __init__(self, door_a, door_b):
|
||||
def __init__(self, door_a, door_b, original=False):
|
||||
self.door_a = door_a
|
||||
self.door_b = door_b
|
||||
self.pair = True
|
||||
self.original = original
|
||||
|
||||
def address_a(self, world, player):
|
||||
d = world.check_for_door(self.door_a, player)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import collections
|
||||
import logging
|
||||
from BaseClasses import CollectionState
|
||||
from BaseClasses import CollectionState, RegionType, DoorType
|
||||
from Regions import key_only_locations
|
||||
from RoomData import DoorKind
|
||||
from collections import deque
|
||||
|
||||
|
||||
def set_rules(world, player):
|
||||
@@ -168,6 +169,7 @@ def global_rules(world, player):
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player))
|
||||
|
||||
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
|
||||
|
||||
set_rule(world.get_entrance('PoD Arena Bonk Path', player), lambda state: state.has_Boots(player))
|
||||
set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
|
||||
@@ -302,7 +304,18 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('GT Mimics 2 WS', player), lambda state: state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('GT Mimics 2 NE', player), lambda state: state.can_shoot_arrows(player))
|
||||
# consider access to refill room
|
||||
# consider can_kill_most_things to gauntlet
|
||||
set_rule(world.get_entrance('GT Gauntlet 1 WN', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 2 EN', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 2 SW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 3 NW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 3 SW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 4 NW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 4 SW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 5 NW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Gauntlet 5 WS', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Wizzrobes 1 SW', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Wizzrobes 2 SE', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Wizzrobes 2 NE', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
||||
@@ -688,63 +701,59 @@ def no_glitches_rules(world, player):
|
||||
if (not world.dark_world_light_cone and check_is_dark_world(world.get_region(region, player))) or (not world.light_world_light_cone and not check_is_dark_world(world.get_region(region, player))):
|
||||
add_lamp_requirement(spot, player)
|
||||
|
||||
add_conditional_lamp('TR Dark Ride Up Stairs', 'TR Dark Ride', 'Entrance')
|
||||
add_conditional_lamp('TR Dark Ride SW', 'TR Dark Ride', 'Entrance')
|
||||
add_conditional_lamp('Mire Dark Shooters Up Stairs', 'Mire Dark Shooters', 'Entrance')
|
||||
add_conditional_lamp('Mire Dark Shooters SW', 'Mire Dark Shooters', 'Entrance')
|
||||
add_conditional_lamp('Mire Dark Shooters SE', 'Mire Dark Shooters', 'Entrance')
|
||||
add_conditional_lamp('Mire Key Rupees NE', 'Mire Key Rupees', 'Entrance')
|
||||
add_conditional_lamp('Mire Block X NW', 'Mire Block X', 'Entrance')
|
||||
add_conditional_lamp('Mire Block X WS', 'Mire Block X', 'Entrance')
|
||||
add_conditional_lamp('Mire Tall Dark and Roomy ES', 'Mire Tall Dark and Roomy', 'Entrance')
|
||||
add_conditional_lamp('Mire Tall Dark and Roomy WS', 'Mire Tall Dark and Roomy', 'Entrance')
|
||||
add_conditional_lamp('Mire Tall Dark and Roomy WN', 'Mire Tall Dark and Roomy', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Right ES', 'Mire Crystal Right', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Mid NW', 'Mire Crystal Mid', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Left WS', 'Mire Crystal Left', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Top SW', 'Mire Crystal Top', 'Entrance')
|
||||
add_conditional_lamp('Mire Shooter Rupees EN', 'Mire Shooter Rupees', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Alley NE', 'PoD Dark Alley', 'Entrance')
|
||||
add_conditional_lamp('PoD Callback WS', 'PoD Callback', 'Entrance')
|
||||
add_conditional_lamp('PoD Callback Warp', 'PoD Callback', 'Entrance')
|
||||
add_conditional_lamp('PoD Turtle Party ES', 'PoD Turtle Party', 'Entrance')
|
||||
add_conditional_lamp('PoD Turtle Party NW', 'PoD Turtle Party', 'Entrance')
|
||||
add_conditional_lamp('PoD Lonely Turtle SW', 'PoD Lonely Turtle', 'Entrance')
|
||||
add_conditional_lamp('PoD Lonely Turtle EN', 'PoD Lonely Turtle', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Pegs Up Ladder', 'PoD Dark Pegs', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Pegs WN', 'PoD Dark Pegs', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Basement W Up Stairs', 'PoD Dark Basement', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Basement E Up Stairs', 'PoD Dark Basement', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Maze EN', 'PoD Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Maze E', 'PoD Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Basement - Left', 'PoD Dark Basement', 'Location')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'PoD Dark Basement', 'Location')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Maze - Top', 'PoD Dark Maze', 'Location')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Maze - Bottom', 'PoD Dark Maze', 'Location')
|
||||
add_conditional_lamp('Eastern Dark Square NW', 'Eastern Dark Square', 'Entrance')
|
||||
add_conditional_lamp('Eastern Dark Square Key Door WN', 'Eastern Dark Square', 'Entrance')
|
||||
add_conditional_lamp('Eastern Dark Square EN', 'Eastern Dark Square', 'Entrance')
|
||||
add_conditional_lamp('Eastern Dark Pots WN', 'Eastern Dark Pots', 'Entrance')
|
||||
add_conditional_lamp('Eastern Darkness S', 'Eastern Darkness', 'Entrance')
|
||||
add_conditional_lamp('Eastern Darkness Up Stairs', 'Eastern Darkness', 'Entrance')
|
||||
add_conditional_lamp('Eastern Darkness NE', 'Eastern Darkness', 'Entrance')
|
||||
add_conditional_lamp('Eastern Rupees SE', 'Eastern Rupees', 'Entrance')
|
||||
add_conditional_lamp('Eastern Palace - Dark Square Pot Key', 'Eastern Dark Square', 'Location')
|
||||
add_conditional_lamp('Eastern Palace - Dark Eyegore Key Drop', 'Eastern Darkness', 'Location')
|
||||
add_conditional_lamp('Tower Lone Statue Down Stairs', 'Tower Lone Statue', 'Entrance')
|
||||
add_conditional_lamp('Tower Lone Statue WN', 'Tower Lone Statue', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Maze EN', 'Tower Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Maze ES', 'Tower Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Chargers WS', 'Tower Dark Chargers', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Chargers Up Stairs', 'Tower Dark Chargers', 'Entrance')
|
||||
add_conditional_lamp('Tower Dual Statues Down Stairs', 'Tower Dual Statues', 'Entrance')
|
||||
add_conditional_lamp('Tower Dual Statues WS', 'Tower Dual Statues', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Pits ES', 'Tower Dark Pits', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Pits EN', 'Tower Dark Pits', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Archers WN', 'Tower Dark Archers', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Archers Up Stairs', 'Tower Dark Archers', 'Entrance')
|
||||
add_conditional_lamp('Castle Tower - Dark Maze', 'Tower Dark Maze', 'Location')
|
||||
add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Tower Dark Archers', 'Location')
|
||||
dark_rooms = {
|
||||
'TR Dark Ride': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride SW'], 'locations': []},
|
||||
'Mire Dark Shooters': {'sewer': False, 'entrances': ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE'], 'locations': []},
|
||||
'Mire Key Rupees': {'sewer': False, 'entrances': ['Mire Key Rupees NE'], 'locations': []},
|
||||
'Mire Block X': {'sewer': False, 'entrances': ['Mire Block X NW', 'Mire Block X WS'], 'locations': []},
|
||||
'Mire Tall Dark and Roomy': {'sewer': False, 'entrances': ['Mire Tall Dark and Roomy ES', 'Mire Tall Dark and Roomy WS', 'Mire Tall Dark and Roomy WN'], 'locations': []},
|
||||
'Mire Crystal Right': {'sewer': False, 'entrances': ['Mire Crystal Right ES'], 'locations': []},
|
||||
'Mire Crystal Mid': {'sewer': False, 'entrances': ['Mire Crystal Mid NW'], 'locations': []},
|
||||
'Mire Crystal Left': {'sewer': False, 'entrances': ['Mire Crystal Left WS'], 'locations': []},
|
||||
'Mire Crystal Top': {'sewer': False, 'entrances': ['Mire Crystal Top SW'], 'locations': []},
|
||||
'Mire Shooter Rupees': {'sewer': False, 'entrances': ['Mire Shooter Rupees EN'], 'locations': []},
|
||||
'PoD Dark Alley': {'sewer': False, 'entrances': ['PoD Dark Alley NE'], 'locations': []},
|
||||
'PoD Callback': {'sewer': False, 'entrances': ['PoD Callback WS', 'PoD Callback Warp'], 'locations': []},
|
||||
'PoD Turtle Party': {'sewer': False, 'entrances': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], 'locations': []},
|
||||
'PoD Lonely Turtle': {'sewer': False, 'entrances': ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN'], 'locations': []},
|
||||
'PoD Dark Pegs': {'sewer': False, 'entrances': ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs WN'], 'locations': []},
|
||||
'PoD Dark Basement': {'sewer': False, 'entrances': ['PoD Dark Basement W Up Stairs', 'PoD Dark Basement E Up Stairs'], 'locations': ['Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right']},
|
||||
'PoD Dark Maze': {'sewer': False, 'entrances': ['PoD Dark Maze EN', 'PoD Dark Maze E'], 'locations': ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom']},
|
||||
'Eastern Dark Square': {'sewer': False, 'entrances': ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN', 'Eastern Dark Square EN'], 'locations': []},
|
||||
'Eastern Dark Pots': {'sewer': False, 'entrances': ['Eastern Dark Pots WN'], 'locations': ['Eastern Palace - Dark Square Pot Key']},
|
||||
'Eastern Darkness': {'sewer': False, 'entrances': ['Eastern Darkness S', 'Eastern Darkness Up Stairs', 'Eastern Darkness NE'], 'locations': ['Eastern Palace - Dark Eyegore Key Drop']},
|
||||
'Eastern Rupees': {'sewer': False, 'entrances': ['Eastern Rupees SE'], 'locations': []},
|
||||
'Tower Lone Statue': {'sewer': False, 'entrances': ['Tower Lone Statue Down Stairs', 'Tower Lone Statue WN'], 'locations': []},
|
||||
'Tower Dark Maze': {'sewer': False, 'entrances': ['Tower Dark Maze EN', 'Tower Dark Maze ES'], 'locations': ['Castle Tower - Dark Maze']},
|
||||
'Tower Dark Chargers': {'sewer': False, 'entrances': ['Tower Dark Chargers WS', 'Tower Dark Chargers Up Stairs'], 'locations': []},
|
||||
'Tower Dual Statues': {'sewer': False, 'entrances': ['Tower Dual Statues Down Stairs', 'Tower Dual Statues WS'], 'locations': []},
|
||||
'Tower Dark Pits': {'sewer': False, 'entrances': ['Tower Dark Pits ES', 'Tower Dark Pits EN'], 'locations': []},
|
||||
'Tower Dark Archers': {'sewer': False, 'entrances': ['Tower Dark Archers WN', 'Tower Dark Archers Up Stairs'], 'locations': ['Castle Tower - Dark Archer Key Drop']},
|
||||
'Sewers Dark Cross': {'sewer': True, 'entrances': ['Sewers Dark Cross Key Door N', 'Sewers Dark Cross South Stairs'], 'locations': ['Sewers - Dark Cross']},
|
||||
'Sewers Behind Tapestry': {'sewer': True, 'entrances': ['Sewers Behind Tapestry S', 'Sewers Behind Tapestry Down Stairs'], 'locations': []},
|
||||
'Sewers Rope Room': {'sewer': True, 'entrances': ['Sewers Rope Room Up Stairs', 'Sewers Rope Room North Stairs'], 'locations': []},
|
||||
'Sewers Water': {'sewer': True, 'entrances': ['Sewers Dark Cross Key Door S', 'Sewers Water W'], 'locations': []},
|
||||
'Sewers Key Rat': {'sewer': True, 'entrances': ['Sewers Key Rat E', 'Sewers Key Rat Key Door N'], 'locations': ['Hyrule Castle - Key Rat Key Drop']},
|
||||
}
|
||||
|
||||
dark_debug_set = set()
|
||||
for region, info in dark_rooms.items():
|
||||
is_dark = False
|
||||
if not world.sewer_light_cone[player]:
|
||||
is_dark = True
|
||||
elif world.doorShuffle[player] != 'crossed' and not info['sewer']:
|
||||
is_dark = True
|
||||
elif world.doorShuffle[player] == 'crossed':
|
||||
sewer_builder = world.dungeon_layouts[player]['Hyrule Castle']
|
||||
is_dark = region not in sewer_builder.master_sector.region_set()
|
||||
if is_dark:
|
||||
dark_debug_set.add(region)
|
||||
for ent in info['entrances']:
|
||||
add_conditional_lamp(ent, region, 'Entrance')
|
||||
for loc in info['locations']:
|
||||
add_conditional_lamp(loc, region, 'Location')
|
||||
logging.getLogger('').debug('Non Dark Regions: ' + ', '.join(set(dark_rooms.keys()).difference(dark_debug_set)))
|
||||
|
||||
add_conditional_lamp('Old Man', 'Old Man Cave', 'Location')
|
||||
add_conditional_lamp('Old Man Cave Exit (East)', 'Old Man Cave', 'Entrance')
|
||||
add_conditional_lamp('Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave', 'Entrance')
|
||||
@@ -752,19 +761,6 @@ def no_glitches_rules(world, player):
|
||||
add_conditional_lamp('Old Man House Front to Back', 'Old Man House', 'Entrance')
|
||||
add_conditional_lamp('Old Man House Back to Front', 'Old Man House', 'Entrance')
|
||||
|
||||
if not world.sewer_light_cone[player]:
|
||||
add_lamp_requirement(world.get_location('Sewers - Dark Cross', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Behind Tapestry S', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Behind Tapestry Down Stairs', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Rope Room Up Stairs', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Rope Room North Stairs', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Dark Cross South Stairs', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Dark Cross Key Door N', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Dark Cross Key Door S', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Water W', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Key Rat E', player), player)
|
||||
add_lamp_requirement(world.get_entrance('Sewers Key Rat Key Door N', player), player)
|
||||
|
||||
|
||||
def open_rules(world, player):
|
||||
# softlock protection as you can reach the sewers small key door with a guard drop key
|
||||
@@ -795,10 +791,42 @@ def swordless_rules(world, player):
|
||||
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
|
||||
|
||||
|
||||
std_kill_rooms = {
|
||||
'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S'],
|
||||
'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'],
|
||||
'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'],
|
||||
'Desert Compass Room': ['Desert Compass NW'],
|
||||
'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'],
|
||||
'Hera Beetles': ['Hera Beetles WS'],
|
||||
'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'],
|
||||
'Tower Dark Archers': ['Tower Dark Archers WN'],
|
||||
'Tower Red Spears': ['Tower Red Spears WN'],
|
||||
'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'],
|
||||
'Tower Circle of Pots': ['Tower Circle of Pots NW'],
|
||||
'PoD Turtle Party': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], # todo: hammer req. in main rules
|
||||
'Thieves Basement Block': ['Thieves Basement Block WN'],
|
||||
'Ice Stalfos Hint': ['Ice Stalfos Hint SE'],
|
||||
'Ice Pengator Trap': ['Ice Pengator Trap NE'],
|
||||
'Mire 2': ['Mire 2 NE'],
|
||||
'Mire Cross': ['Mire Cross ES'],
|
||||
'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'],
|
||||
'GT Petting Zoo': ['GT Petting Zoo SE'],
|
||||
'GT DMs Room': ['GT DMs Room SW'],
|
||||
'GT Gauntlet 1': ['GT Gauntlet 1 WN'],
|
||||
'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'],
|
||||
'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'],
|
||||
'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'],
|
||||
'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'],
|
||||
'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'],
|
||||
'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE']
|
||||
} # all trap rooms?
|
||||
|
||||
|
||||
def standard_rules(world, player):
|
||||
# these are because of rails
|
||||
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
|
||||
set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.has('Zelda Delivered', player))
|
||||
if world.shuffle[player] != 'vanilla':
|
||||
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
|
||||
set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.has('Zelda Delivered', player))
|
||||
|
||||
# too restrictive for crossed?
|
||||
def uncle_item_rule(item):
|
||||
@@ -815,21 +843,21 @@ def standard_rules(world, player):
|
||||
add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player))
|
||||
add_rule(world.get_location('Secret Passage', player), lambda state: state.can_kill_most_things(player))
|
||||
|
||||
# todo: in crossed these chest/key drops are not necessarily present
|
||||
add_rule(world.get_location('Hyrule Castle - Map Chest', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Sewers - Dark Cross', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.can_kill_most_things(player))
|
||||
escape_builder = world.dungeon_layouts[player]['Hyrule Castle']
|
||||
for region in escape_builder.master_sector.regions:
|
||||
for loc in region.locations:
|
||||
add_rule(loc, lambda state: state.can_kill_most_things(player))
|
||||
if region.name in std_kill_rooms:
|
||||
for ent in std_kill_rooms[region.name]:
|
||||
add_rule(world.get_entrance(ent, player), lambda state: state.can_kill_most_things(player))
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Hyrule Castle - Key Rat Key Drop', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_entrance('Hyrule Dungeon Armory S', player), lambda state: state.can_kill_most_things(player))
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
|
||||
set_rule(world.get_entrance('Hyrule Castle Throne Room N', player), lambda state: state.has('Zelda Herself', player))
|
||||
set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player))
|
||||
|
||||
def check_rule_list(state, r_list):
|
||||
return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:])
|
||||
rule_list, debug_path = find_rules_for_zelda_delivery(world, player)
|
||||
set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list))
|
||||
|
||||
for location in ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest']:
|
||||
add_rule(world.get_location(location, player), lambda state: state.has('Zelda Delivered', player))
|
||||
@@ -853,6 +881,31 @@ def standard_rules(world, player):
|
||||
add_rule(world.get_entrance(entrance, player), lambda state: state.has('Zelda Delivered', player))
|
||||
|
||||
|
||||
def find_rules_for_zelda_delivery(world, player):
|
||||
# path rules for backtracking
|
||||
start_region = world.get_region('Hyrule Dungeon Cellblock', player)
|
||||
queue = deque([(start_region, [], [])])
|
||||
visited = {start_region}
|
||||
blank_state = CollectionState(world)
|
||||
while len(queue) > 0:
|
||||
region, path_rules, path = queue.popleft()
|
||||
for ext in region.exits:
|
||||
connect = ext.connected_region
|
||||
if connect and connect.type == RegionType.Dungeon and connect not in visited:
|
||||
rule = ext.access_rule
|
||||
rule_list = list(path_rules)
|
||||
next_path = list(path)
|
||||
if not rule(blank_state):
|
||||
rule_list.append(rule)
|
||||
next_path.append(ext.name)
|
||||
if connect.name == 'Sanctuary':
|
||||
return rule_list, next_path
|
||||
else:
|
||||
visited.add(connect)
|
||||
queue.append((connect, rule_list, next_path))
|
||||
raise Exception('No path to Sanctuary found')
|
||||
|
||||
|
||||
def set_big_bomb_rules(world, player):
|
||||
# this is a mess
|
||||
bombshop_entrance = world.get_region('Big Bomb Shop', player).entrances[0]
|
||||
@@ -1132,14 +1185,13 @@ def set_inverted_big_bomb_rules(world, player):
|
||||
'Hyrule Castle Entrance (East)',
|
||||
'Inverted Ganons Tower',
|
||||
'Cave 45',
|
||||
'Checkerboard Cave']
|
||||
'Checkerboard Cave',
|
||||
'Inverted Big Bomb Shop']
|
||||
LW_DM_entrances = ['Old Man Cave (East)',
|
||||
'Old Man House (Bottom)',
|
||||
'Old Man House (Top)',
|
||||
'Death Mountain Return Cave (East)',
|
||||
'Spectacle Rock Cave Peak',
|
||||
'Spectacle Rock Cave',
|
||||
'Spectacle Rock Cave (Bottom)',
|
||||
'Tower of Hera',
|
||||
'Death Mountain Return Cave (West)',
|
||||
'Paradox Cave (Top)',
|
||||
@@ -1159,7 +1211,7 @@ def set_inverted_big_bomb_rules(world, player):
|
||||
'Chest Game',
|
||||
'Dark World Hammer Peg Cave',
|
||||
'Red Shield Shop',
|
||||
'Dark Sanctuary Hint',
|
||||
'Inverted Dark Sanctuary',
|
||||
'Fortune Teller (Dark)',
|
||||
'Dark World Shop',
|
||||
'Dark World Lumberjack Shop',
|
||||
@@ -1169,7 +1221,7 @@ def set_inverted_big_bomb_rules(world, player):
|
||||
Southern_DW_entrances = ['Hype Cave',
|
||||
'Bonk Fairy (Dark)',
|
||||
'Archery Game',
|
||||
'Inverted Big Bomb Shop',
|
||||
'Inverted Links House',
|
||||
'Dark Lake Hylia Shop',
|
||||
'Swamp Palace']
|
||||
Isolated_DW_entrances = ['Spike Cave',
|
||||
@@ -1200,7 +1252,7 @@ def set_inverted_big_bomb_rules(world, player):
|
||||
|
||||
set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Dark World', 'Region', player) and state.can_reach('Inverted Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player))
|
||||
|
||||
#crossing peg bridge starting from the southern dark world
|
||||
# crossing peg bridge starting from the southern dark world
|
||||
def cross_peg_bridge(state):
|
||||
return state.has('Hammer', player)
|
||||
|
||||
@@ -1261,23 +1313,18 @@ def set_bunny_rules(world, player):
|
||||
# Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing.
|
||||
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
|
||||
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)']
|
||||
# todo: bunny impassable caves
|
||||
# sewers drop may or may not be - maybe just new terminology
|
||||
# desert pots are impassible by bunny - need rules for those transitions
|
||||
# skull woods drops tend to soft lock bunny
|
||||
# tr too - dark ride, chest gap, entrance gap, pots in lazy eyes, etc
|
||||
|
||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins']
|
||||
|
||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
|
||||
'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
|
||||
'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins']
|
||||
|
||||
def path_to_access_rule(path, entrance):
|
||||
return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path)
|
||||
return lambda state: state.can_reach(entrance) and all(rule_func(state) for rule_func in path)
|
||||
|
||||
def options_to_access_rule(options):
|
||||
return lambda state: any(rule(state) for rule in options)
|
||||
return lambda state: any(rule_func(state) for rule_func in options)
|
||||
|
||||
def get_rule_to_add(region):
|
||||
if not region.is_light_world:
|
||||
def get_rule_to_add(start_region):
|
||||
if not start_region.is_light_world:
|
||||
return lambda state: state.has_Pearl(player)
|
||||
# in this case we are mixed region.
|
||||
# we collect possible options.
|
||||
@@ -1290,8 +1337,8 @@ def set_bunny_rules(world, player):
|
||||
# for each such entrance a new option is added that consist of:
|
||||
# a) being able to reach it, and
|
||||
# b) being able to access all entrances from there to `region`
|
||||
seen = set([region])
|
||||
queue = collections.deque([(region, [])])
|
||||
seen = {start_region}
|
||||
queue = deque([(start_region, [])])
|
||||
while queue:
|
||||
(current, path) = queue.popleft()
|
||||
for entrance in current.entrances:
|
||||
@@ -1301,7 +1348,7 @@ def set_bunny_rules(world, player):
|
||||
new_path = path + [entrance.access_rule]
|
||||
seen.add(new_region)
|
||||
if not new_region.is_light_world:
|
||||
continue # we don't care about pure dark world entrances
|
||||
continue # we don't care about pure dark world entrances
|
||||
if new_region.is_dark_world:
|
||||
queue.append((new_region, new_path))
|
||||
else:
|
||||
@@ -1315,13 +1362,25 @@ def set_bunny_rules(world, player):
|
||||
if not region.is_dark_world:
|
||||
continue
|
||||
rule = get_rule_to_add(region)
|
||||
for exit in region.exits:
|
||||
add_rule(exit, rule)
|
||||
for ext in region.exits:
|
||||
add_rule(ext, rule)
|
||||
|
||||
paradox_shop = world.get_region('Light World Death Mountain Shop', player)
|
||||
if paradox_shop.is_dark_world:
|
||||
add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop))
|
||||
|
||||
for ent_name in bunny_impassible_doors:
|
||||
bunny_exit = world.get_entrance(ent_name, player)
|
||||
if bunny_exit.parent_region.is_dark_world:
|
||||
add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))
|
||||
|
||||
doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors]
|
||||
doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked]
|
||||
for door in doors_to_check:
|
||||
room = world.get_room(door.roomIndex, player)
|
||||
if door.entrance.parent_region.is_dark_world and room.kind(door) in [DoorKind.Dashable, DoorKind.Bombable, DoorKind.Hidden]:
|
||||
add_rule(door.entrance, get_rule_to_add(door.entrance.parent_region))
|
||||
|
||||
# Add requirements for all locations that are actually in the dark world, except those available to the bunny
|
||||
for location in world.get_locations():
|
||||
if location.player == player and location.parent_region.is_dark_world:
|
||||
@@ -1331,29 +1390,26 @@ def set_bunny_rules(world, player):
|
||||
|
||||
add_rule(location, get_rule_to_add(location.parent_region))
|
||||
|
||||
|
||||
def set_inverted_bunny_rules(world, player):
|
||||
|
||||
# regions for the exits of multi-entrace caves/drops that bunny cannot pass
|
||||
# Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing.
|
||||
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
|
||||
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)', 'The Sky']
|
||||
# todo: bunny impassable caves
|
||||
# sewers drop may or may not be - maybe just new terminology
|
||||
# desert pots are impassible by bunny - need rules for those transitions
|
||||
# skull woods drops tend to soft lock bunny
|
||||
# tr too - dark ride, chest gap, entrance gap, pots in lazy eyes, etc
|
||||
|
||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins', 'Bombos Tablet', 'Ether Tablet', 'Purple Chest']
|
||||
|
||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
|
||||
'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
|
||||
'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins',
|
||||
'Bombos Tablet', 'Ether Tablet', 'Purple Chest']
|
||||
|
||||
def path_to_access_rule(path, entrance):
|
||||
return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path)
|
||||
return lambda state: state.can_reach(entrance) and all(rule_func(state) for rule_func in path)
|
||||
|
||||
def options_to_access_rule(options):
|
||||
return lambda state: any(rule(state) for rule in options)
|
||||
return lambda state: any(rule_func(state) for rule_func in options)
|
||||
|
||||
def get_rule_to_add(region):
|
||||
if not region.is_dark_world:
|
||||
def get_rule_to_add(start_region):
|
||||
if not start_region.is_dark_world:
|
||||
return lambda state: state.has_Pearl(player)
|
||||
# in this case we are mixed region.
|
||||
# we collect possible options.
|
||||
@@ -1366,8 +1422,8 @@ def set_inverted_bunny_rules(world, player):
|
||||
# for each such entrance a new option is added that consist of:
|
||||
# a) being able to reach it, and
|
||||
# b) being able to access all entrances from there to `region`
|
||||
seen = set([region])
|
||||
queue = collections.deque([(region, [])])
|
||||
seen = {start_region}
|
||||
queue = deque([(start_region, [])])
|
||||
while queue:
|
||||
(current, path) = queue.popleft()
|
||||
for entrance in current.entrances:
|
||||
@@ -1377,7 +1433,7 @@ def set_inverted_bunny_rules(world, player):
|
||||
new_path = path + [entrance.access_rule]
|
||||
seen.add(new_region)
|
||||
if not new_region.is_dark_world:
|
||||
continue # we don't care about pure light world entrances
|
||||
continue # we don't care about pure light world entrances
|
||||
if new_region.is_light_world:
|
||||
queue.append((new_region, new_path))
|
||||
else:
|
||||
@@ -1391,13 +1447,25 @@ def set_inverted_bunny_rules(world, player):
|
||||
if not region.is_light_world:
|
||||
continue
|
||||
rule = get_rule_to_add(region)
|
||||
for exit in region.exits:
|
||||
add_rule(exit, rule)
|
||||
for ext in region.exits:
|
||||
add_rule(ext, rule)
|
||||
|
||||
paradox_shop = world.get_region('Light World Death Mountain Shop', player)
|
||||
if paradox_shop.is_light_world:
|
||||
add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop))
|
||||
|
||||
for ent_name in bunny_impassible_doors:
|
||||
bunny_exit = world.get_entrance(ent_name, player)
|
||||
if bunny_exit.parent_region.is_light_world:
|
||||
add_rule(bunny_exit, get_rule_to_add(bunny_exit.parent_region))
|
||||
|
||||
doors_to_check = [x for x in world.doors if x.player == player and x not in bunny_impassible_doors]
|
||||
doors_to_check = [x for x in doors_to_check if x.type in [DoorType.Normal, DoorType.Interior] and not x.blocked]
|
||||
for door in doors_to_check:
|
||||
room = world.get_room(door.roomIndex, player)
|
||||
if door.entrance.parent_region.is_light_world and room.kind(door) in [DoorKind.Dashable, DoorKind.Bombable, DoorKind.Hidden]:
|
||||
add_rule(door.entrance, get_rule_to_add(door.entrance.parent_region))
|
||||
|
||||
# Add requirements for all locations that are actually in the light world, except those available to the bunny
|
||||
for location in world.get_locations():
|
||||
if location.player == player and location.parent_region.is_light_world:
|
||||
@@ -1408,6 +1476,72 @@ def set_inverted_bunny_rules(world, player):
|
||||
add_rule(location, get_rule_to_add(location.parent_region))
|
||||
|
||||
|
||||
bunny_impassible_doors = {
|
||||
'Hyrule Dungeon Armory S', 'Hyrule Dungeon Armory ES', 'Sewers Secret Room Push Block', 'Sewers Pull Switch S',
|
||||
'Eastern Lobby N', 'Eastern Courtyard Ledge W', 'Eastern Courtyard Ledge E', 'Eastern Pot Switch SE',
|
||||
'Eastern Map Balcony Hook Path', 'Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW',
|
||||
'Eastern Hint Tile Push Block', 'Eastern Darkness S', 'Eastern Darkness NE', 'Eastern Darkness Up Stairs',
|
||||
'Eastern Attic Start WS', 'Eastern Single Eyegore NE', 'Eastern Duo Eyegores NE', 'Desert Main Lobby Left Path',
|
||||
'Desert Main Lobby Right Path', 'Desert Left Alcove Path', 'Desert Right Alcove Path', 'Desert Compass NW',
|
||||
'Desert West Lobby NW', 'Desert Back Lobby NW', 'Desert Four Statues NW', 'Desert Four Statues ES',
|
||||
'Desert Beamos Hall WS', 'Desert Beamos Hall NE', 'Desert Wall Slide NW', 'Hera Lobby Down Stairs',
|
||||
'Hera Lobby Key Stairs', 'Hera Lobby Up Stairs', 'Hera Tile Room EN', 'Hera Tridorm SE', 'Hera Beetles WS',
|
||||
'Hera 4F Down Stairs', 'Tower Gold Knights SW', 'Tower Dark Maze EN', 'Tower Dark Pits ES', 'Tower Dark Archers WN',
|
||||
'Tower Red Spears WN', 'Tower Red Guards EN', 'Tower Red Guards SW', 'Tower Circle of Pots NW', 'Tower Altar NW',
|
||||
'PoD Left Cage SW', 'PoD Middle Cage SE', 'PoD Pit Room Bomb Hole', 'PoD Pit Room Block Path N',
|
||||
'PoD Pit Room Block Path S', 'PoD Stalfos Basement Warp', 'PoD Arena Main SW', 'PoD Arena Main Crystal Path',
|
||||
'PoD Arena Bonk Path', 'PoD Arena Crystal Path', 'PoD Sexy Statue NW', 'PoD Map Balcony Drop Down',
|
||||
'PoD Mimics 1 NW', 'PoD Warp Hint Warp', 'PoD Falling Bridge Path N', 'PoD Falling Bridge Path S',
|
||||
'PoD Mimics 2 NW', 'PoD Bow Statue Down Ladder', 'PoD Dark Pegs Up Ladder', 'PoD Dark Pegs WN',
|
||||
'PoD Turtle Party ES', 'PoD Turtle Party NW', 'PoD Callback Warp', 'Swamp Lobby Moat', 'Swamp Entrance Moat',
|
||||
'Swamp Trench 1 Approach Swim Depart', 'Swamp Trench 1 Approach Key', 'Swamp Trench 1 Key Approach',
|
||||
'Swamp Trench 1 Key Ledge Depart', 'Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Departure Key',
|
||||
'Swamp Hub Hook Path', 'Swamp Compass Donut Push Block',
|
||||
'Swamp Shortcut Blue Barrier', 'Swamp Trench 2 Pots Blue Barrier', 'Swamp Trench 2 Pots Wet',
|
||||
'Swamp Trench 2 Departure Wet', 'Swamp West Shallows Push Blocks', 'Swamp West Ledge Hook Path',
|
||||
'Swamp Barrier Ledge Hook Path', 'Swamp Attic Left Pit', 'Swamp Attic Right Pit', 'Swamp Push Statue NW',
|
||||
'Swamp Push Statue NE', 'Swamp Drain Right Switch', 'Swamp Waterway NE', 'Swamp Waterway N', 'Swamp Waterway NW',
|
||||
'Skull Pot Circle WN', 'Skull Pot Circle Star Path', 'Skull Pull Switch S', 'Skull Big Chest N',
|
||||
'Skull Big Chest Hookpath', 'Skull 2 East Lobby NW', 'Skull Back Drop Star Path', 'Skull 2 West Lobby NW',
|
||||
'Skull 3 Lobby EN', 'Skull Star Pits SW', 'Skull Star Pits ES', 'Skull Torch Room WN', 'Skull Vines NW',
|
||||
'Thieves Conveyor Maze EN', 'Thieves Triple Bypass EN', 'Thieves Triple Bypass SE', 'Thieves Triple Bypass WN',
|
||||
'Thieves Hellway Blue Barrier', 'Thieves Hellway Crystal Blue Barrier', 'Thieves Attic ES',
|
||||
'Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Bridge Block Path',
|
||||
'Thieves Conveyor Block Path', 'Ice Lobby WS', 'Ice Cross Left Push Block', 'Ice Cross Bottom Push Block Left',
|
||||
'Ice Cross Bottom Push Block Right', 'Ice Cross Right Push Block Top', 'Ice Cross Right Push Block Bottom',
|
||||
'Ice Cross Top Push Block Bottom', 'Ice Cross Top Push Block Right', 'Ice Bomb Drop Hole', 'Ice Pengator Switch WS',
|
||||
'Ice Pengator Switch ES', 'Ice Big Key Push Block', 'Ice Stalfos Hint SE', 'Ice Bomb Jump EN',
|
||||
'Ice Pengator Trap NE', 'Ice Hammer Block ES', 'Ice Tongue Pull WS', 'Ice Freezors Bomb Hole', 'Ice Tall Hint WS',
|
||||
'Ice Hookshot Ledge Path', 'Ice Hookshot Balcony Path', 'Ice Many Pots SW', 'Ice Many Pots WS',
|
||||
'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 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',
|
||||
'Mire South Fish Blue Barrier', 'Mire Tile Room NW', 'Mire Compass Blue Barrier', 'Mire Attic Hint Hole',
|
||||
'Mire Dark Shooters SW', 'Mire Crystal Mid Blue Barrier', 'Mire Crystal Left Blue Barrier', 'TR Main Lobby Gap',
|
||||
'TR Lobby Ledge Gap', 'TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE', 'TR Torches NW',
|
||||
'TR Pokey 2 EN', 'TR Pokey 2 ES', 'TR Twin Pokeys SW', 'TR Twin Pokeys EN', 'TR Big Chest Gap',
|
||||
'TR Big Chest Entrance Gap', 'TR Lazy Eyes ES', 'TR Tongue Pull WS', 'TR Tongue Pull NE', 'TR Dark Ride Up Stairs',
|
||||
'TR Dark Ride SW', 'TR Crystal Maze Forwards Path', 'TR Crystal Maze Blue Path', 'TR Crystal Maze Cane Path',
|
||||
'TR Final Abyss South Stairs', 'TR Final Abyss NW', 'GT Hope Room EN', 'GT Blocked Stairs Block Path',
|
||||
'GT Bob\'s Room Hole', 'GT Speed Torch SE', 'GT Speed Torch South Path', 'GT Speed Torch North Path',
|
||||
'GT Crystal Conveyor NE', 'GT Crystal Conveyor WN', 'GT Conveyor Cross EN', 'GT Conveyor Cross WN',
|
||||
'GT Hookshot East-North Path', 'GT Hookshot East-South Path', 'GT Hookshot North-East Path',
|
||||
'GT Hookshot North-South Path', 'GT Hookshot South-East Path', 'GT Hookshot South-North Path',
|
||||
'GT Hookshot Platform Blue Barrier', 'GT Hookshot Entry Blue Barrier', 'GT Double Switch Blue Path',
|
||||
'GT Double Switch Key Blue Path', 'GT Double Switch Blue Barrier', 'GT Double Switch Transition Blue',
|
||||
'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Ice Armos NE', 'GT Ice Armos WS',
|
||||
'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS', 'GT Mimics 2 NE',
|
||||
'GT Hidden Spikes EN', 'GT Cannonball Bridge SE', 'GT Gauntlet 1 WN', 'GT Gauntlet 2 EN', 'GT Gauntlet 2 SW',
|
||||
'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', 'GT Gauntlet 5 NW',
|
||||
'GT Gauntlet 5 WS', 'GT Lanmolas 2 ES', 'GT Lanmolas 2 NW', 'GT Wizzrobes 1 SW', 'GT Wizzrobes 2 SE',
|
||||
'GT Wizzrobes 2 NE', 'GT Torch Cross ES', 'GT Falling Torches NE', 'GT Moldorm Gap', 'GT Validation Block Path'
|
||||
}
|
||||
|
||||
|
||||
def add_key_logic_rules(world, player):
|
||||
key_logic = world.key_logic[player]
|
||||
for d_name, d_logic in key_logic.items():
|
||||
|
||||
@@ -1651,7 +1651,7 @@ class TextTable(object):
|
||||
text['telepathic_tile_castle_tower'] = CompressedTextMapper.convert("{NOBORDER}\nYou can reflect Agahnim's energy with Sword, Bug-net or Hammer.")
|
||||
text['telepathic_tile_ice_large_room'] = CompressedTextMapper.convert("{NOBORDER}\nAll right stop collaborate and listen\nIce is back with my brand new invention")
|
||||
text['telepathic_tile_turtle_rock'] = CompressedTextMapper.convert("{NOBORDER}\nYou shall not pass… without the red cane")
|
||||
text['telepathic_tile_ice_entrace'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
||||
text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
||||
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
||||
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
||||
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2018~~~\nS: Andy\n\n ~~~2017~~~\nA: ajneb174\nS: ajneb174")
|
||||
|
||||
@@ -34,6 +34,8 @@ def is_bundled():
|
||||
return getattr(sys, 'frozen', False)
|
||||
|
||||
def local_path(path):
|
||||
return path
|
||||
|
||||
if local_path.cached_path is not None:
|
||||
return os.path.join(local_path.cached_path, path)
|
||||
|
||||
@@ -188,16 +190,16 @@ def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan)
|
||||
|
||||
for ent, offset in entrance_offsets.items():
|
||||
# print(ent)
|
||||
str = ent
|
||||
string = ent
|
||||
for dp, data in entrance_data.items():
|
||||
byte_array = []
|
||||
address, size = data
|
||||
for i in range(0, size):
|
||||
byte_array.append(old_rom_data[address+(offset*size)+i])
|
||||
bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
|
||||
str += '\t'+bytes
|
||||
some_bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
|
||||
string += '\t'+some_bytes
|
||||
# print("%s: %s" % (dp, bytes))
|
||||
print(str)
|
||||
print(string)
|
||||
|
||||
|
||||
def print_wiki_doors(d_regions, world, player):
|
||||
|
||||
@@ -27,6 +27,8 @@ DRMode:
|
||||
dw 0
|
||||
DRFlags:
|
||||
dw 0
|
||||
DRScroll:
|
||||
db 0
|
||||
|
||||
; Vert 0,6,0 Horz 2,0,8
|
||||
org $279010
|
||||
|
||||
@@ -63,6 +63,8 @@ org $0DFA53
|
||||
jsl.l LampCheckOverride
|
||||
org $028046 ; <- 10046 - Bank02.asm : 217 (JSL EnableForceBlank) (Start of Module_LoadFile)
|
||||
jsl.l OnFileLoadOverride
|
||||
org $07A93F ; < 3A93F - Bank07.asm 6548 (LDA $8A : AND.b #$40 - Mirror checks)
|
||||
jsl.l MirrorCheckOverride
|
||||
|
||||
org $05ef47
|
||||
Sprite_HeartContainer_Override: ;sprite_heart_upgrades.asm : 96-100 (LDA $040C : CMP.b #$1A : BNE .not_in_ganons_tower)
|
||||
@@ -70,6 +72,13 @@ jsl GtBossHeartCheckOverride : bcs .not_in_ganons_tower
|
||||
nop : stz $0dd0, X : rts
|
||||
.not_in_ganons_tower
|
||||
|
||||
|
||||
org $2081f2
|
||||
jsl MirrorCheckOverride2
|
||||
org $20825c
|
||||
jsl MirrorCheckOverride2
|
||||
|
||||
|
||||
; 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
|
||||
; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds
|
||||
|
||||
+14
-1
@@ -37,4 +37,17 @@ OnFileLoadOverride:
|
||||
jsl OnFileLoad ; what I wrote over
|
||||
lda DRFlags : and #$80 : beq + ;flag is off
|
||||
lda $7ef086 : ora #$80 : sta $7ef086
|
||||
+ rtl
|
||||
+ lda DRFlags : and #$02 : beq +
|
||||
lda $7ef353 : bne +
|
||||
lda #$01 : sta $7ef353
|
||||
+ rtl
|
||||
|
||||
MirrorCheckOverride:
|
||||
lda DRFlags : and #$02 : beq ++
|
||||
lda $7ef353 : cmp #$01 : beq +
|
||||
++ lda $8A : and #$40 ; what I wrote over
|
||||
rtl
|
||||
+ lda DRScroll : rtl
|
||||
|
||||
MirrorCheckOverride2:
|
||||
lda $7ef353 : and #$02 : rtl
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
|
||||
DEST_DIRECTORY = '.'
|
||||
|
||||
if os.path.isdir("upx"):
|
||||
upx_string = "--upx-dir=upx"
|
||||
else:
|
||||
upx_string = ""
|
||||
|
||||
if os.path.isdir("build"):
|
||||
shutil.rmtree("build")
|
||||
|
||||
subprocess.run(" ".join(["pyinstaller DungeonRandomizer.spec ",
|
||||
upx_string,
|
||||
"-y ",
|
||||
"--onefile ",
|
||||
f"--distpath {DEST_DIRECTORY} ",
|
||||
]),
|
||||
shell=True)
|
||||
@@ -0,0 +1,21 @@
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
|
||||
DEST_DIRECTORY = '.'
|
||||
|
||||
if os.path.isdir("upx"):
|
||||
upx_string = "--upx-dir=upx"
|
||||
else:
|
||||
upx_string = ""
|
||||
|
||||
if os.path.isdir("build"):
|
||||
shutil.rmtree("build")
|
||||
|
||||
subprocess.run(" ".join(["pyinstaller Gui.spec ",
|
||||
upx_string,
|
||||
"-y ",
|
||||
"--onefile ",
|
||||
f"--distpath {DEST_DIRECTORY} ",
|
||||
]),
|
||||
shell=True)
|
||||
@@ -0,0 +1,347 @@
|
||||
from tkinter import filedialog, messagebox, Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, ALL, NSEW, LEFT, BOTTOM, X, RIGHT, TOP, HORIZONTAL, EW, NS
|
||||
from glob import glob
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
import webbrowser
|
||||
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
||||
from Rom import Sprite
|
||||
from Utils import is_bundled, local_path, output_path, open_file
|
||||
|
||||
|
||||
class SpriteSelector(object):
|
||||
def __init__(self, parent, callback, adjuster=False):
|
||||
if is_bundled():
|
||||
self.deploy_icons()
|
||||
self.parent = parent
|
||||
self.window = Toplevel(parent)
|
||||
self.window.geometry("800x650")
|
||||
self.sections = []
|
||||
self.callback = callback
|
||||
self.adjuster = adjuster
|
||||
|
||||
self.window.wm_title("TAKE ANY ONE YOU WANT")
|
||||
self.window['padx'] = 5
|
||||
self.window['pady'] = 5
|
||||
self.all_sprites = []
|
||||
|
||||
def open_official_sprite_listing(_evt):
|
||||
webbrowser.open("http://alttpr.com/sprite_preview")
|
||||
|
||||
def open_unofficial_sprite_dir(_evt):
|
||||
open_file(self.unofficial_sprite_dir)
|
||||
|
||||
def open_spritesomething_listing(_evt):
|
||||
webbrowser.open("https://artheau.github.io/SpriteSomething/?mode=zelda3/link")
|
||||
|
||||
official_frametitle = Frame(self.window)
|
||||
official_title_text = Label(official_frametitle, text="Official Sprites")
|
||||
official_title_link = Label(official_frametitle, text="(open)", fg="blue", cursor="hand2")
|
||||
official_title_text.pack(side=LEFT)
|
||||
official_title_link.pack(side=LEFT)
|
||||
official_title_link.bind("<Button-1>", open_official_sprite_listing)
|
||||
|
||||
unofficial_frametitle = Frame(self.window)
|
||||
unofficial_title_text = Label(unofficial_frametitle, text="Unofficial Sprites")
|
||||
unofficial_title_link = Label(unofficial_frametitle, text="(open)", fg="blue", cursor="hand2")
|
||||
unofficial_title_text.pack(side=LEFT)
|
||||
unofficial_title_link.pack(side=LEFT)
|
||||
unofficial_title_link.bind("<Button-1>", open_unofficial_sprite_dir)
|
||||
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.')
|
||||
|
||||
frame = Frame(self.window)
|
||||
frame.pack(side=BOTTOM, fill=X, pady=5)
|
||||
|
||||
button = Button(frame, text="Browse for file...", command=self.browse_for_sprite)
|
||||
button.pack(side=RIGHT, padx=(5, 0))
|
||||
|
||||
button = Button(frame, text="Update official sprites", command=self.update_official_sprites)
|
||||
button.pack(side=RIGHT, padx=(5, 0))
|
||||
|
||||
button = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
button = Button(frame, text="Random sprite", command=self.use_random_sprite)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
if adjuster:
|
||||
button = Button(frame, text="Current sprite from rom", command=self.use_default_sprite)
|
||||
button.pack(side=LEFT, padx=(0, 5))
|
||||
|
||||
set_icon(self.window)
|
||||
self.window.focus()
|
||||
|
||||
def icon_section(self, frame_label, path, no_results_label):
|
||||
frame = LabelFrame(self.window, labelwidget=frame_label, padx=5, pady=5)
|
||||
canvas = Canvas(frame, borderwidth=0)
|
||||
y_scrollbar = Scrollbar(frame, orient="vertical", command=canvas.yview)
|
||||
y_scrollbar.pack(side="right", fill="y")
|
||||
content_frame = Frame(canvas)
|
||||
canvas.pack(side="left", fill="both", expand=True)
|
||||
canvas.create_window((4, 4), window=content_frame, anchor="nw")
|
||||
canvas.configure(yscrollcommand=y_scrollbar.set)
|
||||
|
||||
def onFrameConfigure(canvas):
|
||||
"""Reset the scroll region to encompass the inner frame"""
|
||||
canvas.configure(scrollregion=canvas.bbox("all"))
|
||||
|
||||
content_frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
|
||||
frame.pack(side=TOP, fill=X)
|
||||
|
||||
sprites = []
|
||||
|
||||
for file in glob(output_path(path)):
|
||||
sprites.append(Sprite(file))
|
||||
|
||||
sprites.sort(key=lambda s: str.lower(s.name or "").strip())
|
||||
|
||||
i = 0
|
||||
for sprite in sprites:
|
||||
image = get_image_for_sprite(sprite)
|
||||
if image is None:
|
||||
continue
|
||||
self.all_sprites.append(sprite)
|
||||
button = Button(content_frame, image=image, command=lambda spr=sprite: self.select_sprite(spr))
|
||||
ToolTips.register(button, sprite.name + ("\nBy: %s" % sprite.author_name if sprite.author_name else ""))
|
||||
button.image = image
|
||||
button.grid(row=i // 16, column=i % 16)
|
||||
i += 1
|
||||
|
||||
if i == 0:
|
||||
label = Label(content_frame, text=no_results_label)
|
||||
label.pack()
|
||||
|
||||
def update_official_sprites(self):
|
||||
# need to wrap in try catch. We don't want errors getting the json or downloading the files to break us.
|
||||
self.window.destroy()
|
||||
self.parent.update()
|
||||
def work(task):
|
||||
resultmessage = ""
|
||||
successful = True
|
||||
|
||||
def finished():
|
||||
task.close_window()
|
||||
if successful:
|
||||
messagebox.showinfo("Sprite Updater", resultmessage)
|
||||
else:
|
||||
messagebox.showerror("Sprite Updater", resultmessage)
|
||||
SpriteSelector(self.parent, self.callback, self.adjuster)
|
||||
|
||||
try:
|
||||
task.update_status("Downloading official sprites list")
|
||||
with urlopen('https://alttpr.com/sprites') as response:
|
||||
sprites_arr = json.loads(response.read().decode("utf-8"))
|
||||
except Exception as e:
|
||||
resultmessage = "Error getting list of official sprites. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
task.queue_event(finished)
|
||||
return
|
||||
|
||||
try:
|
||||
task.update_status("Determining needed sprites")
|
||||
current_sprites = [os.path.basename(file) for file in glob(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+'/*')]
|
||||
# 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]
|
||||
obsolete_sprites = [sprite for sprite in current_sprites if sprite not in official_filenames]
|
||||
except Exception as e:
|
||||
resultmessage = "Error Determining which sprites to update. Sprites not updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
task.queue_event(finished)
|
||||
return
|
||||
|
||||
updated = 0
|
||||
for (sprite_url, filename) in needed_sprites:
|
||||
try:
|
||||
task.update_status("Downloading needed sprite %g/%g" % (updated + 1, len(needed_sprites)))
|
||||
target = os.path.join(self.official_sprite_dir, filename)
|
||||
with urlopen(sprite_url) as response, open(target, 'wb') as out:
|
||||
shutil.copyfileobj(response, out)
|
||||
except Exception as e:
|
||||
resultmessage = "Error downloading sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
updated += 1
|
||||
|
||||
deleted = 0
|
||||
for sprite in obsolete_sprites:
|
||||
try:
|
||||
task.update_status("Removing obsolete sprite %g/%g" % (deleted + 1, len(obsolete_sprites)))
|
||||
os.remove(os.path.join(self.official_sprite_dir, sprite))
|
||||
except Exception as e:
|
||||
resultmessage = "Error removing obsolete sprite. Not all sprites updated.\n\n%s: %s" % (type(e).__name__, e)
|
||||
successful = False
|
||||
deleted += 1
|
||||
|
||||
if successful:
|
||||
resultmessage = "official sprites updated successfully"
|
||||
|
||||
task.queue_event(finished)
|
||||
|
||||
BackgroundTaskProgress(self.parent, work, "Updating Sprites")
|
||||
|
||||
def browse_for_sprite(self):
|
||||
sprite = filedialog.askopenfilename(
|
||||
filetypes=[("All Sprite Sources", (".zspr", ".spr", ".sfc", ".smc")),
|
||||
("ZSprite files", ".zspr"),
|
||||
("Sprite files", ".spr"),
|
||||
("Rom Files", (".sfc", ".smc")),
|
||||
("All Files", "*")])
|
||||
try:
|
||||
self.callback(Sprite(sprite))
|
||||
except Exception:
|
||||
self.callback(None)
|
||||
self.window.destroy()
|
||||
|
||||
def use_default_sprite(self):
|
||||
self.callback(None, False)
|
||||
self.window.destroy()
|
||||
|
||||
def use_default_link_sprite(self):
|
||||
self.callback(Sprite.default_link_sprite(), False)
|
||||
self.window.destroy()
|
||||
|
||||
def use_random_sprite(self):
|
||||
self.callback(random.choice(self.all_sprites) if self.all_sprites else None, True)
|
||||
self.window.destroy()
|
||||
|
||||
def select_sprite(self, spritename):
|
||||
self.callback(spritename, False)
|
||||
self.window.destroy()
|
||||
|
||||
def deploy_icons(self):
|
||||
if not os.path.exists(self.unofficial_sprite_dir):
|
||||
os.makedirs(self.unofficial_sprite_dir)
|
||||
if not os.path.exists(self.official_sprite_dir):
|
||||
shutil.copytree(self.local_official_sprite_dir, self.official_sprite_dir)
|
||||
|
||||
@property
|
||||
def official_sprite_dir(self):
|
||||
if is_bundled():
|
||||
return output_path("sprites/official")
|
||||
return self.local_official_sprite_dir
|
||||
|
||||
@property
|
||||
def local_official_sprite_dir(self):
|
||||
return local_path("data/sprites/official")
|
||||
|
||||
@property
|
||||
def unofficial_sprite_dir(self):
|
||||
if is_bundled():
|
||||
return output_path("sprites/unofficial")
|
||||
return self.local_unofficial_sprite_dir
|
||||
|
||||
@property
|
||||
def local_unofficial_sprite_dir(self):
|
||||
return local_path("data/sprites/unofficial")
|
||||
|
||||
|
||||
def get_image_for_sprite(sprite):
|
||||
if not sprite.valid:
|
||||
return None
|
||||
height = 24
|
||||
width = 16
|
||||
|
||||
def draw_sprite_into_gif(add_palette_color, set_pixel_color_index):
|
||||
|
||||
def drawsprite(spr, pal_as_colors, offset):
|
||||
for y, row in enumerate(spr):
|
||||
for x, pal_index in enumerate(row):
|
||||
if pal_index:
|
||||
color = pal_as_colors[pal_index - 1]
|
||||
set_pixel_color_index(x + offset[0], y + offset[1], color)
|
||||
|
||||
add_palette_color(16, (40, 40, 40))
|
||||
shadow = [
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
]
|
||||
|
||||
drawsprite(shadow, [16], (2, 17))
|
||||
|
||||
palettes = sprite.decode_palette()
|
||||
for i in range(15):
|
||||
add_palette_color(i + 1, palettes[0][i])
|
||||
|
||||
body = sprite.decode16(0x4C0)
|
||||
drawsprite(body, list(range(1, 16)), (0, 8))
|
||||
head = sprite.decode16(0x40)
|
||||
drawsprite(head, list(range(1, 16)), (0, 0))
|
||||
|
||||
def make_gif(callback):
|
||||
gif_header = b'GIF89a'
|
||||
|
||||
gif_lsd = bytearray(7)
|
||||
gif_lsd[0] = width
|
||||
gif_lsd[2] = height
|
||||
gif_lsd[4] = 0xF4 # 32 color palette follows. transparant + 15 for sprite + 1 for shadow=17 which rounds up to 32 as nearest power of 2
|
||||
gif_lsd[5] = 0 # background color is zero
|
||||
gif_lsd[6] = 0 # aspect raio not specified
|
||||
gif_gct = bytearray(3 * 32)
|
||||
|
||||
gif_gce = bytearray(8)
|
||||
gif_gce[0] = 0x21 # start of extention blocked
|
||||
gif_gce[1] = 0xF9 # identifies this as the Graphics Control extension
|
||||
gif_gce[2] = 4 # we are suppling only the 4 four bytes
|
||||
gif_gce[3] = 0x01 # this gif includes transparency
|
||||
gif_gce[4] = gif_gce[5] = 0 # animation frrame delay (unused)
|
||||
gif_gce[6] = 0 # transparent color is index 0
|
||||
gif_gce[7] = 0 # end of gif_gce
|
||||
gif_id = bytearray(10)
|
||||
gif_id[0] = 0x2c
|
||||
# byte 1,2 are image left. 3,4 are image top both are left as zerosuitsamus
|
||||
gif_id[5] = width
|
||||
gif_id[7] = height
|
||||
gif_id[9] = 0 # no local color table
|
||||
|
||||
gif_img_minimum_code_size = bytes([7]) # we choose 7 bits, so that each pixel is represented by a byte, for conviennce.
|
||||
|
||||
clear = 0x80
|
||||
stop = 0x81
|
||||
|
||||
unchunked_image_data = bytearray(height * (width + 1) + 1)
|
||||
# we technically need a Clear code once every 125 bytes, but we do it at the start of every row for simplicity
|
||||
for row in range(height):
|
||||
unchunked_image_data[row * (width + 1)] = clear
|
||||
unchunked_image_data[-1] = stop
|
||||
|
||||
def add_palette_color(index, color):
|
||||
gif_gct[3 * index] = color[0]
|
||||
gif_gct[3 * index + 1] = color[1]
|
||||
gif_gct[3 * index + 2] = color[2]
|
||||
|
||||
def set_pixel_color_index(x, y, color):
|
||||
unchunked_image_data[y * (width + 1) + x + 1] = color
|
||||
|
||||
callback(add_palette_color, set_pixel_color_index)
|
||||
|
||||
def chunk_image(img):
|
||||
for i in range(0, len(img), 255):
|
||||
chunk = img[i:i + 255]
|
||||
yield bytes([len(chunk)])
|
||||
yield chunk
|
||||
|
||||
gif_img = b''.join([gif_img_minimum_code_size] + list(chunk_image(unchunked_image_data)) + [b'\x00'])
|
||||
|
||||
gif = b''.join([gif_header, gif_lsd, gif_gct, gif_gce, gif_id, gif_img, b'\x3b'])
|
||||
|
||||
return gif
|
||||
|
||||
gif_data = make_gif(draw_sprite_into_gif)
|
||||
image = PhotoImage(data=gif_data)
|
||||
|
||||
return image.zoom(2)
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "classes" package
|
||||
@@ -0,0 +1,109 @@
|
||||
CUSTOMITEMS = [
|
||||
"bow", "progressivebow", "boomerang", "redmerang", "hookshot",
|
||||
"mushroom", "powder", "firerod", "icerod", "bombos",
|
||||
"ether", "quake", "lamp", "hammer", "shovel",
|
||||
|
||||
"flute", "bugnet", "book", "bottle", "somaria",
|
||||
"byrna", "cape", "mirror", "boots", "powerglove",
|
||||
"titansmitt", "progressiveglove", "flippers", "pearl", "heartpiece",
|
||||
|
||||
"heartcontainer", "sancheart", "sword1", "sword2", "sword3",
|
||||
"sword4", "progressivesword", "shield1", "shield2", "shield3",
|
||||
"progressiveshield", "mail2", "mail3", "progressivemail", "halfmagic",
|
||||
|
||||
"quartermagic", "bombsplus5", "bombsplus10", "arrowsplus5", "arrowsplus10",
|
||||
"arrow1", "arrow10", "bomb1", "bomb3", "bomb10",
|
||||
"rupee1", "rupee5", "rupee20", "rupee50", "rupee100",
|
||||
|
||||
"rupee300", "blueclock", "greenclock", "redclock", "silversupgrade",
|
||||
"generickeys", "triforcepieces", "triforcepiecesgoal", "triforce", "rupoor",
|
||||
"rupoorcost"
|
||||
]
|
||||
|
||||
CANTSTARTWITH = [
|
||||
"triforcepiecesgoal", "triforce", "rupoor",
|
||||
"rupoorcost"
|
||||
]
|
||||
|
||||
CUSTOMITEMLABELS = [
|
||||
"Bow", "Progressive Bow", "Blue Boomerang", "Red Boomerang", "Hookshot",
|
||||
"Mushroom", "Magic Powder", "Fire Rod", "Ice Rod", "Bombos",
|
||||
"Ether", "Quake", "Lamp", "Hammer", "Shovel",
|
||||
|
||||
"Ocarina", "Bug Catching Net", "Book of Mudora", "Bottle", "Cane of Somaria",
|
||||
"Cane of Byrna", "Magic Cape", "Magic Mirror", "Pegasus Boots", "Power Glove",
|
||||
"Titans Mitts", "Progressive Glove", "Flippers", "Moon Pearl", "Piece of Heart",
|
||||
|
||||
"Boss Heart Container", "Sanctuary Heart Container", "Fighter Sword", "Master Sword", "Tempered Sword",
|
||||
"Golden Sword", "Progressive Sword", "Blue Shield", "Red Shield", "Mirror Shield",
|
||||
"Progressive Shield", "Blue Mail", "Red Mail", "Progressive Armor", "Magic Upgrade (1/2)",
|
||||
|
||||
"Magic Upgrade (1/4)", "Bomb Upgrade (+5)", "Bomb Upgrade (+10)", "Arrow Upgrade (+5)", "Arrow Upgrade (+10)",
|
||||
"Single Arrow", "Arrows (10)", "Single Bomb", "Bombs (3)", "Bombs (10)",
|
||||
"Rupee (1)", "Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)",
|
||||
|
||||
"Rupees (300)", "Blue Clock", "Green Clock", "Red Clock", "Silver Arrows",
|
||||
"Small Key (Universal)", "Triforce Piece", "Triforce Piece Goal", "Triforce", "Rupoor",
|
||||
"Rupoor Cost"
|
||||
]
|
||||
|
||||
SETTINGSTOPROCESS = {
|
||||
"randomizer": {
|
||||
"item": {
|
||||
"retro": "retro",
|
||||
"worldstate": "mode",
|
||||
"logiclevel": "logic",
|
||||
"goal": "goal",
|
||||
"crystals_gt": "crystals_gt",
|
||||
"crystals_ganon": "crystals_ganon",
|
||||
"weapons": "swords",
|
||||
"itempool": "difficulty",
|
||||
"itemfunction": "item_functionality",
|
||||
"timer": "timer",
|
||||
"progressives": "progressive",
|
||||
"accessibility": "accessibility",
|
||||
"sortingalgo": "algorithm"
|
||||
},
|
||||
"entrance": {
|
||||
"openpyramid": "openpyramid",
|
||||
"shuffleganon": "shuffleganon",
|
||||
"entranceshuffle": "shuffle"
|
||||
},
|
||||
"enemizer": {
|
||||
"potshuffle": "shufflepots",
|
||||
"enemyshuffle": "shuffleenemies",
|
||||
"bossshuffle": "shufflebosses",
|
||||
"enemydamage": "enemy_damage",
|
||||
"enemyhealth": "enemy_health"
|
||||
},
|
||||
"dungeon": {
|
||||
"mapshuffle": "mapshuffle",
|
||||
"compassshuffle": "compassshuffle",
|
||||
"smallkeyshuffle": "keyshuffle",
|
||||
"bigkeyshuffle": "bigkeyshuffle",
|
||||
"dungeondoorshuffle": "door_shuffle",
|
||||
"experimental": "experimental",
|
||||
"dungeon_counters": "dungeon_counters"
|
||||
},
|
||||
"multiworld": {
|
||||
"names": "names"
|
||||
},
|
||||
"gameoptions": {
|
||||
"hints": "hints",
|
||||
"nobgm": "disablemusic",
|
||||
"quickswap": "quickswap",
|
||||
"heartcolor": "heartcolor",
|
||||
"heartbeep": "heartbeep",
|
||||
"menuspeed": "fastmenu",
|
||||
"owpalettes": "ow_palettes",
|
||||
"uwpalettes": "uw_palettes"
|
||||
},
|
||||
"generation": {
|
||||
"spoiler": "create_spoiler",
|
||||
"suppressrom": "suppress_rom",
|
||||
"usestartinventory": "usestartinventory",
|
||||
"usecustompool": "custom",
|
||||
"saveonexit": "saveonexit"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "gui" package
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "gui.about" package
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "gui.adjust" package
|
||||
@@ -0,0 +1,113 @@
|
||||
from tkinter import ttk, filedialog, messagebox, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT, X, BOTTOM
|
||||
from AdjusterMain import adjust
|
||||
from argparse import Namespace
|
||||
from classes.SpriteSelector import SpriteSelector
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
def adjust_page(top, parent, settings):
|
||||
# Adjust page
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Adjust options
|
||||
self.widgets = {}
|
||||
|
||||
# Adjust option sections
|
||||
self.frames = {}
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
self.frames["selectOptionsFrame"] = Frame(self)
|
||||
self.frames["leftAdjustFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
self.frames["rightAdjustFrame"] = Frame(self.frames["selectOptionsFrame"])
|
||||
self.frames["bottomAdjustFrame"] = Frame(self)
|
||||
self.frames["selectOptionsFrame"].pack(fill=X)
|
||||
self.frames["leftAdjustFrame"].pack(side=LEFT)
|
||||
self.frames["rightAdjustFrame"].pack(side=RIGHT)
|
||||
self.frames["bottomAdjustFrame"].pack(fill=X)
|
||||
|
||||
with open(os.path.join("resources","app","gui","adjust","overview","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)
|
||||
|
||||
# Sprite Selection
|
||||
self.spriteNameVar2 = StringVar()
|
||||
spriteDialogFrame2 = Frame(self.frames["leftAdjustFrame"])
|
||||
baseSpriteLabel2 = Label(spriteDialogFrame2, text='Sprite:')
|
||||
spriteEntry2 = Label(spriteDialogFrame2, textvariable=self.spriteNameVar2)
|
||||
self.sprite = None
|
||||
|
||||
def set_sprite(sprite_param, random_sprite=False):
|
||||
if sprite_param is None or not sprite_param.valid:
|
||||
self.sprite = None
|
||||
self.spriteNameVar2.set('(unchanged)')
|
||||
else:
|
||||
self.sprite = sprite_param
|
||||
self.spriteNameVar2.set(self.sprite.name)
|
||||
top.randomSprite.set(random_sprite)
|
||||
|
||||
def SpriteSelectAdjuster():
|
||||
SpriteSelector(parent, set_sprite, adjuster=True)
|
||||
|
||||
spriteSelectButton2 = Button(spriteDialogFrame2, text='...', command=SpriteSelectAdjuster)
|
||||
|
||||
baseSpriteLabel2.pack(side=LEFT)
|
||||
spriteEntry2.pack(side=LEFT)
|
||||
spriteSelectButton2.pack(side=LEFT)
|
||||
spriteDialogFrame2.pack(anchor=E)
|
||||
|
||||
adjustRomFrame = Frame(self.frames["bottomAdjustFrame"])
|
||||
adjustRomLabel = Label(adjustRomFrame, text='Rom to adjust: ')
|
||||
self.romVar2 = StringVar(value=settings["rom"])
|
||||
romEntry2 = Entry(adjustRomFrame, textvariable=self.romVar2)
|
||||
|
||||
def RomSelect2():
|
||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
|
||||
if rom:
|
||||
settings["rom"] = rom
|
||||
self.romVar2.set(rom)
|
||||
romSelectButton2 = Button(adjustRomFrame, text='Select Rom', command=RomSelect2)
|
||||
|
||||
adjustRomLabel.pack(side=LEFT)
|
||||
romEntry2.pack(side=LEFT, fill=X, expand=True)
|
||||
romSelectButton2.pack(side=LEFT)
|
||||
adjustRomFrame.pack(fill=X)
|
||||
|
||||
def adjustRom():
|
||||
options = {
|
||||
"heartbeep": "heartbeep",
|
||||
"heartcolor": "heartcolor",
|
||||
"menuspeed": "fastmenu",
|
||||
"owpalettes": "ow_palettes",
|
||||
"uwpalettes": "uw_palettes",
|
||||
"quickswap": "quickswap",
|
||||
"nobgm": "disablemusic"
|
||||
}
|
||||
guiargs = Namespace()
|
||||
for option in options:
|
||||
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.sprite = self.sprite
|
||||
try:
|
||||
adjust(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")
|
||||
|
||||
adjustButton = Button(self.frames["bottomAdjustFrame"], text='Adjust Rom', command=adjustRom)
|
||||
adjustButton.pack(side=BOTTOM, padx=(5, 0))
|
||||
|
||||
return self,settings
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "gui.custom" package
|
||||
@@ -0,0 +1,55 @@
|
||||
from tkinter import ttk, Frame, N, LEFT, VERTICAL, Y
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
import classes.constants as CONST
|
||||
|
||||
|
||||
def custom_page(top, parent):
|
||||
# Custom Item Pool
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
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
|
||||
|
||||
def create_vertical_rule(num=1):
|
||||
for i in range(0,num):
|
||||
ttk.Separator(self, orient=VERTICAL).pack(side=LEFT, anchor=N, fill=Y)
|
||||
|
||||
def validation(P):
|
||||
if str.isdigit(P) or P == "":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
vcmd=(self.register(validation), '%P')
|
||||
|
||||
# Custom Item Pool options
|
||||
self.customWidgets = {}
|
||||
|
||||
# Custom Item Pool option sections
|
||||
self.frames = {}
|
||||
create_list_frame(self, "itemList1")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self, "itemList2")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self, "itemList3")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self, "itemList4")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self, "itemList5")
|
||||
|
||||
with open(os.path.join("resources", "app", "gui", "custom", "overview", "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.customWidgets[key] = dictWidgets[key]
|
||||
|
||||
for i, key in enumerate(CONST.CUSTOMITEMS):
|
||||
self.customWidgets[key].storageVar.set(top.settings["customitemarray"][i])
|
||||
|
||||
return self
|
||||
@@ -0,0 +1,74 @@
|
||||
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])
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "gui.randomize" package
|
||||
@@ -0,0 +1,38 @@
|
||||
from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
def dungeon_page(parent):
|
||||
# Dungeon Shuffle
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Dungeon Shuffle options
|
||||
self.widgets = {}
|
||||
|
||||
# Dungeon Shuffle option sections
|
||||
self.frames = {}
|
||||
self.frames["keysanity"] = Frame(self)
|
||||
self.frames["keysanity"].pack(anchor=W)
|
||||
|
||||
## Dungeon Item Shuffle
|
||||
mscbLabel = Label(self.frames["keysanity"], text="Shuffle: ")
|
||||
mscbLabel.pack(side=LEFT)
|
||||
|
||||
with open(os.path.join("resources","app","gui","randomize","dungeon","keysanity.json")) as keysanityItems:
|
||||
myDict = json.load(keysanityItems)
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
return self
|
||||
@@ -0,0 +1,63 @@
|
||||
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
|
||||
@@ -0,0 +1,29 @@
|
||||
from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
def entrando_page(parent):
|
||||
# Entrance Randomizer
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Entrance Randomizer options
|
||||
self.widgets = {}
|
||||
|
||||
# Entrance Randomizer option sections
|
||||
self.frames = {}
|
||||
self.frames["widgets"] = Frame(self)
|
||||
self.frames["widgets"].pack(anchor=W)
|
||||
|
||||
with open(os.path.join("resources","app","gui","randomize","entrando","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)
|
||||
|
||||
return self
|
||||
@@ -0,0 +1,78 @@
|
||||
from tkinter import ttk, IntVar, StringVar, Button, Checkbutton, Entry, Frame, Label, OptionMenu, E, W, LEFT, RIGHT
|
||||
from functools import partial
|
||||
import classes.SpriteSelector as spriteSelector
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
def gameoptions_page(top, parent):
|
||||
# Game Options
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Game Options options
|
||||
self.widgets = {}
|
||||
|
||||
# Game Options option sections
|
||||
self.frames = {}
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
self.frames["leftRomOptionsFrame"] = Frame(self)
|
||||
self.frames["rightRomOptionsFrame"] = Frame(self)
|
||||
self.frames["leftRomOptionsFrame"].pack(side=LEFT)
|
||||
self.frames["rightRomOptionsFrame"].pack(side=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():
|
||||
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)
|
||||
|
||||
## Sprite selection
|
||||
spriteDialogFrame = Frame(self.frames["leftRomOptionsFrame"])
|
||||
baseSpriteLabel = Label(spriteDialogFrame, text='Sprite:')
|
||||
|
||||
self.widgets["sprite"] = {}
|
||||
self.widgets["sprite"]["spriteObject"] = None
|
||||
self.widgets["sprite"]["spriteNameVar"] = StringVar()
|
||||
|
||||
self.widgets["sprite"]["spriteNameVar"].set('(unchanged)')
|
||||
spriteEntry = Label(spriteDialogFrame, textvariable=self.widgets["sprite"]["spriteNameVar"])
|
||||
|
||||
def sprite_setter(spriteObject):
|
||||
self.widgets["sprite"]["spriteObject"] = spriteObject
|
||||
|
||||
def sprite_select():
|
||||
spriteSelector.SpriteSelector(parent, partial(set_sprite, spriteSetter=sprite_setter,
|
||||
spriteNameVar=self.widgets["sprite"]["spriteNameVar"],
|
||||
randomSpriteVar=top.randomSprite))
|
||||
|
||||
spriteSelectButton = Button(spriteDialogFrame, text='...', command=sprite_select)
|
||||
|
||||
baseSpriteLabel.pack(side=LEFT)
|
||||
spriteEntry.pack(side=LEFT)
|
||||
spriteSelectButton.pack(side=LEFT)
|
||||
spriteDialogFrame.pack(anchor=E)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def set_sprite(sprite_param, random_sprite=False, spriteSetter=None, spriteNameVar=None, randomSpriteVar=None):
|
||||
if sprite_param is None or not sprite_param.valid:
|
||||
if spriteSetter:
|
||||
spriteSetter(None)
|
||||
if spriteNameVar is not None:
|
||||
spriteNameVar.set('(unchanged)')
|
||||
else:
|
||||
if spriteSetter:
|
||||
spriteSetter(sprite_param)
|
||||
if spriteNameVar is not None:
|
||||
spriteNameVar.set(sprite_param.name)
|
||||
if randomSpriteVar:
|
||||
randomSpriteVar.set(random_sprite)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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
|
||||
@@ -0,0 +1,35 @@
|
||||
from tkinter import ttk, IntVar, StringVar, Checkbutton, Frame, Label, OptionMenu, E, W, LEFT, RIGHT
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
def item_page(parent):
|
||||
# Item Randomizer
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
# Item Randomizer options
|
||||
self.widgets = {}
|
||||
|
||||
# Item Randomizer option sections
|
||||
self.frames = {}
|
||||
|
||||
self.frames["checkboxes"] = Frame(self)
|
||||
self.frames["checkboxes"].pack(anchor=W)
|
||||
|
||||
self.frames["leftItemFrame"] = Frame(self)
|
||||
self.frames["rightItemFrame"] = Frame(self)
|
||||
self.frames["leftItemFrame"].pack(side=LEFT)
|
||||
self.frames["rightItemFrame"].pack(side=RIGHT)
|
||||
|
||||
with open(os.path.join("resources","app","gui","randomize","item","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)
|
||||
|
||||
return self
|
||||
@@ -0,0 +1,38 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
# do nothing, just exist to make "gui.startinventory" package
|
||||
@@ -0,0 +1,63 @@
|
||||
from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, LEFT, RIGHT, X, VERTICAL, Y
|
||||
import gui.widgets as widgets
|
||||
import json
|
||||
import os
|
||||
|
||||
import classes.constants as CONST
|
||||
|
||||
def startinventory_page(top,parent):
|
||||
# Starting Inventory
|
||||
self = ttk.Frame(parent)
|
||||
|
||||
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
|
||||
|
||||
def create_vertical_rule(num=1):
|
||||
for i in range(0,num):
|
||||
ttk.Separator(self, orient=VERTICAL).pack(side=LEFT, anchor=N, fill=Y)
|
||||
|
||||
def validation(P):
|
||||
if str.isdigit(P) or P == "":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
vcmd=(self.register(validation), '%P')
|
||||
|
||||
# Starting Inventory options
|
||||
self.startingWidgets = {}
|
||||
|
||||
# Starting Inventory option sections
|
||||
self.frames = {}
|
||||
create_list_frame(self,"itemList1")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self,"itemList2")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self,"itemList3")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self,"itemList4")
|
||||
create_vertical_rule(2)
|
||||
create_list_frame(self,"itemList5")
|
||||
|
||||
with open(os.path.join("resources","app","gui","custom","overview","widgets.json")) as widgetDefns:
|
||||
myDict = json.load(widgetDefns)
|
||||
for key in CONST.CANTSTARTWITH:
|
||||
for num in range(1, 5 + 1):
|
||||
thisList = "itemList" + str(num)
|
||||
if key in myDict[thisList]:
|
||||
del myDict[thisList][key]
|
||||
for framename,theseWidgets in myDict.items():
|
||||
dictWidgets = widgets.make_widgets_from_dict(self, theseWidgets, self.frames[framename])
|
||||
for key in dictWidgets:
|
||||
self.startingWidgets[key] = dictWidgets[key]
|
||||
|
||||
for key in CONST.CUSTOMITEMS:
|
||||
if key not in CONST.CANTSTARTWITH:
|
||||
val = 0
|
||||
if key in top.settings["startinventoryarray"]:
|
||||
val = top.settings["startinventoryarray"][key]
|
||||
self.startingWidgets[key].storageVar.set(val)
|
||||
|
||||
return self
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, RIGHT, X
|
||||
|
||||
class Empty():
|
||||
pass
|
||||
|
||||
class mySpinbox(Spinbox):
|
||||
def __init__(self, *args, **kwargs):
|
||||
Spinbox.__init__(self, *args, **kwargs)
|
||||
self.bind('<MouseWheel>', self.mouseWheel)
|
||||
self.bind('<Button-4>', self.mouseWheel)
|
||||
self.bind('<Button-5>', self.mouseWheel)
|
||||
|
||||
def mouseWheel(self, event):
|
||||
if event.num == 5 or event.delta == -120:
|
||||
self.invoke('buttondown')
|
||||
elif event.num == 4 or event.delta == 120:
|
||||
self.invoke('buttonup')
|
||||
|
||||
def make_checkbox(self, parent, label, storageVar, manager, managerAttrs):
|
||||
self = Frame(parent, name="checkframe-" + label.lower())
|
||||
self.storageVar = storageVar
|
||||
self.checkbox = Checkbutton(self, text=label, variable=self.storageVar, name="checkbox-" + label.lower())
|
||||
if managerAttrs is not None:
|
||||
self.checkbox.pack(managerAttrs)
|
||||
else:
|
||||
self.checkbox.pack()
|
||||
return self
|
||||
|
||||
def make_selectbox(self, parent, label, options, storageVar, manager, managerAttrs):
|
||||
def change_storage(*args):
|
||||
self.storageVar.set(options[self.labelVar.get()])
|
||||
def change_selected(*args):
|
||||
keys = options.keys()
|
||||
vals = options.values()
|
||||
keysList = list(keys)
|
||||
valsList = list(vals)
|
||||
self.labelVar.set(keysList[valsList.index(str(self.storageVar.get()))])
|
||||
self = Frame(parent, name="selectframe-" + label.lower())
|
||||
self.storageVar = storageVar
|
||||
self.storageVar.trace_add("write",change_selected)
|
||||
self.labelVar = StringVar()
|
||||
self.labelVar.trace_add("write",change_storage)
|
||||
self.label = Label(self, text=label)
|
||||
if managerAttrs is not None and "label" in managerAttrs:
|
||||
self.label.pack(managerAttrs["label"])
|
||||
else:
|
||||
self.label.pack()
|
||||
self.selectbox = OptionMenu(self, self.labelVar, *options.keys())
|
||||
self.selectbox.config(width=20)
|
||||
self.labelVar.set(managerAttrs["default"] if "default" in managerAttrs else list(options.keys())[0])
|
||||
if managerAttrs is not None and "selectbox" in managerAttrs:
|
||||
self.selectbox.pack(managerAttrs["selectbox"])
|
||||
else:
|
||||
self.selectbox.pack()
|
||||
return self
|
||||
|
||||
def make_spinbox(self, parent, label, storageVar, manager, managerAttrs):
|
||||
self = Frame(parent, name="spinframe-" + label.lower())
|
||||
self.storageVar = storageVar
|
||||
self.label = Label(self, text=label)
|
||||
if managerAttrs is not None and "label" in managerAttrs:
|
||||
self.label.pack(managerAttrs["label"])
|
||||
else:
|
||||
self.label.pack()
|
||||
fromNum = 1
|
||||
toNum = 100
|
||||
if "spinbox" in managerAttrs:
|
||||
if "from" in managerAttrs:
|
||||
fromNum = managerAttrs["spinbox"]["from"]
|
||||
if "to" in managerAttrs:
|
||||
toNum = managerAttrs["spinbox"]["to"]
|
||||
self.spinbox = mySpinbox(self, from_=fromNum, to=toNum, width=5, textvariable=self.storageVar, name="spinbox-" + label.lower())
|
||||
if managerAttrs is not None and "spinbox" in managerAttrs:
|
||||
self.spinbox.pack(managerAttrs["spinbox"])
|
||||
else:
|
||||
self.spinbox.pack()
|
||||
return self
|
||||
|
||||
def make_textbox(self, parent, label, storageVar, manager, managerAttrs):
|
||||
widget = Empty()
|
||||
widget.storageVar = storageVar
|
||||
widget.label = Label(parent, text=label)
|
||||
widget.textbox = Entry(parent, justify=RIGHT, textvariable=widget.storageVar, width=3)
|
||||
if "default" in managerAttrs:
|
||||
widget.storageVar.set(managerAttrs["default"])
|
||||
|
||||
# grid
|
||||
if manager == "grid":
|
||||
widget.label.grid(managerAttrs["label"] if managerAttrs is not None and "label" in managerAttrs else None, row=parent.thisRow, column=parent.thisCol)
|
||||
parent.thisCol += 1
|
||||
widget.textbox.grid(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None, row=parent.thisRow, column=parent.thisCol)
|
||||
parent.thisRow += 1
|
||||
parent.thisCol = 0
|
||||
|
||||
# pack
|
||||
elif manager == "pack":
|
||||
widget.label.pack(managerAttrs["label"] if managerAttrs is not None and "label" in managerAttrs else None)
|
||||
widget.textbox.pack(managerAttrs["textbox"] if managerAttrs is not None and "textbox" in managerAttrs else None)
|
||||
return widget
|
||||
|
||||
|
||||
def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(), options=None):
|
||||
widget = None
|
||||
if manager is None:
|
||||
manager = "pack"
|
||||
thisStorageVar = storageVar
|
||||
if isinstance(storageVar,str):
|
||||
if storageVar == "int" or storageVar == "integer":
|
||||
thisStorageVar = IntVar()
|
||||
elif storageVar == "str" or storageVar == "string":
|
||||
thisStorageVar = StringVar()
|
||||
|
||||
if type == "checkbox":
|
||||
if thisStorageVar is None:
|
||||
thisStorageVar = IntVar()
|
||||
widget = make_checkbox(self, parent, label, thisStorageVar, manager, managerAttrs)
|
||||
elif type == "selectbox":
|
||||
if thisStorageVar is None:
|
||||
thisStorageVar = StringVar()
|
||||
widget = make_selectbox(self, parent, label, options, thisStorageVar, manager, managerAttrs)
|
||||
elif type == "spinbox":
|
||||
if thisStorageVar is None:
|
||||
thisStorageVar = StringVar()
|
||||
widget = make_spinbox(self, parent, label, thisStorageVar, manager, managerAttrs)
|
||||
elif type == "textbox":
|
||||
if thisStorageVar is None:
|
||||
thisStorageVar = StringVar()
|
||||
widget = make_textbox(self, parent, label, thisStorageVar, manager, managerAttrs)
|
||||
widget.type = type
|
||||
return widget
|
||||
|
||||
def make_widget_from_dict(self, defn, parent):
|
||||
type = defn["type"] if "type" in defn else None
|
||||
label = defn["label"]["text"] if "label" in defn and "text" in defn["label"] else ""
|
||||
manager = defn["manager"] if "manager" in defn else None
|
||||
managerAttrs = defn["managerAttrs"] if "managerAttrs" in defn else None
|
||||
options = defn["options"] if "options" in defn else None
|
||||
widget = make_widget(self, type, parent, label, None, manager, managerAttrs, options)
|
||||
return widget
|
||||
|
||||
def make_widgets_from_dict(self, defns, parent):
|
||||
widgets = {}
|
||||
for key,defn in defns.items():
|
||||
widgets[key] = make_widget_from_dict(self, defn, parent)
|
||||
return widgets
|
||||
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"checkboxes": {
|
||||
"nobgm": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Disable Music & MSU-1"
|
||||
}
|
||||
},
|
||||
"quickswap": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "L/R Quickswapping"
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftAdjustFrame": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightAdjustFrame": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"owpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Overworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
"Blackout": "blackout"
|
||||
}
|
||||
},
|
||||
"uwpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Underworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
"Blackout": "blackout"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,935 @@
|
||||
{
|
||||
"itemList1": {
|
||||
"bow": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bow"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressivebow": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Progressive Bow"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"boomerang": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Blue Boomerang"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"redmerang": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Red Boomerang"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"hookshot": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Hookshot"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"mushroom": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Mushroom"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"powder": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Magic Powder"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"firerod": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Fire Rod"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"icerod": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Ice Rod"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"bombos": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bombos"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"ether": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Ether"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"quake": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Quake"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"lamp": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Lamp"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"hammer": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Hammer"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"shovel": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Shovel"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList2": {
|
||||
"flute": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Flute"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"bugnet": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bug Net"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"book": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Book"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"bottle": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bottle"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 4
|
||||
}
|
||||
},
|
||||
"somaria": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Cane of Somaria"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"byrna": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Cane of Byrna"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"cape": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Magic Cape"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"mirror": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Magic Mirror"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"boots": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Pegasus Boots"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"powerglove": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Power Glove"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"titansmitt": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Titan's Mitt"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressiveglove": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Progressive Glove"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"flippers": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Flippers"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"pearl": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Moon Pearl"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"heartpiece": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Piece of Heart"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 24
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList3": {
|
||||
"heartcontainer": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Heart Container"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 10
|
||||
}
|
||||
},
|
||||
"sancheart": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Sanctuary Heart"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"sword1": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Fighters' Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"sword2": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Master Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"sword3": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Tempered Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"sword4": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Golden Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressivesword": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Progressive Sword"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 4
|
||||
}
|
||||
},
|
||||
"shield1": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Fighters' Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"shield2": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Fire Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"shield3": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Mirror Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressiveshield": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Progressive Shield"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 3
|
||||
}
|
||||
},
|
||||
"mail2": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Blue Mail"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"mail3": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Red Mail"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"progressivemail": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Progressive Mail"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"halfmagic": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Half Magic"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList4": {
|
||||
"quartermagic": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Quarter Magic"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"bombsplus5": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bomb Cap +5"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"bombsplus10": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bomb Cap +10"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"arrowsplus5": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Arrow Cap +5"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"arrowsplus10": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Arrow Cap +10"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"arrow1": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Arrow (1)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"arrow10": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Arrow (10)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 12
|
||||
}
|
||||
},
|
||||
"bomb1": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bomb (1)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"bomb3": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bomb (3)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 16
|
||||
}
|
||||
},
|
||||
"bomb10": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Bomb (10)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"rupee1": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupee (1)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 2
|
||||
}
|
||||
},
|
||||
"rupee5": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupee (5)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 4
|
||||
}
|
||||
},
|
||||
"rupee20": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupee (20)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 28
|
||||
}
|
||||
},
|
||||
"rupee50": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupee (50)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 7
|
||||
}
|
||||
},
|
||||
"rupee100": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupee (100)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"itemList5": {
|
||||
"rupee300": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupee (300)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 5
|
||||
}
|
||||
},
|
||||
"blueclock": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Blue Clock"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"greenclock": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Green Clock"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"redclock": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Red Clock"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"silversupgrade": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Silver Arrows Upgrade"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"generickeys": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Generic Keys"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"triforcepieces": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Triforce Pieces"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"triforcepiecesgoal": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Triforce Pieces Goal"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"triforce": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Triforce (win game)"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"rupoor": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupoor"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"rupoorcost": {
|
||||
"type": "textbox",
|
||||
"label": {
|
||||
"text": "Rupoor Cost"
|
||||
},
|
||||
"manager": "grid",
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"sticky": "w"
|
||||
},
|
||||
"default": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"dungeon_counters": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Dungeon Chest Counters"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Off"
|
||||
},
|
||||
"options": {
|
||||
"Off": "off",
|
||||
"On": "on",
|
||||
"On Compass Pickup": "pickup"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"checkboxes": {
|
||||
"potshuffle": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Pot Shuffle"
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftEnemizerFrame": {
|
||||
"enemyshuffle": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Enemy Shuffle"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "none",
|
||||
"Shuffled": "shuffled",
|
||||
"Chaos": "chaos"
|
||||
}
|
||||
},
|
||||
"bossshuffle": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Boss Shuffle"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "none",
|
||||
"Basic": "basic",
|
||||
"Shuffled": "shuffled",
|
||||
"Chaos": "chaos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightEnemizerFrame": {
|
||||
"enemydamage": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Enemy Damage"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Vanilla": "default",
|
||||
"Shuffled": "shuffled",
|
||||
"Chaos": "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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"owpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Overworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
"Blackout": "blackout"
|
||||
}
|
||||
},
|
||||
"uwpalettes": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Underworld Palettes"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Default": "default",
|
||||
"Random": "random",
|
||||
"Blackout": "blackout"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
{
|
||||
"checkboxes": {
|
||||
"retro": {
|
||||
"type": "checkbox",
|
||||
"label": {
|
||||
"text": "Retro mode (universal keys)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"leftItemFrame": {
|
||||
"worldstate": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "World State"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
},
|
||||
"default": "Open"
|
||||
},
|
||||
"options": {
|
||||
"Standard": "standard",
|
||||
"Open": "open",
|
||||
"Inverted": "inverted"
|
||||
}
|
||||
},
|
||||
"logiclevel": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Logic Level"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"No Glitches": "noglitches",
|
||||
"Minor Glitches": "minorglitches",
|
||||
"No Logic": "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"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"weapons": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Weapons"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Randomized": "random",
|
||||
"Assured": "assured",
|
||||
"Swordless": "swordless",
|
||||
"Vanilla": "vanilla"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rightItemFrame": {
|
||||
"itempool": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Item Pool"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Normal": "normal",
|
||||
"Hard": "hard",
|
||||
"Expert": "expert"
|
||||
}
|
||||
},
|
||||
"itemfunction": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Item Functionality"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"Normal": "normal",
|
||||
"Hard": "hard",
|
||||
"Expert": "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"
|
||||
}
|
||||
},
|
||||
"progressives": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Progressive Items"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"On": "on",
|
||||
"Off": "off",
|
||||
"Random": "random"
|
||||
}
|
||||
},
|
||||
"accessibility": {
|
||||
"type": "selectbox",
|
||||
"label": {
|
||||
"text": "Accessibility"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"selectbox": {
|
||||
"side": "right"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"100% Inventory": "items",
|
||||
"100% Locations": "locations",
|
||||
"Beatable": "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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"worlds": {
|
||||
"type": "spinbox",
|
||||
"label": {
|
||||
"text": "Worlds"
|
||||
},
|
||||
"managerAttrs": {
|
||||
"label": {
|
||||
"side": "left"
|
||||
},
|
||||
"spinbox": {
|
||||
"side": "right"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user