Merge branch 'translation' into ci-dev
This commit is contained in:
@@ -4,6 +4,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
from collections import OrderedDict, deque, defaultdict
|
from collections import OrderedDict, deque, defaultdict
|
||||||
|
|
||||||
|
from source.classes.BabelFish import BabelFish
|
||||||
from EntranceShuffle import door_addresses
|
from EntranceShuffle import door_addresses
|
||||||
from _vendor.collections_extended import bag
|
from _vendor.collections_extended import bag
|
||||||
from Utils import int16_as_bytes
|
from Utils import int16_as_bytes
|
||||||
@@ -71,6 +72,7 @@ class World(object):
|
|||||||
self.key_logic = {}
|
self.key_logic = {}
|
||||||
self.pool_adjustment = {}
|
self.pool_adjustment = {}
|
||||||
self.key_layout = defaultdict(dict)
|
self.key_layout = defaultdict(dict)
|
||||||
|
self.fish = BabelFish()
|
||||||
|
|
||||||
for player in range(1, players + 1):
|
for player in range(1, players + 1):
|
||||||
# If World State is Retro, set to Open and set Retro flag
|
# If World State is Retro, set to Open and set Retro flag
|
||||||
@@ -1732,43 +1734,60 @@ class Spoiler(object):
|
|||||||
outfile.write('\n\nDoors:\n\n')
|
outfile.write('\n\nDoors:\n\n')
|
||||||
outfile.write('\n'.join(
|
outfile.write('\n'.join(
|
||||||
['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '',
|
['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '',
|
||||||
entry['entrance'],
|
self.world.fish.translate("meta","doors",entry['entrance']),
|
||||||
'<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>',
|
'<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>',
|
||||||
entry['exit'],
|
self.world.fish.translate("meta","doors",entry['exit']),
|
||||||
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] == 'crossed' else '') for
|
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] == 'crossed' else '') for
|
||||||
entry in self.doors.values()]))
|
entry in self.doors.values()]))
|
||||||
if self.doorTypes:
|
if self.doorTypes:
|
||||||
|
# doorNames: For some reason these come in combined, somehow need to split on the thing to translate
|
||||||
|
# doorTypes: Small Key, Bombable, Bonkable
|
||||||
outfile.write('\n\nDoor Types:\n\n')
|
outfile.write('\n\nDoor Types:\n\n')
|
||||||
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['doorNames'], entry['type']) for entry in self.doorTypes.values()]))
|
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', self.world.fish.translate("meta","doors",entry['doorNames']), self.world.fish.translate("meta","doorTypes",entry['type'])) for entry in self.doorTypes.values()]))
|
||||||
if self.entrances:
|
if self.entrances:
|
||||||
|
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
|
||||||
outfile.write('\n\nEntrances:\n\n')
|
outfile.write('\n\nEntrances:\n\n')
|
||||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()]))
|
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","entrances",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","entrances",entry['exit'])) for entry in self.entrances.values()]))
|
||||||
outfile.write('\n\nMedallions:\n')
|
outfile.write('\n\nMedallions:\n')
|
||||||
for dungeon, medallion in self.medallions.items():
|
for dungeon, medallion in self.medallions.items():
|
||||||
outfile.write(f'\n{dungeon}: {medallion}')
|
outfile.write(f'\n{dungeon}: {medallion} Medallion')
|
||||||
if self.startinventory:
|
if self.startinventory:
|
||||||
outfile.write('\n\nStarting Inventory:\n\n')
|
outfile.write('\n\nStarting Inventory:\n\n')
|
||||||
outfile.write('\n'.join(self.startinventory))
|
outfile.write('\n'.join(self.startinventory))
|
||||||
outfile.write('\n\nLocations:\n\n')
|
|
||||||
outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
|
||||||
outfile.write('\n\nShops:\n\n')
|
|
||||||
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
|
|
||||||
outfile.write('\n\nPlaythrough:\n\n')
|
|
||||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
|
||||||
if self.unreachables:
|
|
||||||
outfile.write('\n\nUnreachable Items:\n\n')
|
|
||||||
outfile.write('\n'.join(['%s: %s' % (unreachable.item, unreachable) for unreachable in self.unreachables]))
|
|
||||||
outfile.write('\n\nPaths:\n\n')
|
|
||||||
|
|
||||||
|
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||||
|
# items: Item names
|
||||||
|
outfile.write('\n\nLocations:\n\n')
|
||||||
|
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","locations",location), self.world.fish.translate("meta","items",item)) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
||||||
|
|
||||||
|
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||||
|
# items: Item names
|
||||||
|
outfile.write('\n\nShops:\n\n')
|
||||||
|
outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta","locations",shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta","items",item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
|
||||||
|
|
||||||
|
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||||
|
# items: Item names
|
||||||
|
outfile.write('\n\nPlaythrough:\n\n')
|
||||||
|
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (self.world.fish.translate("meta","locations",location), self.world.fish.translate("meta","items",item)) for (location, item) in sphere.items()] if sphere_nr != '0' else [f' {item}' for item in sphere])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||||
|
if self.unreachables:
|
||||||
|
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||||
|
# items: Item names
|
||||||
|
outfile.write('\n\nUnreachable Items:\n\n')
|
||||||
|
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","items",unreachable.item), self.world.fish.translate("meta","locations",unreachable)) for unreachable in self.unreachables]))
|
||||||
|
|
||||||
|
# rooms: Change up room names; only if it's got no locations in it
|
||||||
|
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
|
||||||
|
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
||||||
|
outfile.write('\n\nPaths:\n\n')
|
||||||
path_listings = []
|
path_listings = []
|
||||||
for location, path in sorted(self.paths.items()):
|
for location, path in sorted(self.paths.items()):
|
||||||
path_lines = []
|
path_lines = []
|
||||||
for region, exit in path:
|
for region, exit in path:
|
||||||
if exit is not None:
|
if exit is not None:
|
||||||
path_lines.append("{} -> {}".format(region, exit))
|
path_lines.append("{} -> {}".format(self.world.fish.translate("meta","rooms",region), self.world.fish.translate("meta","entrances",exit)))
|
||||||
else:
|
else:
|
||||||
path_lines.append(region)
|
path_lines.append(self.world.fish.translate("meta","rooms",region))
|
||||||
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
|
path_listings.append("{}\n {}".format(self.world.fish.translate("meta","locations",location), "\n => ".join(path_lines)))
|
||||||
|
|
||||||
outfile.write('\n'.join(path_listings))
|
outfile.write('\n'.join(path_listings))
|
||||||
|
|
||||||
|
|||||||
446
CLI.py
446
CLI.py
@@ -8,9 +8,8 @@ import textwrap
|
|||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from Main import main
|
|
||||||
|
|
||||||
import source.classes.constants as CONST
|
import source.classes.constants as CONST
|
||||||
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
|
|
||||||
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||||
@@ -25,271 +24,56 @@ def parse_arguments(argv, no_defaults=False):
|
|||||||
# get settings
|
# get settings
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
|
||||||
|
lang = "en"
|
||||||
|
if argv is not None:
|
||||||
|
priority = get_args_priority(None, None, argv)
|
||||||
|
if "load" in priority:
|
||||||
|
priority = priority["load"]
|
||||||
|
if "lang" in priority:
|
||||||
|
lang = priority["lang"]
|
||||||
|
|
||||||
|
fish = BabelFish(lang=lang)
|
||||||
|
|
||||||
# we need to know how many players we have first
|
# we need to know how many players we have first
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
|
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
|
||||||
multiargs, _ = parser.parse_known_args(argv)
|
multiargs, _ = parser.parse_known_args(argv)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
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'],
|
# get args
|
||||||
help='''\
|
args = []
|
||||||
Select Enforcement of Item Requirements. (default: %(default)s)
|
with open(os.path.join("resources","app","cli","args.json")) as argsFile:
|
||||||
No Glitches:
|
args = json.load(argsFile)
|
||||||
Minor Glitches: May require Fake Flippers, Bunny Revival
|
for arg in args:
|
||||||
and Dark Room Navigation.
|
argdata = args[arg]
|
||||||
No Logic: Distribute items without regard for
|
argname = "--" + arg
|
||||||
item requirements.
|
argatts = {}
|
||||||
''')
|
argatts["help"] = "(default: %(default)s)"
|
||||||
parser.add_argument('--mode', default=defval(settings["mode"]), const='open', nargs='?', choices=['standard', 'open', 'inverted'],
|
if "action" in argdata:
|
||||||
help='''\
|
argatts["action"] = argdata["action"]
|
||||||
Select game mode. (default: %(default)s)
|
if "choices" in argdata:
|
||||||
Open: World starts with Zelda rescued.
|
argatts["choices"] = argdata["choices"]
|
||||||
Standard: Fixes Hyrule Castle Secret Entrance and Front Door
|
argatts["const"] = argdata["choices"][0]
|
||||||
but may lead to weird rain state issues if you exit
|
argatts["default"] = argdata["choices"][0]
|
||||||
through the Hyrule Castle side exits before rescuing
|
argatts["nargs"] = "?"
|
||||||
Zelda in a full shuffle.
|
elif arg in settings:
|
||||||
Inverted: Starting locations are Dark Sanctuary in West Dark
|
argatts["default"] = defval(settings[arg] != 0) if "type" in argdata and argdata["type"] == "bool" else defval(settings[arg])
|
||||||
World or at Link's House, which is shuffled freely.
|
arghelp = fish.translate("cli","help",arg)
|
||||||
Requires the moon pearl to be Link in the Light World
|
if "help" in argdata and argdata["help"] == "suppress":
|
||||||
instead of a bunny.
|
argatts["help"] = argparse.SUPPRESS
|
||||||
''')
|
elif not isinstance(arghelp,str):
|
||||||
parser.add_argument('--swords', default=defval(settings["swords"]), const='random', nargs='?', choices= ['random', 'assured', 'swordless', 'vanilla'],
|
argatts["help"] = '\n'.join(arghelp).replace("\\'","'")
|
||||||
help='''\
|
parser.add_argument(argname,**argatts)
|
||||||
Select sword placement. (default: %(default)s)
|
|
||||||
Random: All swords placed randomly.
|
parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help="\n".join(fish.translate("cli","help","seed")), type=int)
|
||||||
Assured: Start game with a sword already.
|
parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli","help","count")), type=int)
|
||||||
Swordless: No swords. Curtains in Skull Woods and Agahnim\'s
|
parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS)
|
||||||
Tower are removed, Agahnim\'s Tower barrier can be
|
|
||||||
destroyed with hammer. Misery Mire and Turtle Rock
|
|
||||||
can be opened without a sword. Hammer damages Ganon.
|
|
||||||
Ether and Bombos Tablet can be activated with Hammer
|
|
||||||
(and Book). Bombos pads have been added in Ice
|
|
||||||
Palace, to allow for an alternative to firerod.
|
|
||||||
Vanilla: Swords are in vanilla locations.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--goal', default=defval(settings["goal"]), const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'],
|
|
||||||
help='''\
|
|
||||||
Select completion goal. (default: %(default)s)
|
|
||||||
Ganon: Collect all crystals, beat Agahnim 2 then
|
|
||||||
defeat Ganon.
|
|
||||||
Crystals: Collect all crystals then defeat Ganon.
|
|
||||||
Pedestal: Places the Triforce at the Master Sword Pedestal.
|
|
||||||
All Dungeons: Collect all crystals, pendants, beat both
|
|
||||||
Agahnim fights and then defeat Ganon.
|
|
||||||
Triforce Hunt: Places 30 Triforce Pieces in the world, collect
|
|
||||||
20 of them to beat the game.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--difficulty', default=defval(settings["difficulty"]), const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
|
||||||
help='''\
|
|
||||||
Select game difficulty. Affects available itempool. (default: %(default)s)
|
|
||||||
Normal: Normal difficulty.
|
|
||||||
Hard: A harder setting with less equipment and reduced health.
|
|
||||||
Expert: A harder yet setting with minimum equipment and health.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--item_functionality', default=defval(settings["item_functionality"]), const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
|
|
||||||
help='''\
|
|
||||||
Select limits on item functionality to increase difficulty. (default: %(default)s)
|
|
||||||
Normal: Normal functionality.
|
|
||||||
Hard: Reduced functionality.
|
|
||||||
Expert: Greatly reduced functionality.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--timer', default=defval(settings["timer"]), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'],
|
|
||||||
help='''\
|
|
||||||
Select game timer setting. Affects available itempool. (default: %(default)s)
|
|
||||||
None: No timer.
|
|
||||||
Display: Displays a timer but does not affect
|
|
||||||
the itempool.
|
|
||||||
Timed: Starts with clock at zero. Green Clocks
|
|
||||||
subtract 4 minutes (Total: 20), Blue Clocks
|
|
||||||
subtract 2 minutes (Total: 10), Red Clocks add
|
|
||||||
2 minutes (Total: 10). Winner is player with
|
|
||||||
lowest time at the end.
|
|
||||||
Timed OHKO: Starts clock at 10 minutes. Green Clocks add
|
|
||||||
5 minutes (Total: 25). As long as clock is at 0,
|
|
||||||
Link will die in one hit.
|
|
||||||
OHKO: Like Timed OHKO, but no clock items are present
|
|
||||||
and the clock is permenantly at zero.
|
|
||||||
Timed Countdown: Starts with clock at 40 minutes. Same clocks as
|
|
||||||
Timed mode. If time runs out, you lose (but can
|
|
||||||
still keep playing).
|
|
||||||
''')
|
|
||||||
parser.add_argument('--progressive', default=defval(settings["progressive"]), const='normal', nargs='?', choices=['on', 'off', 'random'],
|
|
||||||
help='''\
|
|
||||||
Select progressive equipment setting. Affects available itempool. (default: %(default)s)
|
|
||||||
On: Swords, Shields, Armor, and Gloves will
|
|
||||||
all be progressive equipment. Each subsequent
|
|
||||||
item of the same type the player finds will
|
|
||||||
upgrade that piece of equipment by one stage.
|
|
||||||
Off: Swords, Shields, Armor, and Gloves will not
|
|
||||||
be progressive equipment. Higher level items may
|
|
||||||
be found at any time. Downgrades are not possible.
|
|
||||||
Random: Swords, Shields, Armor, and Gloves will, per
|
|
||||||
category, be randomly progressive or not.
|
|
||||||
Link will die in one hit.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--algorithm', default=defval(settings["algorithm"]), const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'],
|
|
||||||
help='''\
|
|
||||||
Select item filling algorithm. (default: %(default)s
|
|
||||||
balanced: vt26 derivative that aims to strike a balance between
|
|
||||||
the overworld heavy vt25 and the dungeon heavy vt26
|
|
||||||
algorithm.
|
|
||||||
vt26: Shuffle items and place them in a random location
|
|
||||||
that it is not impossible to be in. This includes
|
|
||||||
dungeon keys and items.
|
|
||||||
vt25: Shuffle items and place them in a random location
|
|
||||||
that it is not impossible to be in.
|
|
||||||
vt21: Unbiased in its selection, but has tendency to put
|
|
||||||
Ice Rod in Turtle Rock.
|
|
||||||
vt22: Drops off stale locations after 1/3 of progress
|
|
||||||
items were placed to try to circumvent vt21\'s
|
|
||||||
shortcomings.
|
|
||||||
Freshness: Keep track of stale locations (ones that cannot be
|
|
||||||
reached yet) and decrease likeliness of selecting
|
|
||||||
them the more often they were found unreachable.
|
|
||||||
Flood: Push out items starting from Link\'s House and
|
|
||||||
slightly biased to placing progression items with
|
|
||||||
less restrictions.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--shuffle', default=defval(settings["shuffle"]), const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'],
|
|
||||||
help='''\
|
|
||||||
Select Entrance Shuffling Algorithm. (default: %(default)s)
|
|
||||||
Full: Mix cave and dungeon entrances freely while limiting
|
|
||||||
multi-entrance caves to one world.
|
|
||||||
Simple: Shuffle Dungeon Entrances/Exits between each other
|
|
||||||
and keep all 4-entrance dungeons confined to one
|
|
||||||
location. All caves outside of death mountain are
|
|
||||||
shuffled in pairs and matched by original type.
|
|
||||||
Restricted: Use Dungeons shuffling from Simple but freely
|
|
||||||
connect remaining entrances.
|
|
||||||
Crossed: Mix cave and dungeon entrances freely while allowing
|
|
||||||
caves to cross between worlds.
|
|
||||||
Insanity: Decouple entrances and exits from each other and
|
|
||||||
shuffle them freely. Caves that used to be single
|
|
||||||
entrance will still exit to the same location from
|
|
||||||
which they are entered.
|
|
||||||
Vanilla: All entrances are in the same locations they were
|
|
||||||
in the base game.
|
|
||||||
Legacy shuffles preserve behavior from older versions of the
|
|
||||||
entrance randomizer including significant technical limitations.
|
|
||||||
The dungeon variants only mix up dungeons and keep the rest of
|
|
||||||
the overworld vanilla.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--door_shuffle', default=defval(settings["door_shuffle"]), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed'],
|
|
||||||
help='''\
|
|
||||||
Select Door Shuffling Algorithm. (default: %(default)s)
|
|
||||||
Basic: Doors are mixed within a single dungeon.
|
|
||||||
(Not yet implemented)
|
|
||||||
Crossed: Doors are mixed between all dungeons.
|
|
||||||
(Not yet implemented)
|
|
||||||
Vanilla: All doors are connected the same way they were in the
|
|
||||||
base game.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--experimental', default=defval(settings["experimental"] != 0), help='Enable experimental features', action='store_true')
|
|
||||||
parser.add_argument('--dungeon_counters', default=defval(settings["dungeon_counters"]), help='Enable dungeon chest counters', const='off', nargs='?', choices=['off', 'on', 'pickup', 'default'])
|
|
||||||
parser.add_argument('--crystals_ganon', default=defval(settings["crystals_ganon"]), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
|
||||||
help='''\
|
|
||||||
How many crystals are needed to defeat ganon. Any other
|
|
||||||
requirements for ganon for the selected goal still apply.
|
|
||||||
This setting does not apply when the all dungeons goal is
|
|
||||||
selected. (default: %(default)s)
|
|
||||||
Random: Picks a random value between 0 and 7 (inclusive).
|
|
||||||
0-7: Number of crystals needed
|
|
||||||
''')
|
|
||||||
parser.add_argument('--crystals_gt', default=defval(settings["crystals_gt"]), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
|
||||||
help='''\
|
|
||||||
How many crystals are needed to open GT. For inverted mode
|
|
||||||
this applies to the castle tower door instead. (default: %(default)s)
|
|
||||||
Random: Picks a random value between 0 and 7 (inclusive).
|
|
||||||
0-7: Number of crystals needed
|
|
||||||
''')
|
|
||||||
parser.add_argument('--openpyramid', default=defval(settings["openpyramid"] != 0), help='''\
|
|
||||||
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it
|
|
||||||
''', action='store_true')
|
|
||||||
parser.add_argument('--rom', default=defval(settings["rom"]), help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
|
||||||
parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
|
||||||
parser.add_argument('--seed', default=defval(int(settings["seed"]) if settings["seed"] != "" and settings["seed"] is not None else None), help='Define seed number to generate.', type=int)
|
|
||||||
parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else None), help='''\
|
|
||||||
Use to batch generate multiple seeds with same settings.
|
|
||||||
If --seed is provided, it will be used for the first seed, then
|
|
||||||
used to derive the next seed (i.e. generating 10 seeds with
|
|
||||||
--seed given will produce the same 10 (different) roms each
|
|
||||||
time).
|
|
||||||
''', type=int)
|
|
||||||
parser.add_argument('--fastmenu', default=defval(settings["fastmenu"]), const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'],
|
|
||||||
help='''\
|
|
||||||
Select the rate at which the menu opens and closes.
|
|
||||||
(default: %(default)s)
|
|
||||||
''')
|
|
||||||
parser.add_argument('--quickswap', default=defval(settings["quickswap"] != 0), help='Enable quick item swapping with L and R.', action='store_true')
|
|
||||||
parser.add_argument('--disablemusic', default=defval(settings["disablemusic"] != 0), help='Disables game music.', action='store_true')
|
|
||||||
parser.add_argument('--mapshuffle', default=defval(settings["mapshuffle"] != 0), help='Maps are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
|
||||||
parser.add_argument('--compassshuffle', default=defval(settings["compassshuffle"] != 0), help='Compasses are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
|
||||||
parser.add_argument('--keyshuffle', default=defval(settings["keyshuffle"] != 0), help='Small Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
|
||||||
parser.add_argument('--bigkeyshuffle', default=defval(settings["bigkeyshuffle"] != 0), help='Big Keys are no longer restricted to their dungeons, but can be anywhere', action='store_true')
|
|
||||||
parser.add_argument('--keysanity', default=defval(settings["keysanity"] != 0), help=argparse.SUPPRESS, action='store_true')
|
|
||||||
parser.add_argument('--retro', default=defval(settings["retro"] != 0), help='''\
|
|
||||||
Keys are universal, shooting arrows costs rupees,
|
|
||||||
and a few other little things make this more like Zelda-1.
|
|
||||||
''', action='store_true')
|
|
||||||
parser.add_argument('--startinventory', default=defval(settings["startinventory"]), help='Specifies a list of items that will be in your starting inventory (separated by commas)')
|
|
||||||
parser.add_argument('--usestartinventory', default=defval(settings["usestartinventory"] != 0), help='Not supported.')
|
|
||||||
parser.add_argument('--custom', default=defval(settings["custom"] != 0), help='Not supported.')
|
|
||||||
parser.add_argument('--customitemarray', default={}, help='Not supported.')
|
|
||||||
parser.add_argument('--accessibility', default=defval(settings["accessibility"]), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\
|
|
||||||
Select Item/Location Accessibility. (default: %(default)s)
|
|
||||||
Items: You can reach all unique inventory items. No guarantees about
|
|
||||||
reaching all locations or all keys.
|
|
||||||
Locations: You will be able to reach every location in the game.
|
|
||||||
None: You will be able to reach enough locations to beat the game.
|
|
||||||
''')
|
|
||||||
parser.add_argument('--hints', default=defval(settings["hints"] != 0), help='''\
|
|
||||||
Make telepathic tiles and storytellers give helpful hints.
|
|
||||||
''', action='store_true')
|
|
||||||
# included for backwards compatibility
|
# 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('--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('--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('--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:
|
if multiargs.multi:
|
||||||
for player in range(1, multiargs.multi + 1):
|
for player in range(1, multiargs.multi + 1):
|
||||||
@@ -324,6 +108,7 @@ def parse_arguments(argv, no_defaults=False):
|
|||||||
def get_settings():
|
def get_settings():
|
||||||
# set default settings
|
# set default settings
|
||||||
settings = {
|
settings = {
|
||||||
|
"lang": "en",
|
||||||
"retro": False,
|
"retro": False,
|
||||||
"mode": "open",
|
"mode": "open",
|
||||||
"logic": "noglitches",
|
"logic": "noglitches",
|
||||||
@@ -378,88 +163,89 @@ def get_settings():
|
|||||||
"custom": False,
|
"custom": False,
|
||||||
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
|
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
|
||||||
|
|
||||||
"seed": None,
|
"seed": "",
|
||||||
"count": None,
|
"count": 1,
|
||||||
"startinventory": "",
|
"startinventory": "",
|
||||||
"beemizer": 0,
|
"beemizer": 0,
|
||||||
"remote_items": False,
|
"remote_items": False,
|
||||||
"race": False,
|
"race": False,
|
||||||
"customitemarray": {
|
"customitemarray": {
|
||||||
"bow": 0,
|
"bow": 0,
|
||||||
"progressivebow": 2,
|
"progressivebow": 2,
|
||||||
"boomerang": 1,
|
"boomerang": 1,
|
||||||
"redmerang": 1,
|
"redmerang": 1,
|
||||||
"hookshot": 1,
|
"hookshot": 1,
|
||||||
"mushroom": 1,
|
"mushroom": 1,
|
||||||
"powder": 1,
|
"powder": 1,
|
||||||
"firerod": 1,
|
"firerod": 1,
|
||||||
"icerod": 1,
|
"icerod": 1,
|
||||||
"bombos": 1,
|
"bombos": 1,
|
||||||
"ether": 1,
|
"ether": 1,
|
||||||
"quake": 1,
|
"quake": 1,
|
||||||
"lamp": 1,
|
"lamp": 1,
|
||||||
"hammer": 1,
|
"hammer": 1,
|
||||||
"shovel": 1,
|
"shovel": 1,
|
||||||
"flute": 1,
|
"flute": 1,
|
||||||
"bugnet": 1,
|
"bugnet": 1,
|
||||||
"book": 1,
|
"book": 1,
|
||||||
"bottle": 4,
|
"bottle": 4,
|
||||||
"somaria": 1,
|
"somaria": 1,
|
||||||
"byrna": 1,
|
"byrna": 1,
|
||||||
"cape": 1,
|
"cape": 1,
|
||||||
"mirror": 1,
|
"mirror": 1,
|
||||||
"boots": 1,
|
"boots": 1,
|
||||||
"powerglove": 0,
|
"powerglove": 0,
|
||||||
"titansmitt": 0,
|
"titansmitt": 0,
|
||||||
"progressiveglove": 2,
|
"progressiveglove": 2,
|
||||||
"flippers": 1,
|
"flippers": 1,
|
||||||
"pearl": 1,
|
"pearl": 1,
|
||||||
"heartpiece": 24,
|
"heartpiece": 24,
|
||||||
"heartcontainer": 10,
|
"heartcontainer": 10,
|
||||||
"sancheart": 1,
|
"sancheart": 1,
|
||||||
"sword1": 0,
|
"sword1": 0,
|
||||||
"sword2": 0,
|
"sword2": 0,
|
||||||
"sword3": 0,
|
"sword3": 0,
|
||||||
"sword4": 0,
|
"sword4": 0,
|
||||||
"progressivesword": 4,
|
"progressivesword": 4,
|
||||||
"shield1": 0,
|
"shield1": 0,
|
||||||
"shield2": 0,
|
"shield2": 0,
|
||||||
"shield3": 0,
|
"shield3": 0,
|
||||||
"progressiveshield": 3,
|
"progressiveshield": 3,
|
||||||
"mail2": 0,
|
"mail2": 0,
|
||||||
"mail3": 0,
|
"mail3": 0,
|
||||||
"progressivemail": 2,
|
"progressivemail": 2,
|
||||||
"halfmagic": 1,
|
"halfmagic": 1,
|
||||||
"quartermagic": 0,
|
"quartermagic": 0,
|
||||||
"bombsplus5": 0,
|
"bombsplus5": 0,
|
||||||
"bombsplus10": 0,
|
"bombsplus10": 0,
|
||||||
"arrowsplus5": 0,
|
"arrowsplus5": 0,
|
||||||
"arrowsplus10": 0,
|
"arrowsplus10": 0,
|
||||||
"arrow1": 1,
|
"arrow1": 1,
|
||||||
"arrow10": 12,
|
"arrow10": 12,
|
||||||
"bomb1": 0,
|
"bomb1": 0,
|
||||||
"bomb3": 16,
|
"bomb3": 16,
|
||||||
"bomb10": 1,
|
"bomb10": 1,
|
||||||
"rupee1": 2,
|
"rupee1": 2,
|
||||||
"rupee5": 4,
|
"rupee5": 4,
|
||||||
"rupee20": 28,
|
"rupee20": 28,
|
||||||
"rupee50": 7,
|
"rupee50": 7,
|
||||||
"rupee100": 1,
|
"rupee100": 1,
|
||||||
"rupee300": 5,
|
"rupee300": 5,
|
||||||
"blueclock": 0,
|
"blueclock": 0,
|
||||||
"greenclock": 0,
|
"greenclock": 0,
|
||||||
"redclock": 0,
|
"redclock": 0,
|
||||||
"silversupgrade": 0,
|
"silversupgrade": 0,
|
||||||
"generickeys": 0,
|
"generickeys": 0,
|
||||||
"triforcepieces": 0,
|
"triforcepieces": 0,
|
||||||
"triforcepiecesgoal": 0,
|
"triforcepiecesgoal": 0,
|
||||||
"triforce": 0,
|
"triforce": 0,
|
||||||
"rupoor": 0,
|
"rupoor": 0,
|
||||||
"rupoorcost": 10
|
"rupoorcost": 10
|
||||||
},
|
},
|
||||||
"randomSprite": False,
|
"randomSprite": False,
|
||||||
"outputpath": os.path.join("."),
|
"outputpath": os.path.join("."),
|
||||||
"saveonexit": "ask",
|
"saveonexit": "ask",
|
||||||
|
"outputname": "",
|
||||||
"startinventoryarray": {}
|
"startinventoryarray": {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ def within_dungeon(world, player):
|
|||||||
dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
|
dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
|
||||||
dungeon_builders[key].entrance_list = list(entrances_map[key])
|
dungeon_builders[key].entrance_list = list(entrances_map[key])
|
||||||
recombinant_builders = {}
|
recombinant_builders = {}
|
||||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish)
|
||||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||||
|
|
||||||
paths = determine_required_paths(world, player)
|
paths = determine_required_paths(world, player)
|
||||||
@@ -326,15 +326,15 @@ def within_dungeon(world, player):
|
|||||||
start = time.process_time()
|
start = time.process_time()
|
||||||
for builder in world.dungeon_layouts[player].values():
|
for builder in world.dungeon_layouts[player].values():
|
||||||
shuffle_key_doors(builder, world, player)
|
shuffle_key_doors(builder, world, player)
|
||||||
logging.getLogger('').info('Key door shuffle time: %s', time.process_time()-start)
|
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time"), time.process_time()-start)
|
||||||
smooth_door_pairs(world, player)
|
smooth_door_pairs(world, player)
|
||||||
|
|
||||||
|
|
||||||
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map):
|
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, fish):
|
||||||
for name, split_list in split_region_starts.items():
|
for name, split_list in split_region_starts.items():
|
||||||
builder = dungeon_builders.pop(name)
|
builder = dungeon_builders.pop(name)
|
||||||
recombinant_builders[name] = builder
|
recombinant_builders[name] = builder
|
||||||
split_builders = split_dungeon_builder(builder, split_list)
|
split_builders = split_dungeon_builder(builder, split_list, fish)
|
||||||
dungeon_builders.update(split_builders)
|
dungeon_builders.update(split_builders)
|
||||||
for sub_name, split_entrances in split_list.items():
|
for sub_name, split_entrances in split_list.items():
|
||||||
sub_builder = dungeon_builders[name+' '+sub_name]
|
sub_builder = dungeon_builders[name+' '+sub_name]
|
||||||
@@ -366,7 +366,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
|||||||
sector_queue.append(builder)
|
sector_queue.append(builder)
|
||||||
last_key = builder.name
|
last_key = builder.name
|
||||||
else:
|
else:
|
||||||
logging.getLogger('').info('Generating dungeon: %s', builder.name)
|
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","generating.dungeon"), builder.name)
|
||||||
ds = generate_dungeon(builder, origin_list_sans_drops, split_dungeon, world, player)
|
ds = generate_dungeon(builder, origin_list_sans_drops, split_dungeon, world, player)
|
||||||
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
|
find_new_entrances(ds, connections, potentials, enabled_entrances, world, player)
|
||||||
ds.name = name
|
ds.name = name
|
||||||
@@ -671,7 +671,7 @@ def cross_dungeon(world, player):
|
|||||||
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
|
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
|
||||||
loc.forced_item = loc.item = ItemFactory(key_name, player)
|
loc.forced_item = loc.item = ItemFactory(key_name, player)
|
||||||
recombinant_builders = {}
|
recombinant_builders = {}
|
||||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish)
|
||||||
|
|
||||||
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
|
||||||
|
|
||||||
@@ -792,7 +792,7 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
dungeon.small_keys = []
|
dungeon.small_keys = []
|
||||||
else:
|
else:
|
||||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||||
logging.getLogger('').info('Cross Dungeon: Key door shuffle time: %s', time.process_time()-start)
|
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time.crossed"), time.process_time()-start)
|
||||||
|
|
||||||
|
|
||||||
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||||
@@ -949,14 +949,14 @@ def calc_used_dungeon_items(builder):
|
|||||||
|
|
||||||
def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
|
def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
logger.info('Shuffling Key doors for %s', builder.name)
|
logger.info('%s %s', world.fish.translate("cli","cli","shuffling.keydoors"), builder.name)
|
||||||
# find valid combination of candidates
|
# find valid combination of candidates
|
||||||
if len(builder.candidates) < builder.key_doors_num:
|
if len(builder.candidates) < builder.key_doors_num:
|
||||||
if not drop_keys:
|
if not drop_keys:
|
||||||
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
||||||
return False
|
return False
|
||||||
builder.key_doors_num = len(builder.candidates) # reduce number of key doors
|
builder.key_doors_num = len(builder.candidates) # reduce number of key doors
|
||||||
logger.info('Lowering key door count because not enough candidates: %s', builder.name)
|
logger.info('%s: %s', world.fish.translate("cli","cli","lowering.keys.candidates"), builder.name)
|
||||||
combinations = ncr(len(builder.candidates), builder.key_doors_num)
|
combinations = ncr(len(builder.candidates), builder.key_doors_num)
|
||||||
itr = 0
|
itr = 0
|
||||||
start = time.process_time()
|
start = time.process_time()
|
||||||
@@ -976,7 +976,7 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
|
|||||||
if not drop_keys:
|
if not drop_keys:
|
||||||
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
logger.info('No valid layouts for %s with %s doors', builder.name, builder.key_doors_num)
|
||||||
return False
|
return False
|
||||||
logger.info('Lowering key door count because no valid layouts: %s', builder.name)
|
logger.info('%s: %s', world.fish.translate("cli","cli","lowering.keys.layouts"), builder.name)
|
||||||
builder.key_doors_num -= 1
|
builder.key_doors_num -= 1
|
||||||
if builder.key_doors_num < 0:
|
if builder.key_doors_num < 0:
|
||||||
raise Exception('Bad dungeon %s - 0 key doors not valid' % builder.name)
|
raise Exception('Bad dungeon %s - 0 key doors not valid' % builder.name)
|
||||||
|
|||||||
@@ -1149,8 +1149,8 @@ def create_dungeon_builders(all_sectors, world, player, dungeon_entrances=None):
|
|||||||
# polarity:
|
# polarity:
|
||||||
if not global_pole.is_valid(dungeon_map):
|
if not global_pole.is_valid(dungeon_map):
|
||||||
raise NeutralizingException('Either free location/crystal assignment is already globally invalid - lazy dev check this earlier!')
|
raise NeutralizingException('Either free location/crystal assignment is already globally invalid - lazy dev check this earlier!')
|
||||||
logger.info('-Balancing Doors')
|
logger.info(world.fish.translate("cli","cli","balance.doors"))
|
||||||
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
|
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, world.fish)
|
||||||
# the rest
|
# the rest
|
||||||
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
|
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
|
||||||
return dungeon_map
|
return dungeon_map
|
||||||
@@ -1434,9 +1434,9 @@ def sum_polarity(sector_list):
|
|||||||
return pol
|
return pol
|
||||||
|
|
||||||
|
|
||||||
def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger):
|
def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, fish):
|
||||||
# step 1: fix polarity connection issues
|
# step 1: fix polarity connection issues
|
||||||
logger.info('--Basic Traversal')
|
logger.info(fish.translate("cli","cli","basic.traversal"))
|
||||||
unconnected_builders = identify_polarity_issues(dungeon_map)
|
unconnected_builders = identify_polarity_issues(dungeon_map)
|
||||||
while len(unconnected_builders) > 0:
|
while len(unconnected_builders) > 0:
|
||||||
for name, builder in unconnected_builders.items():
|
for name, builder in unconnected_builders.items():
|
||||||
@@ -1474,7 +1474,7 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
|
|||||||
problem_builders = identify_simple_branching_issues(problem_builders)
|
problem_builders = identify_simple_branching_issues(problem_builders)
|
||||||
|
|
||||||
# step 3: fix neutrality issues
|
# step 3: fix neutrality issues
|
||||||
polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger)
|
polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish)
|
||||||
|
|
||||||
# step 4: fix dead ends again
|
# step 4: fix dead ends again
|
||||||
neutral_choices: List[List] = neutralize_the_rest(polarized_sectors)
|
neutral_choices: List[List] = neutralize_the_rest(polarized_sectors)
|
||||||
@@ -1523,11 +1523,11 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
|
|||||||
tries += 1
|
tries += 1
|
||||||
|
|
||||||
|
|
||||||
def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger):
|
def polarity_step_3(dungeon_map, polarized_sectors, global_pole, logger, fish):
|
||||||
builder_order = list(dungeon_map.values())
|
builder_order = list(dungeon_map.values())
|
||||||
random.shuffle(builder_order)
|
random.shuffle(builder_order)
|
||||||
for builder in builder_order:
|
for builder in builder_order:
|
||||||
logger.info('--Balancing %s', builder.name)
|
logger.info('%s %s', fish.translate("cli","cli","balancing"), builder.name)
|
||||||
while not builder.polarity().is_neutral():
|
while not builder.polarity().is_neutral():
|
||||||
candidates = find_neutralizing_candidates(builder, polarized_sectors)
|
candidates = find_neutralizing_candidates(builder, polarized_sectors)
|
||||||
valid, sectors = False, None
|
valid, sectors = False, None
|
||||||
@@ -1831,9 +1831,9 @@ def assign_the_rest(dungeon_map, neutral_sectors, global_pole):
|
|||||||
assign_sector(sector_list[i], builder, neutral_sectors, global_pole)
|
assign_sector(sector_list[i], builder, neutral_sectors, global_pole)
|
||||||
|
|
||||||
|
|
||||||
def split_dungeon_builder(builder, split_list):
|
def split_dungeon_builder(builder, split_list, fish):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
logger.info('Splitting Up Desert/Skull')
|
logger.info(fish.translate("cli","cli","splitting.up") + ' ' + 'Desert/Skull')
|
||||||
candidate_sectors = dict.fromkeys(builder.sectors)
|
candidate_sectors = dict.fromkeys(builder.sectors)
|
||||||
global_pole = GlobalPolarity(candidate_sectors)
|
global_pole = GlobalPolarity(candidate_sectors)
|
||||||
|
|
||||||
@@ -1844,10 +1844,10 @@ def split_dungeon_builder(builder, split_list):
|
|||||||
sub_builder.all_entrances = split_entrances
|
sub_builder.all_entrances = split_entrances
|
||||||
for r_name in split_entrances:
|
for r_name in split_entrances:
|
||||||
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
|
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
|
||||||
return balance_split(candidate_sectors, dungeon_map, global_pole)
|
return balance_split(candidate_sectors, dungeon_map, global_pole, fish)
|
||||||
|
|
||||||
|
|
||||||
def balance_split(candidate_sectors, dungeon_map, global_pole):
|
def balance_split(candidate_sectors, dungeon_map, global_pole, fish):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
# categorize sectors
|
# categorize sectors
|
||||||
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
|
crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors)
|
||||||
@@ -1860,8 +1860,8 @@ def balance_split(candidate_sectors, dungeon_map, global_pole):
|
|||||||
# blue barriers
|
# blue barriers
|
||||||
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
|
assign_crystal_barrier_sectors(dungeon_map, crystal_barriers, global_pole)
|
||||||
# polarity:
|
# polarity:
|
||||||
logger.info('-Re-balancing ' + next(iter(dungeon_map.keys())) + ' et al')
|
logger.info(fish.translate("cli","cli","re-balancing") + ' ' + next(iter(dungeon_map.keys())) + ' et al')
|
||||||
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger)
|
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger, fish)
|
||||||
# the rest
|
# the rest
|
||||||
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
|
assign_the_rest(dungeon_map, neutral_sectors, global_pole)
|
||||||
return dungeon_map
|
return dungeon_map
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import textwrap
|
|||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from CLI import parse_arguments
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
|
from CLI import parse_arguments, get_args_priority
|
||||||
from Main import main
|
from Main import main
|
||||||
from Rom import get_sprite_from_name
|
from Rom import get_sprite_from_name
|
||||||
from Utils import is_bundled, close_console
|
from Utils import is_bundled, close_console
|
||||||
@@ -42,6 +44,12 @@ def start():
|
|||||||
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
|
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel]
|
||||||
logging.basicConfig(format='%(message)s', level=loglevel)
|
logging.basicConfig(format='%(message)s', level=loglevel)
|
||||||
|
|
||||||
|
settings = get_args_priority(None, None, None)
|
||||||
|
lang = "en"
|
||||||
|
if "load" in settings and "lang" in settings["load"]:
|
||||||
|
lang = settings["load"]["lang"]
|
||||||
|
fish = BabelFish(lang=lang)
|
||||||
|
|
||||||
if args.gui:
|
if args.gui:
|
||||||
from Gui import guiMain
|
from Gui import guiMain
|
||||||
guiMain(args)
|
guiMain(args)
|
||||||
@@ -51,11 +59,11 @@ def start():
|
|||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
for _ in range(args.count):
|
for _ in range(args.count):
|
||||||
try:
|
try:
|
||||||
main(seed=seed, args=args)
|
main(seed=seed, args=args, fish=fish)
|
||||||
logger.info('Finished run %s', _+1)
|
logger.info('%s %s', fish.translate("cli","cli","finished.run"), _+1)
|
||||||
except (FillError, Exception, RuntimeError) as err:
|
except (FillError, Exception, RuntimeError) as err:
|
||||||
failures.append((err, seed))
|
failures.append((err, seed))
|
||||||
logger.warning('Generation failed: %s', err)
|
logger.warning('%s: %s', fish.translate("cli","cli","generation.failed"), err)
|
||||||
seed = random.randint(0, 999999999)
|
seed = random.randint(0, 999999999)
|
||||||
for fail in failures:
|
for fail in failures:
|
||||||
logger.info('%s seed failed with: %s', fail[1], fail[0])
|
logger.info('%s seed failed with: %s', fail[1], fail[0])
|
||||||
@@ -66,7 +74,7 @@ def start():
|
|||||||
logger.info('Generation fail rate: ' + str(fail_rate[0] ).rjust(3, " ") + '.' + str(fail_rate[1] ).ljust(6, '0') + '%')
|
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') + '%')
|
logger.info('Generation success rate: ' + str(success_rate[0]).rjust(3, " ") + '.' + str(success_rate[1]).ljust(6, '0') + '%')
|
||||||
else:
|
else:
|
||||||
main(seed=args.seed, args=args)
|
main(seed=args.seed, args=args, fish=fish)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
17
Gui.py
17
Gui.py
@@ -21,6 +21,9 @@ from source.gui.bottom import bottom_frame, create_guiargs
|
|||||||
from GuiUtils import set_icon
|
from GuiUtils import set_icon
|
||||||
from Main import __version__ as ESVersion
|
from Main import __version__ as ESVersion
|
||||||
|
|
||||||
|
from source.classes.BabelFish import BabelFish
|
||||||
|
from source.classes.Empty import Empty
|
||||||
|
|
||||||
|
|
||||||
def guiMain(args=None):
|
def guiMain(args=None):
|
||||||
# Save settings to file
|
# Save settings to file
|
||||||
@@ -143,14 +146,22 @@ def guiMain(args=None):
|
|||||||
# add randomizer notebook to main window
|
# add randomizer notebook to main window
|
||||||
self.pages["randomizer"].notebook.pack()
|
self.pages["randomizer"].notebook.pack()
|
||||||
|
|
||||||
|
settings = get_args_priority(None, None, None)
|
||||||
|
lang = "en"
|
||||||
|
if "load" in settings and "lang" in settings["load"]:
|
||||||
|
lang = settings["load"]["lang"]
|
||||||
|
self.fish = BabelFish(lang=lang)
|
||||||
|
|
||||||
# bottom of window: Open Output Directory, Open Documentation (if exists)
|
# bottom of window: Open Output Directory, Open Documentation (if exists)
|
||||||
self.frames["bottom"] = bottom_frame(self, self, None)
|
self.pages["bottom"] = Empty()
|
||||||
|
self.pages["bottom"].pages = {}
|
||||||
|
self.pages["bottom"].pages["content"] = bottom_frame(self, self, None)
|
||||||
## Save Settings Button
|
## Save Settings Button
|
||||||
savesettingsButton = Button(self.frames["bottom"], text='Save Settings to File', command=lambda: save_settings_from_gui(True))
|
savesettingsButton = Button(self.pages["bottom"].pages["content"], text='Save Settings to File', command=lambda: save_settings_from_gui(True))
|
||||||
savesettingsButton.pack(side=RIGHT)
|
savesettingsButton.pack(side=RIGHT)
|
||||||
|
|
||||||
# set bottom frame to main window
|
# set bottom frame to main window
|
||||||
self.frames["bottom"].pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
self.pages["bottom"].pages["content"].pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
||||||
|
|
||||||
self.outputPath = StringVar()
|
self.outputPath = StringVar()
|
||||||
self.randomSprite = BooleanVar()
|
self.randomSprite = BooleanVar()
|
||||||
|
|||||||
62
Main.py
62
Main.py
@@ -27,7 +27,7 @@ from Utils import output_path, parse_player_names, print_wiki_doors_by_region, p
|
|||||||
__version__ = '0.0.18.4d'
|
__version__ = '0.0.18.4d'
|
||||||
|
|
||||||
|
|
||||||
def main(args, seed=None):
|
def main(args, seed=None, fish=None):
|
||||||
if args.outputpath:
|
if args.outputpath:
|
||||||
os.makedirs(args.outputpath, exist_ok=True)
|
os.makedirs(args.outputpath, exist_ok=True)
|
||||||
output_path.cached_path = args.outputpath
|
output_path.cached_path = args.outputpath
|
||||||
@@ -59,10 +59,15 @@ def main(args, seed=None):
|
|||||||
world.beemizer = args.beemizer.copy()
|
world.beemizer = args.beemizer.copy()
|
||||||
world.experimental = args.experimental.copy()
|
world.experimental = args.experimental.copy()
|
||||||
world.dungeon_counters = args.dungeon_counters.copy()
|
world.dungeon_counters = args.dungeon_counters.copy()
|
||||||
|
world.fish = fish
|
||||||
|
|
||||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
logger.info('ALttP Door Randomizer Version %s - Seed: %s\n', __version__, world.seed)
|
logger.info(
|
||||||
|
world.fish.translate("cli","cli","app.title") + "\n",
|
||||||
|
__version__,
|
||||||
|
world.seed
|
||||||
|
)
|
||||||
|
|
||||||
parsed_names = parse_player_names(args.names, world.players, args.teams)
|
parsed_names = parse_player_names(args.names, world.players, args.teams)
|
||||||
world.teams = len(parsed_names)
|
world.teams = len(parsed_names)
|
||||||
@@ -95,7 +100,7 @@ def main(args, seed=None):
|
|||||||
create_rooms(world, player)
|
create_rooms(world, player)
|
||||||
create_dungeons(world, player)
|
create_dungeons(world, player)
|
||||||
|
|
||||||
logger.info('Shuffling the World about.')
|
logger.info(world.fish.translate("cli","cli","shuffling.world"))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.mode[player] != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
@@ -103,7 +108,7 @@ def main(args, seed=None):
|
|||||||
else:
|
else:
|
||||||
link_inverted_entrances(world, player)
|
link_inverted_entrances(world, player)
|
||||||
|
|
||||||
logger.info('Shuffling dungeons')
|
logger.info(world.fish.translate("cli","cli","shuffling.dungeons"))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
link_doors(world, player)
|
link_doors(world, player)
|
||||||
@@ -111,21 +116,21 @@ def main(args, seed=None):
|
|||||||
mark_light_world_regions(world, player)
|
mark_light_world_regions(world, player)
|
||||||
else:
|
else:
|
||||||
mark_dark_world_regions(world, player)
|
mark_dark_world_regions(world, player)
|
||||||
logger.info('Generating Item Pool.')
|
logger.info(world.fish.translate("cli","cli","generating.itempool"))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
generate_itempool(world, player)
|
generate_itempool(world, player)
|
||||||
|
|
||||||
logger.info('Calculating Access Rules.')
|
logger.info(world.fish.translate("cli","cli","calc.access.rules"))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
set_rules(world, player)
|
set_rules(world, player)
|
||||||
|
|
||||||
logger.info('Placing Dungeon Prizes.')
|
logger.info(world.fish.translate("cli","cli","placing.dungeon.prizes"))
|
||||||
|
|
||||||
fill_prizes(world)
|
fill_prizes(world)
|
||||||
|
|
||||||
logger.info('Placing Dungeon Items.')
|
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
|
||||||
|
|
||||||
shuffled_locations = None
|
shuffled_locations = None
|
||||||
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
|
||||||
@@ -139,9 +144,17 @@ def main(args, seed=None):
|
|||||||
for player in range(1, world.players+1):
|
for player in range(1, world.players+1):
|
||||||
for key_layout in world.key_layout[player].values():
|
for key_layout in world.key_layout[player].values():
|
||||||
if not validate_key_placement(key_layout, world, player):
|
if not validate_key_placement(key_layout, world, player):
|
||||||
raise RuntimeError("Keylock detected: %s (Player %d)" % (key_layout.sector.name, player))
|
raise RuntimeError(
|
||||||
|
"%s: %s (%s %d)" %
|
||||||
|
(
|
||||||
|
world.fish.translate("cli","cli","keylock.detected"),
|
||||||
|
key_layout.sector.name,
|
||||||
|
world.fish.translate("cli","cli","player"),
|
||||||
|
player
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
logger.info('Fill the world.')
|
logger.info(world.fish.translate("cli","cli","fill.world"))
|
||||||
|
|
||||||
if args.algorithm == 'flood':
|
if args.algorithm == 'flood':
|
||||||
flood_items(world) # different algo, biased towards early game progress items
|
flood_items(world) # different algo, biased towards early game progress items
|
||||||
@@ -160,14 +173,14 @@ def main(args, seed=None):
|
|||||||
distribute_items_restrictive(world, True)
|
distribute_items_restrictive(world, True)
|
||||||
|
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
logger.info('Balancing multiworld progression.')
|
logger.info(world.fish.translate("cli","cli","balance.multiworld"))
|
||||||
balance_multiworld_progression(world)
|
balance_multiworld_progression(world)
|
||||||
|
|
||||||
# if we only check for beatable, we can do this sanity check first before creating the rom
|
# if we only check for beatable, we can do this sanity check first before creating the rom
|
||||||
if not world.can_beat_game():
|
if not world.can_beat_game():
|
||||||
raise RuntimeError('Cannot beat game. Something went terribly wrong here!')
|
raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game"))
|
||||||
|
|
||||||
logger.info('Patching ROM.')
|
logger.info(world.fish.translate("cli","cli","patching.rom"))
|
||||||
|
|
||||||
outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed)
|
outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed)
|
||||||
|
|
||||||
@@ -191,8 +204,8 @@ def main(args, seed=None):
|
|||||||
if not args.jsonout:
|
if not args.jsonout:
|
||||||
rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000)
|
rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000)
|
||||||
else:
|
else:
|
||||||
logging.warning("EnemizerCLI not found at:" + args.enemizercli)
|
logging.warning(world.fish.translate("cli","cli","enemizer.not.found") + ': ' + args.enemizercli)
|
||||||
logging.warning("No Enemizer options will be applied until this is resolved.")
|
logging.warning(world.fish.translate("cli","cli","enemizer.nothing.applied"))
|
||||||
|
|
||||||
if args.race:
|
if args.race:
|
||||||
patch_race_rom(rom)
|
patch_race_rom(rom)
|
||||||
@@ -246,7 +259,7 @@ def main(args, seed=None):
|
|||||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||||
|
|
||||||
if not args.skip_playthrough:
|
if not args.skip_playthrough:
|
||||||
logger.info('Calculating playthrough.')
|
logger.info(world.fish.translate("cli","cli","calc.playthrough"))
|
||||||
create_playthrough(world)
|
create_playthrough(world)
|
||||||
|
|
||||||
if args.jsonout:
|
if args.jsonout:
|
||||||
@@ -254,8 +267,9 @@ def main(args, seed=None):
|
|||||||
elif args.create_spoiler and not args.skip_playthrough:
|
elif args.create_spoiler and not args.skip_playthrough:
|
||||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||||
|
|
||||||
logger.info('Done. Enjoy.')
|
logger.info(world.fish.translate("cli","cli","done"))
|
||||||
logger.info('Total Time: %s', time.perf_counter() - start)
|
logger.info(world.fish.translate("cli","cli","seed") + ": %d", world.seed)
|
||||||
|
logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start)
|
||||||
|
|
||||||
# print_wiki_doors_by_room(dungeon_regions,world,1)
|
# print_wiki_doors_by_room(dungeon_regions,world,1)
|
||||||
# print_wiki_doors_by_region(dungeon_regions,world,1)
|
# print_wiki_doors_by_region(dungeon_regions,world,1)
|
||||||
@@ -402,7 +416,7 @@ def create_playthrough(world):
|
|||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
sphere_candidates = list(prog_locations)
|
sphere_candidates = list(prog_locations)
|
||||||
logging.getLogger('').debug('Building up collection spheres.')
|
logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres"))
|
||||||
while sphere_candidates:
|
while sphere_candidates:
|
||||||
state.sweep_for_events(key_only=True)
|
state.sweep_for_events(key_only=True)
|
||||||
state.sweep_for_crystal_access()
|
state.sweep_for_crystal_access()
|
||||||
@@ -421,11 +435,11 @@ def create_playthrough(world):
|
|||||||
|
|
||||||
state_cache.append(state.copy())
|
state_cache.append(state.copy())
|
||||||
|
|
||||||
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations))
|
logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
|
||||||
if not sphere:
|
if not sphere:
|
||||||
logging.getLogger('').debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
logging.getLogger('').debug(world.fish.translate("cli","cli","cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
|
||||||
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
|
||||||
raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.')
|
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
|
||||||
else:
|
else:
|
||||||
old_world.spoiler.unreachables = sphere_candidates.copy()
|
old_world.spoiler.unreachables = sphere_candidates.copy()
|
||||||
break
|
break
|
||||||
@@ -477,9 +491,9 @@ def create_playthrough(world):
|
|||||||
|
|
||||||
collection_spheres.append(sphere)
|
collection_spheres.append(sphere)
|
||||||
|
|
||||||
logging.getLogger('').debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations))
|
logging.getLogger('').debug(world.fish.translate("cli","cli","building.final.spheres"), len(collection_spheres), len(sphere), len(required_locations))
|
||||||
if not sphere:
|
if not sphere:
|
||||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.required"))
|
||||||
|
|
||||||
# store the required locations for statistical analysis
|
# store the required locations for statistical analysis
|
||||||
old_world.required_locations = [(location.name, location.player) for sphere in collection_spheres for location in sphere]
|
old_world.required_locations = [(location.name, location.player) for sphere in collection_spheres for location in sphere]
|
||||||
|
|||||||
313
resources/app/cli/args.json
Normal file
313
resources/app/cli/args.json
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
{
|
||||||
|
"lang": {},
|
||||||
|
"create_spoiler": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"logic": {
|
||||||
|
"choices": [
|
||||||
|
"noglitches",
|
||||||
|
"minorglitches",
|
||||||
|
"nologic"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"choices": [
|
||||||
|
"open",
|
||||||
|
"standard",
|
||||||
|
"inverted",
|
||||||
|
"retro"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"swords": {
|
||||||
|
"choices": [
|
||||||
|
"random",
|
||||||
|
"assured",
|
||||||
|
"swordless",
|
||||||
|
"vanilla"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"goal": {
|
||||||
|
"choices": [
|
||||||
|
"ganon",
|
||||||
|
"pedestal",
|
||||||
|
"dungeons",
|
||||||
|
"triforcehunt",
|
||||||
|
"crystals"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"difficulty": {
|
||||||
|
"choices": [
|
||||||
|
"normal",
|
||||||
|
"hard",
|
||||||
|
"expert"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"item_functionality": {
|
||||||
|
"choices": [
|
||||||
|
"normal",
|
||||||
|
"hard",
|
||||||
|
"expert"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timer": {
|
||||||
|
"choices": [
|
||||||
|
"none",
|
||||||
|
"display",
|
||||||
|
"timed",
|
||||||
|
"timed-ohko",
|
||||||
|
"ohko",
|
||||||
|
"timed-countdown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"progressive": {
|
||||||
|
"choices": [
|
||||||
|
"on",
|
||||||
|
"off",
|
||||||
|
"random"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"algorithm": {
|
||||||
|
"choices": [
|
||||||
|
"balanced",
|
||||||
|
"freshness",
|
||||||
|
"flood",
|
||||||
|
"vt21",
|
||||||
|
"vt22",
|
||||||
|
"vt25",
|
||||||
|
"vt26"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shuffle": {
|
||||||
|
"choices": [
|
||||||
|
"vanilla",
|
||||||
|
"simple",
|
||||||
|
"restricted",
|
||||||
|
"full",
|
||||||
|
"crossed",
|
||||||
|
"insanity",
|
||||||
|
"restricted_legacy",
|
||||||
|
"full_legacy",
|
||||||
|
"madness_legacy",
|
||||||
|
"insanity_legacy",
|
||||||
|
"dungeonsfull",
|
||||||
|
"dungeonssimple"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"door_shuffle": {
|
||||||
|
"choices": [
|
||||||
|
"basic",
|
||||||
|
"crossed",
|
||||||
|
"vanilla"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"experimental": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"dungeon_counters": {
|
||||||
|
"choices": [
|
||||||
|
"off",
|
||||||
|
"on",
|
||||||
|
"pickup",
|
||||||
|
"default"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"crystals_ganon": {
|
||||||
|
"choices": [
|
||||||
|
7, 6, 5, 4, 3, 2, 1, 0, "random"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"crystals_gt": {
|
||||||
|
"choices": [
|
||||||
|
7, 6, 5, 4, 3, 2, 1, 0, "random"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"openpyramid": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"rom": {},
|
||||||
|
"loglevel": {
|
||||||
|
"choices": [
|
||||||
|
"info",
|
||||||
|
"error",
|
||||||
|
"warning",
|
||||||
|
"debug"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"fastmenu": {
|
||||||
|
"choices": [
|
||||||
|
"normal",
|
||||||
|
"instant",
|
||||||
|
"double",
|
||||||
|
"triple",
|
||||||
|
"quadruple",
|
||||||
|
"half"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"quickswap": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"disablemusic": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"mapshuffle": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"compassshuffle": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"keyshuffle": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"bigkeyshuffle": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"keysanity": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool",
|
||||||
|
"help": "suppress"
|
||||||
|
},
|
||||||
|
"retro": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"startinventory": {},
|
||||||
|
"usestartinventory": {
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"type": "bool",
|
||||||
|
"help": "suppress"
|
||||||
|
},
|
||||||
|
"accessibility": {
|
||||||
|
"choices": [
|
||||||
|
"items",
|
||||||
|
"locations",
|
||||||
|
"none"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hints": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"shuffleganon": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool",
|
||||||
|
"help": "suppress"
|
||||||
|
},
|
||||||
|
"no-shuffleganon": {
|
||||||
|
"action": "store_false",
|
||||||
|
"dest": "shuffleganon"
|
||||||
|
},
|
||||||
|
"heartbeep": {
|
||||||
|
"choices": [
|
||||||
|
"normal",
|
||||||
|
"double",
|
||||||
|
"half",
|
||||||
|
"quarter",
|
||||||
|
"off"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"heartcolor": {
|
||||||
|
"choices": [
|
||||||
|
"red",
|
||||||
|
"blue",
|
||||||
|
"green",
|
||||||
|
"yellow",
|
||||||
|
"random"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ow_palettes": {
|
||||||
|
"choices": [
|
||||||
|
"default",
|
||||||
|
"random",
|
||||||
|
"blackout"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uw_palettes": {
|
||||||
|
"choices": [
|
||||||
|
"default",
|
||||||
|
"random",
|
||||||
|
"blackout"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sprite": {},
|
||||||
|
"suppress_rom": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"gui": {
|
||||||
|
"action": "store_true"
|
||||||
|
},
|
||||||
|
"jsonout": {
|
||||||
|
"action": "store_true"
|
||||||
|
},
|
||||||
|
"skip_playthrough": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"enemizercli": {
|
||||||
|
"setting": "enemizercli"
|
||||||
|
},
|
||||||
|
"shufflebosses": {
|
||||||
|
"choices": [
|
||||||
|
"none",
|
||||||
|
"basic",
|
||||||
|
"normal",
|
||||||
|
"chaos"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shuffleenemies": {
|
||||||
|
"choices": [
|
||||||
|
"none",
|
||||||
|
"shuffled",
|
||||||
|
"chaos"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enemy_health": {
|
||||||
|
"choices": [
|
||||||
|
"default",
|
||||||
|
"easy",
|
||||||
|
"normal",
|
||||||
|
"hard",
|
||||||
|
"expert"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enemy_damage": {
|
||||||
|
"choices": [
|
||||||
|
"default",
|
||||||
|
"shuffled",
|
||||||
|
"chaos"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shufflepots": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"remote_items": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"names": {},
|
||||||
|
"outputpath": {},
|
||||||
|
"race": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"saveonexit": {
|
||||||
|
"choices": [
|
||||||
|
"ask",
|
||||||
|
"always",
|
||||||
|
"never"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputname": {}
|
||||||
|
}
|
||||||
27
resources/app/cli/lang/de.json
Normal file
27
resources/app/cli/lang/de.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"cli": {
|
||||||
|
"app.title": "ALttP Tür Randomisier Version %s - Nummer: %d",
|
||||||
|
"shuffling.world": "Welt wird durchmischt.",
|
||||||
|
"generating.itempool": "Generier Gegenstandsbasis.",
|
||||||
|
"calc.access.rules": "Berechne Zugriffsregeln.",
|
||||||
|
"placing.dungeon.prizes": "Platziere Verliespreise.",
|
||||||
|
"placing.dungeon.items": "Platziere Verliesgegenstände.",
|
||||||
|
"fill.world": "Fülle die Welt.",
|
||||||
|
"balance.multiworld": "Gleiche Multiwelt-Fortschritt aus.",
|
||||||
|
"patching.rom": "Patche ROM.",
|
||||||
|
"calc.playthrough": "Berechne Durschpiellösung.",
|
||||||
|
"done": "Fertig. Viel Spaß.",
|
||||||
|
"total.time": "Gesamtzeit: %s",
|
||||||
|
"building.collection.spheres": "Baue Sammelbereiche auf.",
|
||||||
|
"building.calculating.spheres": "Berechneter Bereich %i, beinhaltet %i von %i Progressionsgegenständen.",
|
||||||
|
"cannot.reach.items": "Die folgenden Gegenstände können nicht erreicht werden: %s",
|
||||||
|
"cannot.reach.item": "%s (Spieler %d) in %s (Spieler %d)",
|
||||||
|
"check.item.location": "Prüfe ob %s (Spieler %d) benötigt wird um das Spiel zu schlagen.",
|
||||||
|
"check.item.location.true": "Ja, Gegenstand wird benötigt um das Spiel zu schlagen.",
|
||||||
|
"check.item.location.false": "Nein, Gegenstand wird nicht benötigt um das Spiel zu schlagen.",
|
||||||
|
"building.final.spheres": "Berechneter Finalbereich %i, beinhaltet, %i von %i Progressionsgegenständen.",
|
||||||
|
"cannot.beat.game": "Spiel is nicht schlagbar.",
|
||||||
|
"cannot.reach.progression": "Nicht alle Progressionsgegenstände erreichbar.",
|
||||||
|
"cannot.reach.required": "Nitch alle benötigten Gegenstände erreichbar."
|
||||||
|
}
|
||||||
|
}
|
||||||
272
resources/app/cli/lang/en.json
Normal file
272
resources/app/cli/lang/en.json
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
{
|
||||||
|
"cli": {
|
||||||
|
"app.title": "ALttP Door Randomizer Version %s - Seed: %d",
|
||||||
|
"version": "Version",
|
||||||
|
"seed": "Seed",
|
||||||
|
"player": "Player",
|
||||||
|
"shuffling.world": "Shuffling the World about",
|
||||||
|
"shuffling.dungeons": "Shuffling dungeons",
|
||||||
|
"basic.traversal": "--Basic Traversal",
|
||||||
|
"generating.dungeon": "Generating dungeon",
|
||||||
|
"shuffling.keydoors": "Shuffling Key doors for",
|
||||||
|
"lowering.keys.candidates": "Lowering key door count because not enough candidates",
|
||||||
|
"lowering.keys.layouts": "Lowering key door count because no valid layouts",
|
||||||
|
"keydoor.shuffle.time": "Key door shuffle time",
|
||||||
|
"keydoor.shuffle.time.crossed": "Cross Dungeon: Key door shuffle time",
|
||||||
|
"generating.itempool": "Generating Item Pool",
|
||||||
|
"calc.access.rules": "Calculating Access Rules",
|
||||||
|
"placing.dungeon.prizes": "Placing Dungeon Prizes",
|
||||||
|
"placing.dungeon.items": "Placing Dungeon Items",
|
||||||
|
"keylock.detected": "Keylock detected",
|
||||||
|
"fill.world": "Fill the world",
|
||||||
|
"balance.doors": "-Balancing Doors",
|
||||||
|
"re-balancing": "-Re-balancing",
|
||||||
|
"balancing": "--Balancing",
|
||||||
|
"splitting.up": "Splitting Up",
|
||||||
|
"balance.multiworld": "Balancing multiworld progression",
|
||||||
|
"cannot.beat.game": "Cannot beat game! Something went terribly wrong here!",
|
||||||
|
"cannot.reach.items": "The following items could not be reached: %s",
|
||||||
|
"cannot.reach.item": "%s (Player %d) at %s (Player %d)",
|
||||||
|
"check.item.location": "Checking if %s (Player %d) is required to beat the game.",
|
||||||
|
"check.item.location.true": "Yes, item is required.",
|
||||||
|
"check.item.location.false": "No, item is not required.",
|
||||||
|
"cannot.reach.progression": "Not all progression items reachable. Something went terribly wrong here.",
|
||||||
|
"cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.",
|
||||||
|
"patching.rom": "Patching ROM",
|
||||||
|
"calc.playthrough": "Calculating playthrough",
|
||||||
|
"done": "Done. Enjoy.",
|
||||||
|
"total.time": "Total Time: %s",
|
||||||
|
"finished.run": "Finished run",
|
||||||
|
"generation.failed": "Generation failed",
|
||||||
|
"generation.fail.rate": "Generation fail rate",
|
||||||
|
"generation.success.rate": "Generation success rate",
|
||||||
|
"enemizer.not.found": "Enemizer not found at",
|
||||||
|
"enemizer.nothing.applied": "No Enemizer options will be applied until this is resolved.",
|
||||||
|
"building.collection.spheres": "Building up collection spheres",
|
||||||
|
"building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.",
|
||||||
|
"building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items."
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"lang": [ "App Language, if available, defaults to English" ],
|
||||||
|
"create_spoiler": [ "Output a Spoiler File" ],
|
||||||
|
"logic": [
|
||||||
|
"Select Enforcement of Item Requirements. (default: %(default)s)",
|
||||||
|
"No Glitches: No Glitch knowledge required.",
|
||||||
|
"Minor Glitches: May require Fake Flippers, Bunny Revival",
|
||||||
|
" and Dark Room Navigation.",
|
||||||
|
"No Logic: Distribute items without regard for",
|
||||||
|
" item requirements."
|
||||||
|
],
|
||||||
|
"mode": [
|
||||||
|
"Select game mode. (default: %(default)s)",
|
||||||
|
"Open: World starts with Zelda rescued.",
|
||||||
|
"Standard: Fixes Hyrule Castle Secret Entrance and Front Door",
|
||||||
|
" but may lead to weird rain state issues if you exit",
|
||||||
|
" through the Hyrule Castle side exits before rescuing",
|
||||||
|
" Zelda in a full shuffle.",
|
||||||
|
"Inverted: Starting locations are Dark Sanctuary in West Dark",
|
||||||
|
" World or at Link's House, which is shuffled freely.",
|
||||||
|
" Requires the moon pearl to be Link in the Light World",
|
||||||
|
" instead of a bunny.",
|
||||||
|
"Retro: Keys are universal, shooting arrows costs rupees,",
|
||||||
|
" and a few other little things make this more like Zelda-1."
|
||||||
|
],
|
||||||
|
"swords": [
|
||||||
|
"Select sword placement. (default: %(default)s)",
|
||||||
|
"Random: All swords placed randomly.",
|
||||||
|
"Assured: Start game with a sword already.",
|
||||||
|
"Swordless: No swords. Curtains in Skull Woods and Agahnim\\'s",
|
||||||
|
" Tower are removed, Agahnim\\'s Tower barrier can be",
|
||||||
|
" destroyed with hammer. Misery Mire and Turtle Rock",
|
||||||
|
" can be opened without a sword. Hammer damages Ganon.",
|
||||||
|
" Ether and Bombos Tablet can be activated with Hammer",
|
||||||
|
" (and Book). Bombos pads have been added in Ice",
|
||||||
|
" Palace, to allow for an alternative to firerod.",
|
||||||
|
"Vanilla: Swords are in vanilla locations."
|
||||||
|
],
|
||||||
|
"goal": [
|
||||||
|
"Select completion goal. (default: %(default)s)",
|
||||||
|
"Ganon: Collect all crystals, beat Agahnim 2 then",
|
||||||
|
" defeat Ganon.",
|
||||||
|
"Crystals: Collect all crystals then defeat Ganon.",
|
||||||
|
"Pedestal: Places the Triforce at the Master Sword Pedestal.",
|
||||||
|
"All Dungeons: Collect all crystals, pendants, beat both",
|
||||||
|
" Agahnim fights and then defeat Ganon.",
|
||||||
|
"Triforce Hunt: Places 30 Triforce Pieces in the world, collect",
|
||||||
|
" 20 of them to beat the game."
|
||||||
|
],
|
||||||
|
"difficulty": [
|
||||||
|
"Select game difficulty. Affects available itempool. (default: %(default)s)",
|
||||||
|
"Normal: Normal difficulty.",
|
||||||
|
"Hard: A harder setting with less equipment and reduced health.",
|
||||||
|
"Expert: A harder yet setting with minimum equipment and health."
|
||||||
|
],
|
||||||
|
"item_functionality": [
|
||||||
|
"Select limits on item functionality to increase difficulty. (default: %(default)s)",
|
||||||
|
"Normal: Normal functionality.",
|
||||||
|
"Hard: Reduced functionality.",
|
||||||
|
"Expert: Greatly reduced functionality."
|
||||||
|
],
|
||||||
|
"timer": [
|
||||||
|
"Select game timer setting. Affects available itempool. (default: %(default)s)",
|
||||||
|
"None: No timer.",
|
||||||
|
"Display: Displays a timer but does not affect",
|
||||||
|
" the itempool.",
|
||||||
|
"Timed: Starts with clock at zero. Green Clocks",
|
||||||
|
" subtract 4 minutes (Total: 20), Blue Clocks",
|
||||||
|
" subtract 2 minutes (Total: 10), Red Clocks add",
|
||||||
|
" 2 minutes (Total: 10). Winner is player with",
|
||||||
|
" lowest time at the end.",
|
||||||
|
"Timed OHKO: Starts clock at 10 minutes. Green Clocks add",
|
||||||
|
" 5 minutes (Total: 25). As long as clock is at 0,",
|
||||||
|
" Link will die in one hit.",
|
||||||
|
"OHKO: Like Timed OHKO, but no clock items are present",
|
||||||
|
" and the clock is permenantly at zero.",
|
||||||
|
"Timed Countdown:Starts with clock at 40 minutes. Same clocks as",
|
||||||
|
" Timed mode. If time runs out, you lose (but can",
|
||||||
|
" still keep playing)."
|
||||||
|
],
|
||||||
|
"progressive": [
|
||||||
|
"Select progressive equipment setting. Affects available itempool. (default: %(default)s)",
|
||||||
|
"On: Swords, Shields, Armor, and Gloves will",
|
||||||
|
" all be progressive equipment. Each subsequent",
|
||||||
|
" item of the same type the player finds will",
|
||||||
|
" upgrade that piece of equipment by one stage.",
|
||||||
|
"Off: Swords, Shields, Armor, and Gloves will not",
|
||||||
|
" be progressive equipment. Higher level items may",
|
||||||
|
" be found at any time. Downgrades are not possible.",
|
||||||
|
"Random: Swords, Shields, Armor, and Gloves will, per",
|
||||||
|
" category, be randomly progressive or not.",
|
||||||
|
" Link will die in one hit."
|
||||||
|
],
|
||||||
|
"algorithm": [
|
||||||
|
"Select item filling algorithm. (default: %(default)s)",
|
||||||
|
"balanced: vt26 derivative that aims to strike a balance between",
|
||||||
|
" the overworld heavy vt25 and the dungeon heavy vt26",
|
||||||
|
" algorithm.",
|
||||||
|
"vt26: Shuffle items and place them in a random location",
|
||||||
|
" that it is not impossible to be in. This includes",
|
||||||
|
" dungeon keys and items.",
|
||||||
|
"vt25: Shuffle items and place them in a random location",
|
||||||
|
" that it is not impossible to be in.",
|
||||||
|
"vt21: Unbiased in its selection, but has tendency to put",
|
||||||
|
" Ice Rod in Turtle Rock.",
|
||||||
|
"vt22: Drops off stale locations after 1/3 of progress",
|
||||||
|
" items were placed to try to circumvent vt21\\'s",
|
||||||
|
" shortcomings.",
|
||||||
|
"Freshness: Keep track of stale locations (ones that cannot be",
|
||||||
|
" reached yet) and decrease likeliness of selecting",
|
||||||
|
" them the more often they were found unreachable.",
|
||||||
|
"Flood: Push out items starting from Link\\'s House and",
|
||||||
|
" slightly biased to placing progression items with",
|
||||||
|
" less restrictions."
|
||||||
|
],
|
||||||
|
"shuffle": [
|
||||||
|
"Select Entrance Shuffling Algorithm. (default: %(default)s)",
|
||||||
|
"Full: Mix cave and dungeon entrances freely while limiting",
|
||||||
|
" multi-entrance caves to one world.",
|
||||||
|
"Simple: Shuffle Dungeon Entrances/Exits between each other",
|
||||||
|
" and keep all 4-entrance dungeons confined to one",
|
||||||
|
" location. All caves outside of death mountain are",
|
||||||
|
" shuffled in pairs and matched by original type.",
|
||||||
|
"Restricted: Use Dungeons shuffling from Simple but freely",
|
||||||
|
" connect remaining entrances.",
|
||||||
|
"Crossed: Mix cave and dungeon entrances freely while allowing",
|
||||||
|
" caves to cross between worlds.",
|
||||||
|
"Insanity: Decouple entrances and exits from each other and",
|
||||||
|
" shuffle them freely. Caves that used to be single",
|
||||||
|
" entrance will still exit to the same location from",
|
||||||
|
" which they are entered.",
|
||||||
|
"Vanilla: All entrances are in the same locations they were",
|
||||||
|
" in the base game.",
|
||||||
|
"Legacy shuffles preserve behavior from older versions of the",
|
||||||
|
"entrance randomizer including significant technical limitations.",
|
||||||
|
"The dungeon variants only mix up dungeons and keep the rest of",
|
||||||
|
"the overworld vanilla."
|
||||||
|
],
|
||||||
|
"door_shuffle": [
|
||||||
|
"Select Door Shuffling Algorithm. (default: %(default)s)",
|
||||||
|
"Basic: Doors are mixed within a single dungeon.",
|
||||||
|
"Crossed: Doors are mixed between all dungeons.",
|
||||||
|
"Vanilla: All doors are connected the same way they were in the",
|
||||||
|
" base game."
|
||||||
|
],
|
||||||
|
"experimental": [ "Enable experimental features. (default: %(default)s)" ],
|
||||||
|
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
|
||||||
|
"crystals_ganon": [
|
||||||
|
"How many crystals are needed to defeat ganon. Any other",
|
||||||
|
"requirements for ganon for the selected goal still apply.",
|
||||||
|
"This setting does not apply when the all dungeons goal is",
|
||||||
|
"selected. (default: %(default)s)",
|
||||||
|
"Random: Picks a random value between 0 and 7 (inclusive).",
|
||||||
|
"0-7: Number of crystals needed"
|
||||||
|
],
|
||||||
|
"crystals_gt": [
|
||||||
|
"How many crystals are needed to open GT. For inverted mode",
|
||||||
|
"this applies to the castle tower door instead. (default: %(default)s)",
|
||||||
|
"Random: Picks a random value between 0 and 7 (inclusive).",
|
||||||
|
"0-7: Number of crystals needed"
|
||||||
|
],
|
||||||
|
"openpyramid": [ "Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it. (default: %(default)s)" ],
|
||||||
|
"rom": [
|
||||||
|
"Path to an ALttP JP (1.0) rom to use as a base." ,
|
||||||
|
"(default: %(default)s)"
|
||||||
|
],
|
||||||
|
"loglevel": [ "Select level of logging for output. (default: %(default)s)" ],
|
||||||
|
"seed": [ "Define seed number to generate." ],
|
||||||
|
"count": [
|
||||||
|
"Use to batch generate multiple seeds with same settings.",
|
||||||
|
"If --seed is provided, it will be used for the first seed, then",
|
||||||
|
"used to derive the next seed (i.e. generating %(default)s seed(s) with",
|
||||||
|
"--seed given will produce the same %(default)s (different) rom(s) each",
|
||||||
|
"time)."
|
||||||
|
],
|
||||||
|
"fastmenu": [
|
||||||
|
"Select the rate at which the menu opens and closes. (default: %(default)s)"
|
||||||
|
],
|
||||||
|
"quickswap": [ "Enable quick item swapping with L and R. (default: %(default)s)" ],
|
||||||
|
"disablemusic": [ "Disables game music including MSU-1. (default: %(default)s)" ],
|
||||||
|
"mapshuffle": [ "Maps are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||||
|
"compassshuffle": [ "Compasses are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||||
|
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||||
|
"bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||||
|
"retro": [
|
||||||
|
"Keys are universal, shooting arrows costs rupees,",
|
||||||
|
"and a few other little things make this more like Zelda-1. (default: %(default)s)"
|
||||||
|
],
|
||||||
|
"startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ],
|
||||||
|
"usestartinventory": [ "Toggle usage of Starting Inventory." ],
|
||||||
|
"custom": [ "Not supported." ],
|
||||||
|
"customitemarray": [ "Not supported." ],
|
||||||
|
"accessibility": [
|
||||||
|
"Select Item/Location Accessibility. (default: %(default)s)",
|
||||||
|
"Items: You can reach all unique inventory items. No guarantees about",
|
||||||
|
" reaching all locations or all keys.",
|
||||||
|
"Locations: You will be able to reach every location in the game.",
|
||||||
|
"None: You will be able to reach enough locations to beat the game."
|
||||||
|
],
|
||||||
|
"hints": [ "Make telepathic tiles and storytellers give helpful hints. (default: %(default)s)" ],
|
||||||
|
"no-shuffleganon": [
|
||||||
|
"If set, the Pyramid Hole and Ganon's Tower are not",
|
||||||
|
"included entrance shuffle pool. (default: %(default)s)"
|
||||||
|
],
|
||||||
|
"heartbeep": [
|
||||||
|
"Select the rate at which the heart beep sound is played at",
|
||||||
|
"low health. (default: %(default)s)"
|
||||||
|
],
|
||||||
|
"heartcolor": [ "Select the color of Link\\'s heart meter. (default: %(default)s)" ],
|
||||||
|
"sprite": [
|
||||||
|
"Path to a sprite sheet to use for Link. Needs to be in",
|
||||||
|
"binary format and have a length of 0x7000 (28672) bytes,",
|
||||||
|
"or 0x7078 (28792) bytes including palette data.",
|
||||||
|
"Alternatively, can be a ALttP Rom patched with a Link",
|
||||||
|
"sprite that will be extracted."
|
||||||
|
],
|
||||||
|
"suppress_rom": [ "Do not create an output rom file. (default: %(default)s)" ],
|
||||||
|
"gui": [ "Launch the GUI. (default: %(default)s)" ],
|
||||||
|
"jsonout": [
|
||||||
|
"Output .json patch to stdout instead of a patched rom. Used",
|
||||||
|
"for VT site integration, do not use otherwise. (default: %(default)s)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
34
resources/app/cli/lang/es.json
Normal file
34
resources/app/cli/lang/es.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"cli": {
|
||||||
|
"app.title": "ALttP Puerta Aleatorizador Versión %s - Número: %d",
|
||||||
|
"player": "Jugador",
|
||||||
|
"shuffling.world": "Barajando el Mundo",
|
||||||
|
"shuffling.dungeons": "Barajando Mazmorras",
|
||||||
|
"basic.traversal": "--Recorrido Básico",
|
||||||
|
"generating.dungeon": "Generando mazmorra",
|
||||||
|
"shuffling.keydoors": "Barajando Puertas Clave para",
|
||||||
|
|
||||||
|
"keylock.detected": "Bloqueo de Teclas detectado",
|
||||||
|
"fill.world": "Llenar el Mundo",
|
||||||
|
"balance.doors": "-Equilibriando Puertas",
|
||||||
|
"re-balancing": "-Reequilibriando",
|
||||||
|
"balancing": "--Equilibriando",
|
||||||
|
"splitting.up": "División",
|
||||||
|
|
||||||
|
"cannot.beat.game": "No se puede vencer el juego. Algo salió terriblemente mal.",
|
||||||
|
"cannot.reach.items": "No se pudo llegar a los siguientes elementos: %s",
|
||||||
|
"cannot.reach.item": "%s (Jugador %d) at %s (Jugador %d)",
|
||||||
|
"check.item.location": "Comprobar si se requiere que %s (Jugador %d) gane el juego.",
|
||||||
|
"check.item.location.true": "Sí, se requiere artículo.",
|
||||||
|
"check.item.location.false": "No, no se requiere artículo.",
|
||||||
|
|
||||||
|
"patching.rom": "Parchear ROM",
|
||||||
|
"calc.playthrough": "Cálculo de Juego",
|
||||||
|
"generation.failed": "Generación Fallida",
|
||||||
|
"enemizer.not.found": "Enemizer no encontrado en",
|
||||||
|
|
||||||
|
"building.collection.spheres": "Construyendo esferas de recolección.",
|
||||||
|
"building.calculating.spheres": "Esfera calculada %i, que contiene %i de %i elementos de progreso.",
|
||||||
|
"building.final.spheres": "Esfera final calculada %i, que contiene %i de %i elementos de progreso."
|
||||||
|
}
|
||||||
|
}
|
||||||
272
resources/app/gui/lang/en.json
Normal file
272
resources/app/gui/lang/en.json
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
{
|
||||||
|
"gui": {
|
||||||
|
"adjust.nobgm": "Disable Music & MSU-1",
|
||||||
|
"adjust.quickswap": "L/R Quickswapping",
|
||||||
|
|
||||||
|
"adjust.heartcolor": "Heart Color",
|
||||||
|
"adjust.heartcolor.red": "Red",
|
||||||
|
"adjust.heartcolor.blue": "Blue",
|
||||||
|
"adjust.heartcolor.green": "Green",
|
||||||
|
"adjust.heartcolor.yellow": "Yellow",
|
||||||
|
"adjust.heartcolor.random": "Random",
|
||||||
|
|
||||||
|
"adjust.heartbeep": "Heart Beep sound rate",
|
||||||
|
"adjust.heartbeep.double": "Double",
|
||||||
|
"adjust.heartbeep.normal": "Normal",
|
||||||
|
"adjust.heartbeep.half": "Half",
|
||||||
|
"adjust.heartbeep.quarter": "Quarter",
|
||||||
|
"adjust.heartbeep.off": "Off",
|
||||||
|
|
||||||
|
"adjust.menuspeed": "Menu Speed",
|
||||||
|
"adjust.menuspeed.instant": "Instant",
|
||||||
|
"adjust.menuspeed.quadruple": "Quadruple",
|
||||||
|
"adjust.menuspeed.triple": "Triple",
|
||||||
|
"adjust.menuspeed.double": "Double",
|
||||||
|
"adjust.menuspeed.normal": "Normal",
|
||||||
|
"adjust.menuspeed.half": "Half",
|
||||||
|
|
||||||
|
"adjust.owpalettes": "Overworld Palettes",
|
||||||
|
"adjust.owpalettes.default": "Default",
|
||||||
|
"adjust.owpalettes.random": "Random",
|
||||||
|
"adjust.owpalettes.blackout": "Blackout",
|
||||||
|
|
||||||
|
"adjust.uwpalettes": "Underworld Palettes",
|
||||||
|
"adjust.uwpalettes.default": "Default",
|
||||||
|
"adjust.uwpalettes.random": "Random",
|
||||||
|
"adjust.uwpalettes.blackout": "Blackout",
|
||||||
|
|
||||||
|
"adjust.sprite": "Sprite:",
|
||||||
|
"adjust.sprite.unchanged": "(unchanged)",
|
||||||
|
|
||||||
|
"adjust.rom": "Rom to adjust: ",
|
||||||
|
"adjust.rom.romfiles": "Rom Files",
|
||||||
|
"adjust.rom.button": "Select Rom",
|
||||||
|
"adjust.rom.go": "Adjust Rom",
|
||||||
|
"adjust.rom.dialog.error": "Error while patching",
|
||||||
|
"adjust.rom.dialog.success": "Success",
|
||||||
|
"adjust.rom.dialog.success.message": "Rom patched successfully.",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.dungeon.keysanity": "Shuffle: ",
|
||||||
|
"randomizer.dungeon.mapshuffle": "Maps",
|
||||||
|
"randomizer.dungeon.compassshuffle": "Compasses",
|
||||||
|
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
|
||||||
|
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
||||||
|
|
||||||
|
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
|
||||||
|
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
||||||
|
"randomizer.dungeon.dungeondoorshuffle.basic": "Basic",
|
||||||
|
"randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed",
|
||||||
|
|
||||||
|
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
||||||
|
|
||||||
|
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
||||||
|
"randomizer.dungeon.dungeon_counters.default": "Auto",
|
||||||
|
"randomizer.dungeon.dungeon_counters.off": "Off",
|
||||||
|
"randomizer.dungeon.dungeon_counters.on": "On",
|
||||||
|
"randomizer.dungeon.dungeon_counters.pickup": "On Compass Pickup",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.enemizer.potshuffle": "Pot Shuffle",
|
||||||
|
|
||||||
|
"randomizer.enemizer.enemyshuffle": "Enemy Shuffle",
|
||||||
|
"randomizer.enemizer.enemyshuffle.none": "Vanilla",
|
||||||
|
"randomizer.enemizer.enemyshuffle.shuffled": "Shuffled",
|
||||||
|
"randomizer.enemizer.enemyshuffle.chaos": "Chaos",
|
||||||
|
|
||||||
|
"randomizer.enemizer.bossshuffle": "Boss Shuffle",
|
||||||
|
"randomizer.enemizer.bossshuffle.none": "Vanilla",
|
||||||
|
"randomizer.enemizer.bossshuffle.basic": "Basic",
|
||||||
|
"randomizer.enemizer.bossshuffle.shuffled": "Shuffled",
|
||||||
|
"randomizer.enemizer.bossshuffle.chaos": "Chaos",
|
||||||
|
|
||||||
|
"randomizer.enemizer.enemydamage": "Enemy Damage",
|
||||||
|
"randomizer.enemizer.enemydamage.default": "Vanilla",
|
||||||
|
"randomizer.enemizer.enemydamage.shuffled": "Shuffled",
|
||||||
|
"randomizer.enemizer.enemydamage.chaos": "Chaos",
|
||||||
|
|
||||||
|
"randomizer.enemizer.enemyhealth": "Enemy Health",
|
||||||
|
"randomizer.enemizer.enemyhealth.default": "Vanilla",
|
||||||
|
"randomizer.enemizer.enemyhealth.easy": "Easy",
|
||||||
|
"randomizer.enemizer.enemyhealth.normal": "Normal",
|
||||||
|
"randomizer.enemizer.enemyhealth.hard": "Hard",
|
||||||
|
"randomizer.enemizer.enemyhealth.expert": "Expert",
|
||||||
|
|
||||||
|
"randomizer.enemizer.enemizercli": "EnemizerCLI path: ",
|
||||||
|
"randomizer.enemizer.enemizercli.online": "(get online)",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.entrance.openpyramid": "Pre-open Pyramid Hole",
|
||||||
|
"randomizer.entrance.shuffleganon": "Include Ganon's Tower and Pyramid Hole in shuffle pool",
|
||||||
|
|
||||||
|
"randomizer.entrance.entranceshuffle": "Entrance Shuffle",
|
||||||
|
"randomizer.entrance.entranceshuffle.vanilla": "Vanilla",
|
||||||
|
"randomizer.entrance.entranceshuffle.simple": "Simple",
|
||||||
|
"randomizer.entrance.entranceshuffle.restricted": "Restricted",
|
||||||
|
"randomizer.entrance.entranceshuffle.full": "Full",
|
||||||
|
"randomizer.entrance.entranceshuffle.crossed": "Crossed",
|
||||||
|
"randomizer.entrance.entranceshuffle.insanity": "Insanity",
|
||||||
|
"randomizer.entrance.entranceshuffle.restricted_legacy": "Restricted (Legacy)",
|
||||||
|
"randomizer.entrance.entranceshuffle.full_legacy": "Full (Legacy)",
|
||||||
|
"randomizer.entrance.entranceshuffle.madness_legacy": "Madness (Legacy)",
|
||||||
|
"randomizer.entrance.entranceshuffle.insanity_legacy": "Insanity (Legacy)",
|
||||||
|
"randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full",
|
||||||
|
"randomizer.entrance.entranceshuffle.dungeonssimple": "Dungeons + Simple",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.gameoptions.hints": "Include Helpful Hints",
|
||||||
|
"randomizer.gameoptions.nobgm": "Disable Music & MSU-1",
|
||||||
|
"randomizer.gameoptions.quickswap": "L/R Quickswapping",
|
||||||
|
|
||||||
|
"randomizer.gameoptions.heartcolor": "Heart Color",
|
||||||
|
"randomizer.gameoptions.heartcolor.red": "Red",
|
||||||
|
"randomizer.gameoptions.heartcolor.blue": "Blue",
|
||||||
|
"randomizer.gameoptions.heartcolor.green": "Green",
|
||||||
|
"randomizer.gameoptions.heartcolor.yellow": "Yellow",
|
||||||
|
"randomizer.gameoptions.heartcolor.random": "Random",
|
||||||
|
|
||||||
|
"randomizer.gameoptions.heartbeep": "Heart Beep sound rate",
|
||||||
|
"randomizer.gameoptions.heartbeep.double": "Double",
|
||||||
|
"randomizer.gameoptions.heartbeep.normal": "Normal",
|
||||||
|
"randomizer.gameoptions.heartbeep.half": "Half",
|
||||||
|
"randomizer.gameoptions.heartbeep.quarter": "Quarter",
|
||||||
|
"randomizer.gameoptions.heartbeep.off": "Off",
|
||||||
|
|
||||||
|
"randomizer.gameoptions.menuspeed": "Menu Speed",
|
||||||
|
"randomizer.gameoptions.menuspeed.instant": "Instant",
|
||||||
|
"randomizer.gameoptions.menuspeed.quadruple": "Quadruple",
|
||||||
|
"randomizer.gameoptions.menuspeed.triple": "Triple",
|
||||||
|
"randomizer.gameoptions.menuspeed.double": "Double",
|
||||||
|
"randomizer.gameoptions.menuspeed.normal": "Normal",
|
||||||
|
"randomizer.gameoptions.menuspeed.half": "Half",
|
||||||
|
|
||||||
|
"randomizer.gameoptions.owpalettes": "Overworld Palettes",
|
||||||
|
"randomizer.gameoptions.owpalettes.default": "Default",
|
||||||
|
"randomizer.gameoptions.owpalettes.random": "Random",
|
||||||
|
"randomizer.gameoptions.owpalettes.blackout": "Blackout",
|
||||||
|
|
||||||
|
"randomizer.gameoptions.uwpalettes": "Underworld Palettes",
|
||||||
|
"randomizer.gameoptions.uwpalettes.default": "Default",
|
||||||
|
"randomizer.gameoptions.uwpalettes.random": "Random",
|
||||||
|
"randomizer.gameoptions.uwpalettes.blackout": "Blackout",
|
||||||
|
|
||||||
|
"randomizer.gameoptions.sprite": "Sprite:",
|
||||||
|
"randomizer.gameoptions.sprite.unchanged": "(unchanged)",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.generation.spoiler": "Create Spoiler Log",
|
||||||
|
"randomizer.generation.suppressrom": "Do not create patched ROM",
|
||||||
|
"randomizer.generation.usestartinventory": "Use starting inventory",
|
||||||
|
"randomizer.generation.usecustompool": "Use custom item pool",
|
||||||
|
|
||||||
|
"randomizer.generation.saveonexit": "Save Settings on Exit",
|
||||||
|
"randomizer.generation.saveonexit.ask": "Ask Me",
|
||||||
|
"randomizer.generation.saveonexit.always": "Always",
|
||||||
|
"randomizer.generation.saveonexit.never": "Never",
|
||||||
|
|
||||||
|
"randomizer.generation.rom": "Base Rom: ",
|
||||||
|
"randomizer.generation.rom.button": "Select Rom",
|
||||||
|
"randomizer.generation.rom.dialog.romfiles": "Rom Files",
|
||||||
|
"randomizer.generation.rom.dialog.allfiles": "All Files",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.item.retro": "Retro mode (universal keys)",
|
||||||
|
|
||||||
|
"randomizer.item.worldstate": "World State",
|
||||||
|
"randomizer.item.worldstate.standard": "Standard",
|
||||||
|
"randomizer.item.worldstate.open": "Open",
|
||||||
|
"randomizer.item.worldstate.inverted": "Inverted",
|
||||||
|
"randomizer.item.worldstate.retro": "Retro",
|
||||||
|
|
||||||
|
"randomizer.item.logiclevel": "Logic Level",
|
||||||
|
"randomizer.item.logiclevel.noglitches": "No Glitches",
|
||||||
|
"randomizer.item.logiclevel.minorglitches": "Minor Glitches",
|
||||||
|
"randomizer.item.logiclevel.nologic": "No Logic",
|
||||||
|
|
||||||
|
"randomizer.item.goal": "Goal",
|
||||||
|
"randomizer.item.goal.ganon": "Defeat Ganon",
|
||||||
|
"randomizer.item.goal.pedestal": "Master Sword Pedestal",
|
||||||
|
"randomizer.item.goal.dungeons": "All Dungeons",
|
||||||
|
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
||||||
|
"randomizer.item.goal.crystals": "Crystals",
|
||||||
|
|
||||||
|
"randomizer.item.crystals_gt": "Crystals to open GT",
|
||||||
|
"randomizer.item.crystals_gt.0": "0",
|
||||||
|
"randomizer.item.crystals_gt.1": "1",
|
||||||
|
"randomizer.item.crystals_gt.2": "2",
|
||||||
|
"randomizer.item.crystals_gt.3": "3",
|
||||||
|
"randomizer.item.crystals_gt.4": "4",
|
||||||
|
"randomizer.item.crystals_gt.5": "5",
|
||||||
|
"randomizer.item.crystals_gt.6": "6",
|
||||||
|
"randomizer.item.crystals_gt.7": "7",
|
||||||
|
"randomizer.item.crystals_gt.random": "Random",
|
||||||
|
|
||||||
|
"randomizer.item.crystals_ganon": "Crystals to harm Ganon",
|
||||||
|
"randomizer.item.crystals_ganon.0": "0",
|
||||||
|
"randomizer.item.crystals_ganon.1": "1",
|
||||||
|
"randomizer.item.crystals_ganon.2": "2",
|
||||||
|
"randomizer.item.crystals_ganon.3": "3",
|
||||||
|
"randomizer.item.crystals_ganon.4": "4",
|
||||||
|
"randomizer.item.crystals_ganon.5": "5",
|
||||||
|
"randomizer.item.crystals_ganon.6": "6",
|
||||||
|
"randomizer.item.crystals_ganon.7": "7",
|
||||||
|
"randomizer.item.crystals_ganon.random": "Random",
|
||||||
|
|
||||||
|
"randomizer.item.weapons": "Weapons",
|
||||||
|
"randomizer.item.weapons.random": "Randomized",
|
||||||
|
"randomizer.item.weapons.assured": "Assured",
|
||||||
|
"randomizer.item.weapons.swordless": "Swordless",
|
||||||
|
"randomizer.item.weapons.vanilla": "Vanilla",
|
||||||
|
|
||||||
|
"randomizer.item.itempool": "Item Pool",
|
||||||
|
"randomizer.item.itempool.normal": "Normal",
|
||||||
|
"randomizer.item.itempool.hard": "Hard",
|
||||||
|
"randomizer.item.itempool.expert": "Expert",
|
||||||
|
|
||||||
|
"randomizer.item.itemfunction": "Item Functionality",
|
||||||
|
"randomizer.item.itemfunction.normal": "Normal",
|
||||||
|
"randomizer.item.itemfunction.hard": "Hard",
|
||||||
|
"randomizer.item.itemfunction.expert": "Expert",
|
||||||
|
|
||||||
|
"randomizer.item.timer": "Timer Setting",
|
||||||
|
"randomizer.item.timer.none": "No Timer",
|
||||||
|
"randomizer.item.timer.display": "Stopwatch",
|
||||||
|
"randomizer.item.timer.timed": "Timed",
|
||||||
|
"randomizer.item.timer.timed-ohko": "Timed OHKO",
|
||||||
|
"randomizer.item.timer.ohko": "OHKO",
|
||||||
|
"randomizer.item.timer.timed-countdown": "Timed Countdown",
|
||||||
|
|
||||||
|
"randomizer.item.progressives": "Progressive Items",
|
||||||
|
"randomizer.item.progressives.on": "On",
|
||||||
|
"randomizer.item.progressives.off": "Off",
|
||||||
|
"randomizer.item.progressives.random": "Random",
|
||||||
|
|
||||||
|
"randomizer.item.accessibility": "Accessibility",
|
||||||
|
"randomizer.item.accessibility.items": "100% Inventory",
|
||||||
|
"randomizer.item.accessibility.locations": "100% Locations",
|
||||||
|
"randomizer.item.accessibility.none": "Beatable",
|
||||||
|
|
||||||
|
"randomizer.item.sortingalgo": "Item Sorting",
|
||||||
|
"randomizer.item.sortingalgo.freshness": "Freshness",
|
||||||
|
"randomizer.item.sortingalgo.flood": "Flood",
|
||||||
|
"randomizer.item.sortingalgo.vt21": "VT8.21",
|
||||||
|
"randomizer.item.sortingalgo.vt22": "VT8.22",
|
||||||
|
"randomizer.item.sortingalgo.vt25": "VT8.25",
|
||||||
|
"randomizer.item.sortingalgo.vt26": "VT8.26",
|
||||||
|
"randomizer.item.sortingalgo.balanced": "Balanced",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.multiworld.worlds": "Worlds",
|
||||||
|
"randomizer.multiworld.names": "Player names",
|
||||||
|
|
||||||
|
|
||||||
|
"bottom.content.seed": "Seed #",
|
||||||
|
"bottom.content.generationcount": "Count",
|
||||||
|
"bottom.content.go": "Generate Patched Rom",
|
||||||
|
"bottom.content.dialog.error": "Error while creating seed",
|
||||||
|
"bottom.content.dialog.success": "Success",
|
||||||
|
"bottom.content.dialog.success.message": "Rom created successfully.",
|
||||||
|
"bottom.content.outputdir": "Open Output Directory",
|
||||||
|
"bottom.content.docs": "Open Documentation"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,13 +11,13 @@
|
|||||||
"selectbox": {
|
"selectbox": {
|
||||||
"side": "right"
|
"side": "right"
|
||||||
},
|
},
|
||||||
"default": "Basic"
|
"default": "basic"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Vanilla": "vanilla",
|
"vanilla",
|
||||||
"Basic": "basic",
|
"basic",
|
||||||
"Crossed": "crossed"
|
"crossed"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"type": "checkbox",
|
"type": "checkbox",
|
||||||
@@ -37,13 +37,13 @@
|
|||||||
"selectbox": {
|
"selectbox": {
|
||||||
"side": "right"
|
"side": "right"
|
||||||
},
|
},
|
||||||
"default": "Auto"
|
"default": "default"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Auto": "default",
|
"default",
|
||||||
"Off": "off",
|
"off",
|
||||||
"On": "on",
|
"on",
|
||||||
"On Compass Pickup": "pickup"
|
"pickup"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Vanilla": "none",
|
"none",
|
||||||
"Shuffled": "shuffled",
|
"shuffled",
|
||||||
"Chaos": "chaos"
|
"chaos"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"bossshuffle": {
|
"bossshuffle": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -40,12 +40,12 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Vanilla": "none",
|
"none",
|
||||||
"Basic": "basic",
|
"basic",
|
||||||
"Shuffled": "shuffled",
|
"shuffled",
|
||||||
"Chaos": "chaos"
|
"chaos"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rightEnemizerFrame": {
|
"rightEnemizerFrame": {
|
||||||
@@ -62,11 +62,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Vanilla": "default",
|
"default",
|
||||||
"Shuffled": "shuffled",
|
"shuffled",
|
||||||
"Chaos": "chaos"
|
"chaos"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"enemyhealth": {
|
"enemyhealth": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -81,13 +81,13 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Vanilla": "default",
|
"default",
|
||||||
"Easy": "easy",
|
"easy",
|
||||||
"Normal": "normal",
|
"normal",
|
||||||
"Hard": "hard",
|
"hard",
|
||||||
"Expert": "expert"
|
"expert"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,20 +21,20 @@
|
|||||||
"label": { "side": "left" },
|
"label": { "side": "left" },
|
||||||
"selectbox": { "side": "right" }
|
"selectbox": { "side": "right" }
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Vanilla": "vanilla",
|
"vanilla",
|
||||||
"Simple": "simple",
|
"simple",
|
||||||
"Restricted": "restricted",
|
"restricted",
|
||||||
"Full": "full",
|
"full",
|
||||||
"Crossed": "crossed",
|
"crossed",
|
||||||
"Insanity": "insanity",
|
"insanity",
|
||||||
"Restricted (Legacy)": "restricted_legacy",
|
"restricted_legacy",
|
||||||
"Full (Legacy)": "full_legacy",
|
"full_legacy",
|
||||||
"Madness (Legacy)": "madness_legacy",
|
"madness_legacy",
|
||||||
"Insanity (Legacy)": "insanity_legacy",
|
"insanity_legacy",
|
||||||
"Dungeons + Full": "dungeonsfull",
|
"dungeonsfull",
|
||||||
"Dungeons + Simple": "dungeonssimple"
|
"dungeonssimple"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,13 +34,13 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Red": "red",
|
"red",
|
||||||
"Blue": "blue",
|
"blue",
|
||||||
"Green": "green",
|
"green",
|
||||||
"Yellow": "yellow",
|
"yellow",
|
||||||
"Random": "random"
|
"random"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"heartbeep": {
|
"heartbeep": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -54,15 +54,15 @@
|
|||||||
"selectbox": {
|
"selectbox": {
|
||||||
"side": "right"
|
"side": "right"
|
||||||
},
|
},
|
||||||
"default": "Normal"
|
"default": "normal"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Double": "double",
|
"double",
|
||||||
"Normal": "normal",
|
"normal",
|
||||||
"Half": "half",
|
"half",
|
||||||
"Quarter": "quarter",
|
"quarter",
|
||||||
"Off": "off"
|
"off"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rightRomOptionsFrame": {
|
"rightRomOptionsFrame": {
|
||||||
@@ -78,16 +78,16 @@
|
|||||||
"selectbox": {
|
"selectbox": {
|
||||||
"side": "right"
|
"side": "right"
|
||||||
},
|
},
|
||||||
"default": "Normal"
|
"default": "normal"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Instant": "instant",
|
"instant",
|
||||||
"Quadruple": "quadruple",
|
"quadruple",
|
||||||
"Triple": "triple",
|
"triple",
|
||||||
"Double": "double",
|
"double",
|
||||||
"Normal": "normal",
|
"normal",
|
||||||
"Half": "half"
|
"half"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"owpalettes": {
|
"owpalettes": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -102,11 +102,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Default": "default",
|
"default",
|
||||||
"Random": "random",
|
"random",
|
||||||
"Blackout": "blackout"
|
"blackout"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"uwpalettes": {
|
"uwpalettes": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -121,11 +121,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Default": "default",
|
"default",
|
||||||
"Random": "random",
|
"random",
|
||||||
"Blackout": "blackout"
|
"blackout"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,10 +36,10 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Ask Me": "ask",
|
"ask",
|
||||||
"Always": "always",
|
"always",
|
||||||
"Never": "never"
|
"never"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
"selectbox": {
|
"selectbox": {
|
||||||
"side": "right"
|
"side": "right"
|
||||||
},
|
},
|
||||||
"default": "Open"
|
"default": "open"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Standard": "standard",
|
"standard",
|
||||||
"Open": "open",
|
"open",
|
||||||
"Inverted": "inverted",
|
"inverted",
|
||||||
"Retro": "retro"
|
"retro"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"logiclevel": {
|
"logiclevel": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -42,11 +42,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"No Glitches": "noglitches",
|
"noglitches",
|
||||||
"Minor Glitches": "minorglitches",
|
"minorglitches",
|
||||||
"No Logic": "nologic"
|
"nologic"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"goal": {
|
"goal": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -61,13 +61,13 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Defeat Ganon": "ganon",
|
"ganon",
|
||||||
"Master Sword Pedestal": "pedestal",
|
"pedestal",
|
||||||
"All Dungeons": "dungeons",
|
"dungeons",
|
||||||
"Triforce Hunt": "triforcehunt",
|
"triforcehunt",
|
||||||
"Crystals": "crystals"
|
"crystals"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"crystals_gt": {
|
"crystals_gt": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -82,17 +82,17 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"0": "0",
|
"0",
|
||||||
"1": "1",
|
"1",
|
||||||
"2": "2",
|
"2",
|
||||||
"3": "3",
|
"3",
|
||||||
"4": "4",
|
"4",
|
||||||
"5": "5",
|
"5",
|
||||||
"6": "6",
|
"6",
|
||||||
"7": "7",
|
"7",
|
||||||
"Random": "random"
|
"random"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"crystals_ganon": {
|
"crystals_ganon": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -107,17 +107,17 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"0": "0",
|
"0",
|
||||||
"1": "1",
|
"1",
|
||||||
"2": "2",
|
"2",
|
||||||
"3": "3",
|
"3",
|
||||||
"4": "4",
|
"4",
|
||||||
"5": "5",
|
"5",
|
||||||
"6": "6",
|
"6",
|
||||||
"7": "7",
|
"7",
|
||||||
"Random": "random"
|
"random"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"weapons": {
|
"weapons": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -132,12 +132,12 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Randomized": "random",
|
"random",
|
||||||
"Assured": "assured",
|
"assured",
|
||||||
"Swordless": "swordless",
|
"swordless",
|
||||||
"Vanilla": "vanilla"
|
"vanilla"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rightItemFrame": {
|
"rightItemFrame": {
|
||||||
@@ -154,11 +154,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Normal": "normal",
|
"normal",
|
||||||
"Hard": "hard",
|
"hard",
|
||||||
"Expert": "expert"
|
"expert"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"itemfunction": {
|
"itemfunction": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -173,11 +173,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Normal": "normal",
|
"normal",
|
||||||
"Hard": "hard",
|
"hard",
|
||||||
"Expert": "expert"
|
"expert"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"timer": {
|
"timer": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -192,14 +192,14 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"No Timer": "none",
|
"none",
|
||||||
"Stopwatch": "display",
|
"display",
|
||||||
"Timed": "timed",
|
"timed",
|
||||||
"Timed OHKO": "timed-ohko",
|
"timed-ohko",
|
||||||
"OHKO": "ohko",
|
"ohko",
|
||||||
"Timed Countdown": "timed-countdown"
|
"timed-countdown"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"progressives": {
|
"progressives": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -214,11 +214,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"On": "on",
|
"on",
|
||||||
"Off": "off",
|
"off",
|
||||||
"Random": "random"
|
"random"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"accessibility": {
|
"accessibility": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -233,11 +233,11 @@
|
|||||||
"side": "right"
|
"side": "right"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"100% Inventory": "items",
|
"items",
|
||||||
"100% Locations": "locations",
|
"locations",
|
||||||
"Beatable": "none"
|
"none"
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"sortingalgo": {
|
"sortingalgo": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
@@ -251,17 +251,17 @@
|
|||||||
"selectbox": {
|
"selectbox": {
|
||||||
"side": "right"
|
"side": "right"
|
||||||
},
|
},
|
||||||
"default": "Balanced"
|
"default": "balanced"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": [
|
||||||
"Freshness": "freshness",
|
"freshness",
|
||||||
"Flood": "flood",
|
"flood",
|
||||||
"VT8.21": "vt21",
|
"vt21",
|
||||||
"VT8.22": "vt22",
|
"vt22",
|
||||||
"VT8.25": "vt25",
|
"vt25",
|
||||||
"VT8.26": "vt26",
|
"vt26",
|
||||||
"Balanced": "balanced"
|
"balanced"
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
resources/app/meta/lang/en.json
Normal file
3
resources/app/meta/lang/en.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
112
source/classes/BabelFish.py
Normal file
112
source/classes/BabelFish.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import json
|
||||||
|
import locale
|
||||||
|
import os
|
||||||
|
|
||||||
|
class BabelFish():
|
||||||
|
def __init__(self,subpath=["resources","app","meta"],lang=None):
|
||||||
|
localization_string = locale.getdefaultlocale()[0] #get set localization
|
||||||
|
self.locale = localization_string[:2] if lang is None else lang #let caller override localization
|
||||||
|
self.langs = ["en"] #start with English
|
||||||
|
if(not self.locale == "en"): #add localization
|
||||||
|
self.langs.append(self.locale)
|
||||||
|
|
||||||
|
self.lang_defns = {} #collect translations
|
||||||
|
self.add_translation_file() #start with default translation file
|
||||||
|
self.add_translation_file(["resources","app","cli"]) #add help translation file
|
||||||
|
self.add_translation_file(["resources","app","gui"]) #add gui label translation file
|
||||||
|
self.add_translation_file(["resources","user","meta"]) #add user translation file
|
||||||
|
|
||||||
|
def add_translation_file(self,subpath=["resources","app","meta"]):
|
||||||
|
if not isinstance(subpath, list):
|
||||||
|
subpath = [subpath]
|
||||||
|
if "lang" not in subpath:
|
||||||
|
subpath.append("lang") #look in lang folder
|
||||||
|
subpath = os.path.join(*subpath) #put in path separators
|
||||||
|
key = subpath.split(os.sep)
|
||||||
|
for check in ["resources","app","user"]:
|
||||||
|
if check in key:
|
||||||
|
key.remove(check)
|
||||||
|
key = os.path.join(*key) #put in path separators
|
||||||
|
for lang in self.langs:
|
||||||
|
if not lang in self.lang_defns:
|
||||||
|
self.lang_defns[lang] = {}
|
||||||
|
langs_filename = os.path.join(subpath,lang + ".json") #get filename of translation file
|
||||||
|
if os.path.isfile(langs_filename): #if we've got a file
|
||||||
|
with open(langs_filename,encoding="utf-8") as f: #open it
|
||||||
|
self.lang_defns[lang][key[:key.rfind(os.sep)].replace(os.sep,'.')] = json.load(f) #save translation definitions
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
# print(langs_filename + " not found for translation!")
|
||||||
|
|
||||||
|
def translate(self, domain="", key="", subkey=""): #three levels of keys
|
||||||
|
# start with nothing
|
||||||
|
display_text = ""
|
||||||
|
|
||||||
|
# exits check for not exit first and then append Exit at end
|
||||||
|
# multiRooms check for not chest name first and then append chest name at end
|
||||||
|
specials = {
|
||||||
|
"exit": False,
|
||||||
|
"multiRoom": False
|
||||||
|
}
|
||||||
|
|
||||||
|
# Domain
|
||||||
|
if os.sep in domain:
|
||||||
|
domain = domain.replace(os.sep,'.')
|
||||||
|
# display_text = domain
|
||||||
|
|
||||||
|
# Operate on Key
|
||||||
|
if key != "":
|
||||||
|
if display_text != "":
|
||||||
|
display_text += '.'
|
||||||
|
# display_text += key
|
||||||
|
# Exits
|
||||||
|
if "exit" in key and "gui" not in domain:
|
||||||
|
key = key.replace("exit","")
|
||||||
|
specials["exit"] = True
|
||||||
|
if "Exit" in key and "gui" not in domain:
|
||||||
|
key = key.replace("Exit","")
|
||||||
|
specials["exit"] = True
|
||||||
|
# Locations
|
||||||
|
tmp = key.split(" - ")
|
||||||
|
if len(tmp) >= 2:
|
||||||
|
specials["multiRoom"] = tmp[len(tmp) - 1]
|
||||||
|
tmp.pop()
|
||||||
|
key = " - ".join(tmp)
|
||||||
|
key = key.strip()
|
||||||
|
|
||||||
|
# Operate on Subkey
|
||||||
|
if subkey != "":
|
||||||
|
if display_text != "":
|
||||||
|
display_text += '.'
|
||||||
|
display_text += subkey
|
||||||
|
# Exits
|
||||||
|
if "exit" in subkey and "gui" not in domain:
|
||||||
|
subkey = subkey.replace("exit","")
|
||||||
|
specials["exit"] = True
|
||||||
|
if "Exit" in subkey and "gui" not in domain:
|
||||||
|
subkey = subkey.replace("Exit","")
|
||||||
|
specials["exit"] = True
|
||||||
|
# Locations
|
||||||
|
tmp = subkey.split(" - ")
|
||||||
|
if len(tmp) >= 2:
|
||||||
|
specials["multiRoom"] = tmp[len(tmp) - 1]
|
||||||
|
tmp.pop()
|
||||||
|
subkey = " - ".join(tmp)
|
||||||
|
subkey = subkey.strip()
|
||||||
|
|
||||||
|
my_lang = self.lang_defns[self.locale] #handle for localization
|
||||||
|
en_lang = self.lang_defns["en"] #handle for English
|
||||||
|
|
||||||
|
if domain in my_lang and key in my_lang[domain] and subkey in my_lang[domain][key] and not my_lang[domain][key][subkey] == "": #get localization first
|
||||||
|
display_text = my_lang[domain][key][subkey]
|
||||||
|
elif domain in en_lang and key in en_lang[domain] and subkey in en_lang[domain][key] and not en_lang[domain][key][subkey] == "": #gracefully degrade to English
|
||||||
|
display_text = en_lang[domain][key][subkey]
|
||||||
|
elif specials["exit"]:
|
||||||
|
specials["exit"] = False
|
||||||
|
|
||||||
|
if specials["exit"]:
|
||||||
|
display_text += " Exit"
|
||||||
|
elif specials["multiRoom"] and specials["multiRoom"] not in display_text:
|
||||||
|
display_text += " - " + specials["multiRoom"]
|
||||||
|
|
||||||
|
return display_text
|
||||||
3
source/classes/Empty.py
Normal file
3
source/classes/Empty.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Need a dummy class
|
||||||
|
class Empty():
|
||||||
|
pass
|
||||||
@@ -110,5 +110,11 @@ SETTINGSTOPROCESS = {
|
|||||||
"usecustompool": "custom",
|
"usecustompool": "custom",
|
||||||
"saveonexit": "saveonexit"
|
"saveonexit": "saveonexit"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bottom": {
|
||||||
|
"content": {
|
||||||
|
"seed": "seed",
|
||||||
|
"generationcount": "count"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from Main import main
|
|||||||
from Utils import local_path, output_path, open_file
|
from Utils import local_path, output_path, open_file
|
||||||
import source.classes.constants as CONST
|
import source.classes.constants as CONST
|
||||||
import source.gui.widgets as widgets
|
import source.gui.widgets as widgets
|
||||||
|
from source.classes.Empty import Empty
|
||||||
|
|
||||||
|
|
||||||
def bottom_frame(self, parent, args=None):
|
def bottom_frame(self, parent, args=None):
|
||||||
@@ -17,19 +18,34 @@ def bottom_frame(self, parent, args=None):
|
|||||||
# Bottom Frame options
|
# Bottom Frame options
|
||||||
self.widgets = {}
|
self.widgets = {}
|
||||||
|
|
||||||
seedCountFrame = Frame(self)
|
# Seed input
|
||||||
seedCountFrame.pack()
|
# widget ID
|
||||||
## Seed #
|
widget = "seed"
|
||||||
seedLabel = Label(self, text='Seed #')
|
|
||||||
|
# Empty object
|
||||||
|
self.widgets[widget] = Empty()
|
||||||
|
# pieces
|
||||||
|
self.widgets[widget].pieces = {}
|
||||||
|
|
||||||
|
# frame
|
||||||
|
self.widgets[widget].pieces["frame"] = Frame(self)
|
||||||
|
# frame: label
|
||||||
|
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text="Seed #")
|
||||||
|
self.widgets[widget].pieces["frame"].label.pack(side=LEFT)
|
||||||
|
# storagevar
|
||||||
savedSeed = parent.settings["seed"]
|
savedSeed = parent.settings["seed"]
|
||||||
self.seedVar = StringVar(value=savedSeed)
|
self.widgets[widget].storageVar = StringVar(value=savedSeed)
|
||||||
|
# textbox
|
||||||
|
self.widgets[widget].type = "textbox"
|
||||||
|
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"], width=15, textvariable=self.widgets[widget].storageVar)
|
||||||
|
self.widgets[widget].pieces["textbox"].pack(side=LEFT)
|
||||||
|
|
||||||
def saveSeed(caller,_,mode):
|
def saveSeed(caller,_,mode):
|
||||||
savedSeed = self.seedVar.get()
|
savedSeed = self.widgets["seed"].storageVar.get()
|
||||||
parent.settings["seed"] = int(savedSeed) if savedSeed.isdigit() else None
|
parent.settings["seed"] = int(savedSeed) if savedSeed.isdigit() else None
|
||||||
self.seedVar.trace_add("write",saveSeed)
|
self.widgets[widget].storageVar.trace_add("write",saveSeed)
|
||||||
seedEntry = Entry(self, width=15, textvariable=self.seedVar)
|
# frame: pack
|
||||||
seedLabel.pack(side=LEFT)
|
self.widgets[widget].pieces["frame"].pack(side=LEFT)
|
||||||
seedEntry.pack(side=LEFT)
|
|
||||||
|
|
||||||
## Number of Generation attempts
|
## Number of Generation attempts
|
||||||
key = "generationcount"
|
key = "generationcount"
|
||||||
@@ -56,10 +72,10 @@ def bottom_frame(self, parent, args=None):
|
|||||||
if guiargs.count is not None:
|
if guiargs.count is not None:
|
||||||
seed = guiargs.seed
|
seed = guiargs.seed
|
||||||
for _ in range(guiargs.count):
|
for _ in range(guiargs.count):
|
||||||
main(seed=seed, args=guiargs)
|
main(seed=seed, args=guiargs, fish=parent.fish)
|
||||||
seed = random.randint(0, 999999999)
|
seed = random.randint(0, 999999999)
|
||||||
else:
|
else:
|
||||||
main(seed=guiargs.seed, args=guiargs)
|
main(seed=guiargs.seed, args=guiargs, fish=parent.fish)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||||
@@ -67,8 +83,19 @@ def bottom_frame(self, parent, args=None):
|
|||||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||||
|
|
||||||
## Generate Button
|
## Generate Button
|
||||||
generateButton = Button(self, text='Generate Patched Rom', command=generateRom)
|
# widget ID
|
||||||
generateButton.pack(side=LEFT)
|
widget = "go"
|
||||||
|
|
||||||
|
# Empty object
|
||||||
|
self.widgets[widget] = Empty()
|
||||||
|
# pieces
|
||||||
|
self.widgets[widget].pieces = {}
|
||||||
|
|
||||||
|
# button
|
||||||
|
self.widgets[widget].type = "button"
|
||||||
|
self.widgets[widget].pieces["button"] = Button(self, text='Generate Patched Rom', command=generateRom)
|
||||||
|
# button: pack
|
||||||
|
self.widgets[widget].pieces["button"].pack(side=LEFT)
|
||||||
|
|
||||||
def open_output():
|
def open_output():
|
||||||
if args and args.outputpath:
|
if args and args.outputpath:
|
||||||
@@ -76,15 +103,42 @@ def bottom_frame(self, parent, args=None):
|
|||||||
else:
|
else:
|
||||||
open_file(output_path(parent.settings["outputpath"]))
|
open_file(output_path(parent.settings["outputpath"]))
|
||||||
|
|
||||||
openOutputButton = Button(self, text='Open Output Directory', command=open_output)
|
## Output Button
|
||||||
openOutputButton.pack(side=RIGHT)
|
# widget ID
|
||||||
|
widget = "outputdir"
|
||||||
|
|
||||||
|
# Empty object
|
||||||
|
self.widgets[widget] = Empty()
|
||||||
|
# pieces
|
||||||
|
self.widgets[widget].pieces = {}
|
||||||
|
|
||||||
|
# storagevar
|
||||||
|
self.widgets[widget].storageVar = StringVar(value=parent.settings["outputpath"])
|
||||||
|
|
||||||
|
# button
|
||||||
|
self.widgets[widget].type = "button"
|
||||||
|
self.widgets[widget].pieces["button"] = Button(self, text='Open Output Directory', command=open_output)
|
||||||
|
# button: pack
|
||||||
|
self.widgets[widget].pieces["button"].pack(side=RIGHT)
|
||||||
|
|
||||||
## Documentation Button
|
## Documentation Button
|
||||||
|
# widget ID
|
||||||
|
widget = "docs"
|
||||||
|
|
||||||
|
# Empty object
|
||||||
|
self.widgets[widget] = Empty()
|
||||||
|
# pieces
|
||||||
|
self.widgets[widget].pieces = {}
|
||||||
|
# button
|
||||||
|
self.widgets[widget].type = "button"
|
||||||
|
self.widgets[widget].selectbox = Empty()
|
||||||
|
self.widgets[widget].selectbox.storageVar = Empty()
|
||||||
if os.path.exists(local_path('README.html')):
|
if os.path.exists(local_path('README.html')):
|
||||||
def open_readme():
|
def open_readme():
|
||||||
open_file(local_path('README.html'))
|
open_file(local_path('README.html'))
|
||||||
openReadmeButton = Button(self, text='Open Documentation', command=open_readme)
|
self.widgets[widget].pieces["button"] = Button(self, text='Open Documentation', command=open_readme)
|
||||||
openReadmeButton.pack(side=RIGHT)
|
# button: pack
|
||||||
|
self.widgets[widget].pieces["button"].pack(side=RIGHT)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -107,22 +161,26 @@ def create_guiargs(parent):
|
|||||||
setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get())
|
setattr(guiargs, arg, parent.pages[mainpage].pages[subpage].widgets[widget].storageVar.get())
|
||||||
|
|
||||||
# Get EnemizerCLI setting
|
# Get EnemizerCLI setting
|
||||||
guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.get()
|
guiargs.enemizercli = parent.pages["randomizer"].pages["enemizer"].widgets["enemizercli"].storageVar.get()
|
||||||
|
|
||||||
# Get Multiworld Worlds count
|
# Get Multiworld Worlds count
|
||||||
guiargs.multi = int(parent.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.get())
|
guiargs.multi = int(parent.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.get())
|
||||||
|
|
||||||
# Get baserom path
|
# Get baserom path
|
||||||
guiargs.rom = parent.pages["randomizer"].pages["generation"].romVar.get()
|
guiargs.rom = parent.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
|
||||||
|
|
||||||
# Get if we're using the Custom Item Pool
|
# Get if we're using the Custom Item Pool
|
||||||
guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get())
|
guiargs.custom = bool(parent.pages["randomizer"].pages["generation"].widgets["usecustompool"].storageVar.get())
|
||||||
|
|
||||||
# Get Seed ID
|
# Get Seed ID
|
||||||
guiargs.seed = int(parent.frames["bottom"].seedVar.get()) if parent.frames["bottom"].seedVar.get() else None
|
guiargs.seed = None
|
||||||
|
if parent.pages["bottom"].pages["content"].widgets["seed"].storageVar.get():
|
||||||
|
guiargs.seed = parent.pages["bottom"].pages["content"].widgets["seed"].storageVar.get()
|
||||||
|
|
||||||
# Get number of generations to run
|
# Get number of generations to run
|
||||||
guiargs.count = int(parent.frames["bottom"].widgets["generationcount"].storageVar.get()) if parent.frames["bottom"].widgets["generationcount"].storageVar.get() != '1' else None
|
guiargs.count = 1
|
||||||
|
if parent.pages["bottom"].pages["content"].widgets["generationcount"].storageVar.get():
|
||||||
|
guiargs.count = int(parent.pages["bottom"].pages["content"].widgets["generationcount"].storageVar.get())
|
||||||
|
|
||||||
# Get Adjust settings
|
# Get Adjust settings
|
||||||
adjustargs = {
|
adjustargs = {
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ from source.classes.SpriteSelector import SpriteSelector as spriteSelector
|
|||||||
from source.gui.randomize.gameoptions import set_sprite
|
from source.gui.randomize.gameoptions import set_sprite
|
||||||
from Rom import Sprite, get_sprite_from_name
|
from Rom import Sprite, get_sprite_from_name
|
||||||
import source.classes.constants as CONST
|
import source.classes.constants as CONST
|
||||||
|
from source.classes.BabelFish import BabelFish
|
||||||
|
from source.classes.Empty import Empty
|
||||||
|
|
||||||
# Load args/settings for most tabs
|
# Load args/settings for most tabs
|
||||||
def loadcliargs(gui, args, settings=None):
|
def loadcliargs(gui, args, settings=None):
|
||||||
if args is not None:
|
if args is not None:
|
||||||
|
fish = BabelFish()
|
||||||
# for k, v in vars(args).items():
|
# for k, v in vars(args).items():
|
||||||
# if type(v) is dict:
|
# if type(v) is dict:
|
||||||
# setattr(args, k, v[1]) # only get values for player 1 for now
|
# setattr(args, k, v[1]) # only get values for player 1 for now
|
||||||
@@ -21,39 +24,146 @@ def loadcliargs(gui, args, settings=None):
|
|||||||
for subpage in options[mainpage]:
|
for subpage in options[mainpage]:
|
||||||
# Cycle through each widget
|
# Cycle through each widget
|
||||||
for widget in options[mainpage][subpage]:
|
for widget in options[mainpage][subpage]:
|
||||||
# Get the value and set it
|
if widget in gui.pages[mainpage].pages[subpage].widgets:
|
||||||
arg = options[mainpage][subpage][widget]
|
thisType = ""
|
||||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
|
# Get the value and set it
|
||||||
# If we're on the Game Options page and it's not about Hints
|
arg = options[mainpage][subpage][widget]
|
||||||
if subpage == "gameoptions" and not widget == "hints":
|
if args[arg] == None:
|
||||||
# Check if we've got settings
|
args[arg] = ""
|
||||||
# Check if we've got the widget in Adjust settings
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
hasSettings = settings is not None
|
if hasattr(gui.pages[mainpage].pages[subpage].widgets[widget],"type"):
|
||||||
hasWidget = ("adjust." + widget) in settings if hasSettings else None
|
thisType = gui.pages[mainpage].pages[subpage].widgets[widget].type
|
||||||
if hasWidget is None:
|
if thisType == "checkbox":
|
||||||
# If we've got a Game Options val and we don't have an Adjust val, use the Game Options val
|
gui.pages[mainpage].pages[subpage].widgets[widget].checkbox.configure(text=label)
|
||||||
gui.pages["adjust"].content.widgets[widget].storageVar.set(args[arg])
|
elif thisType == "selectbox":
|
||||||
|
theseOptions = gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||||
|
i = 0
|
||||||
|
for value in theseOptions["values"]:
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options["labels"][i] = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + '.' + value)
|
||||||
|
i += 1
|
||||||
|
for i in range(0, len(theseOptions["values"])):
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox["menu"].entryconfigure(i, label=theseOptions["labels"][i])
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].selectbox.options = theseOptions
|
||||||
|
elif thisType == "spinbox":
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
|
||||||
|
# If we're on the Game Options page and it's not about Hints
|
||||||
|
if subpage == "gameoptions" and not widget == "hints":
|
||||||
|
# Check if we've got settings
|
||||||
|
# Check if we've got the widget in Adjust settings
|
||||||
|
hasSettings = settings is not None
|
||||||
|
hasWidget = ("adjust." + widget) in settings if hasSettings else None
|
||||||
|
label = fish.translate("gui","gui","adjust." + widget)
|
||||||
|
if ("adjust." + widget) in label:
|
||||||
|
label = fish.translate("gui","gui","randomizer.gameoptions." + widget)
|
||||||
|
if hasattr(gui.pages["adjust"].content.widgets[widget],"type"):
|
||||||
|
type = gui.pages["adjust"].content.widgets[widget].type
|
||||||
|
if type == "checkbox":
|
||||||
|
gui.pages["adjust"].content.widgets[widget].checkbox.configure(text=label)
|
||||||
|
elif type == "selectbox":
|
||||||
|
gui.pages["adjust"].content.widgets[widget].label.configure(text=label)
|
||||||
|
if hasWidget is None:
|
||||||
|
# If we've got a Game Options val and we don't have an Adjust val, use the Game Options val
|
||||||
|
gui.pages["adjust"].content.widgets[widget].storageVar.set(args[arg])
|
||||||
|
|
||||||
# Get EnemizerCLI setting
|
# Get EnemizerCLI setting
|
||||||
gui.pages["randomizer"].pages["enemizer"].enemizerCLIpathVar.set(args["enemizercli"])
|
mainpage = "randomizer"
|
||||||
|
subpage = "enemizer"
|
||||||
|
widget = "enemizercli"
|
||||||
|
setting = "enemizercli"
|
||||||
|
# set storagevar
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[setting])
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||||
|
# set get from web label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + ".online")
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["online"].label.configure(text=label)
|
||||||
|
|
||||||
# Get baserom path
|
# Get baserom path
|
||||||
gui.pages["randomizer"].pages["generation"].romVar.set(args["rom"])
|
mainpage = "randomizer"
|
||||||
|
subpage = "generation"
|
||||||
|
widget = "rom"
|
||||||
|
setting = "rom"
|
||||||
|
# set storagevar
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[setting])
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||||
|
# set button label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget + ".button")
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||||
|
|
||||||
# Get Multiworld Worlds count
|
# Get Multiworld Worlds count
|
||||||
if args["multi"]:
|
mainpage = "randomizer"
|
||||||
gui.pages["randomizer"].pages["multiworld"].widgets["worlds"].storageVar.set(str(args["multi"]))
|
subpage = "multiworld"
|
||||||
|
widget = "worlds"
|
||||||
|
setting = "multi"
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||||
|
if args[setting]:
|
||||||
|
# set storagevar
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(str(args[setting]))
|
||||||
|
|
||||||
|
# Set Multiworld Names
|
||||||
|
mainpage = "randomizer"
|
||||||
|
subpage = "multiworld"
|
||||||
|
widget = "names"
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||||
|
|
||||||
# Get Seed ID
|
# Get Seed ID
|
||||||
if args["seed"]:
|
mainpage = "bottom"
|
||||||
gui.frames["bottom"].seedVar.set(str(args["seed"]))
|
subpage = "content"
|
||||||
|
widget = "seed"
|
||||||
|
setting = "seed"
|
||||||
|
if args[setting]:
|
||||||
|
gui.pages[mainpage].widgets[widget].storageVar.set(args[setting])
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["frame"].label.configure(text=label)
|
||||||
|
|
||||||
# Get number of generations to run
|
# Get number of generations to run
|
||||||
if args["count"]:
|
mainpage = "bottom"
|
||||||
gui.frames["bottom"].widgets["generationcount"].storageVar.set(str(args["count"]))
|
subpage = "content"
|
||||||
|
widget = "generationcount"
|
||||||
|
setting = "count"
|
||||||
|
if args[setting]:
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(str(args[setting]))
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||||
|
|
||||||
|
# Set Generate button
|
||||||
|
mainpage = "bottom"
|
||||||
|
subpage = "content"
|
||||||
|
widget = "go"
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||||
|
|
||||||
|
# Set Output Directory button
|
||||||
|
mainpage = "bottom"
|
||||||
|
subpage = "content"
|
||||||
|
widget = "outputdir"
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||||
# Get output path
|
# Get output path
|
||||||
gui.outputPath.set(args["outputpath"])
|
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args["outputpath"])
|
||||||
|
|
||||||
|
# Set Documentation button
|
||||||
|
mainpage = "bottom"
|
||||||
|
subpage = "content"
|
||||||
|
widget = "docs"
|
||||||
|
if widget in gui.pages[mainpage].pages[subpage].widgets:
|
||||||
|
if "button" in gui.pages[mainpage].pages[subpage].widgets[widget].pieces:
|
||||||
|
# set textbox/frame label
|
||||||
|
label = fish.translate("gui","gui",mainpage + '.' + subpage + '.' + widget)
|
||||||
|
gui.pages[mainpage].pages[subpage].widgets[widget].pieces["button"].configure(text=label)
|
||||||
|
|
||||||
# Figure out Sprite Selection
|
# Figure out Sprite Selection
|
||||||
def sprite_setter(spriteObject):
|
def sprite_setter(spriteObject):
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import source.gui.widgets as widgets
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from source.classes.Empty import Empty
|
||||||
|
|
||||||
def enemizer_page(parent,settings):
|
def enemizer_page(parent,settings):
|
||||||
def open_enemizer_download(_evt):
|
def open_enemizer_download(_evt):
|
||||||
@@ -46,22 +47,43 @@ def enemizer_page(parent,settings):
|
|||||||
|
|
||||||
## Enemizer CLI Path
|
## Enemizer CLI Path
|
||||||
# This one's more-complicated, build it and stuff it
|
# This one's more-complicated, build it and stuff it
|
||||||
enemizerPathFrame = Frame(self.frames["bottomEnemizerFrame"])
|
# widget ID
|
||||||
enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ")
|
widget = "enemizercli"
|
||||||
enemizerCLIlabel.pack(side=LEFT)
|
|
||||||
enemizerURL = Label(enemizerPathFrame, text="(get online)", fg="blue", cursor="hand2")
|
# Empty object
|
||||||
enemizerURL.pack(side=LEFT)
|
self.widgets[widget] = Empty()
|
||||||
enemizerURL.bind("<Button-1>", open_enemizer_download)
|
# pieces
|
||||||
self.enemizerCLIpathVar = StringVar(value=settings["enemizercli"])
|
self.widgets[widget].pieces = {}
|
||||||
enemizerCLIpathEntry = Entry(enemizerPathFrame, textvariable=self.enemizerCLIpathVar)
|
|
||||||
enemizerCLIpathEntry.pack(side=LEFT, fill=X, expand=True)
|
# frame
|
||||||
|
self.widgets[widget].pieces["frame"] = Frame(self.frames["bottomEnemizerFrame"])
|
||||||
|
# frame: label
|
||||||
|
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text="EnemizerCLI path: ")
|
||||||
|
self.widgets[widget].pieces["frame"].label.pack(side=LEFT)
|
||||||
|
|
||||||
|
# get app online
|
||||||
|
self.widgets[widget].pieces["online"] = Empty()
|
||||||
|
# get app online: label
|
||||||
|
self.widgets[widget].pieces["online"].label = Label(self.widgets[widget].pieces["frame"], text="(get online)", fg="blue", cursor="hand2")
|
||||||
|
self.widgets[widget].pieces["online"].label.pack(side=LEFT)
|
||||||
|
# get app online: open browser
|
||||||
|
self.widgets[widget].pieces["online"].label.bind("<Button-1>", open_enemizer_download)
|
||||||
|
# storage var
|
||||||
|
self.widgets[widget].storageVar = StringVar(value=settings["enemizercli"])
|
||||||
|
# textbox
|
||||||
|
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"], textvariable=self.widgets[widget].storageVar)
|
||||||
|
self.widgets[widget].pieces["textbox"].pack(side=LEFT, fill=X, expand=True)
|
||||||
|
|
||||||
def EnemizerSelectPath():
|
def EnemizerSelectPath():
|
||||||
path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")], initialdir=os.path.join("."))
|
path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")], initialdir=os.path.join("."))
|
||||||
if path:
|
if path:
|
||||||
self.enemizerCLIpathVar.set(path)
|
self.widgets[widget].storageVar.set(path)
|
||||||
settings["enemizercli"] = path
|
settings["enemizercli"] = path
|
||||||
enemizerCLIbrowseButton = Button(enemizerPathFrame, text='...', command=EnemizerSelectPath)
|
# dialog button
|
||||||
enemizerCLIbrowseButton.pack(side=LEFT)
|
self.widgets[widget].pieces["opendialog"] = Button(self.widgets[widget].pieces["frame"], text='...', command=EnemizerSelectPath)
|
||||||
enemizerPathFrame.pack(fill=X)
|
self.widgets[widget].pieces["opendialog"].pack(side=LEFT)
|
||||||
|
|
||||||
|
# frame: pack
|
||||||
|
self.widgets[widget].pieces["frame"].pack(fill=X)
|
||||||
|
|
||||||
return self,settings
|
return self,settings
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, E,
|
|||||||
import source.gui.widgets as widgets
|
import source.gui.widgets as widgets
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from source.classes.Empty import Empty
|
||||||
|
|
||||||
def generation_page(parent,settings):
|
def generation_page(parent,settings):
|
||||||
# Generation Setup
|
# Generation Setup
|
||||||
@@ -28,20 +29,38 @@ def generation_page(parent,settings):
|
|||||||
self.frames["baserom"].pack(anchor=W, fill=X)
|
self.frames["baserom"].pack(anchor=W, fill=X)
|
||||||
## Locate base ROM
|
## Locate base ROM
|
||||||
# This one's more-complicated, build it and stuff it
|
# This one's more-complicated, build it and stuff it
|
||||||
baseRomFrame = Frame(self.frames["baserom"])
|
# widget ID
|
||||||
baseRomLabel = Label(baseRomFrame, text='Base Rom: ')
|
widget = "rom"
|
||||||
self.romVar = StringVar()
|
|
||||||
romEntry = Entry(baseRomFrame, textvariable=self.romVar)
|
|
||||||
self.romVar.set(settings["rom"])
|
|
||||||
|
|
||||||
|
# Empty object
|
||||||
|
self.widgets[widget] = Empty()
|
||||||
|
# pieces
|
||||||
|
self.widgets[widget].pieces = {}
|
||||||
|
|
||||||
|
# frame
|
||||||
|
self.widgets[widget].pieces["frame"] = Frame(self.frames["baserom"])
|
||||||
|
# frame: label
|
||||||
|
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text='Base Rom: ')
|
||||||
|
# storage var
|
||||||
|
self.widgets[widget].storageVar = StringVar()
|
||||||
|
# textbox
|
||||||
|
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"], textvariable=self.widgets[widget].storageVar)
|
||||||
|
self.widgets[widget].storageVar.set(settings["rom"])
|
||||||
|
|
||||||
|
# FIXME: Translate these
|
||||||
def RomSelect():
|
def RomSelect():
|
||||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")], initialdir=os.path.join("."))
|
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")], initialdir=os.path.join("."))
|
||||||
self.romVar.set(rom)
|
self.widgets[widget].storageVar.set(rom)
|
||||||
romSelectButton = Button(baseRomFrame, text='Select Rom', command=RomSelect)
|
# dialog button
|
||||||
|
self.widgets[widget].pieces["button"] = Button(self.widgets[widget].pieces["frame"], text='Select Rom', command=RomSelect)
|
||||||
|
|
||||||
baseRomLabel.pack(side=LEFT)
|
# frame label: pack
|
||||||
romEntry.pack(side=LEFT, fill=X, expand=True)
|
self.widgets[widget].pieces["frame"].label.pack(side=LEFT)
|
||||||
romSelectButton.pack(side=LEFT)
|
# textbox: pack
|
||||||
baseRomFrame.pack(fill=X)
|
self.widgets[widget].pieces["textbox"].pack(side=LEFT, fill=X, expand=True)
|
||||||
|
# button: pack
|
||||||
|
self.widgets[widget].pieces["button"].pack(side=LEFT)
|
||||||
|
# frame: pack
|
||||||
|
self.widgets[widget].pieces["frame"].pack(fill=X)
|
||||||
|
|
||||||
return self,settings
|
return self,settings
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, X, LEFT
|
|||||||
import source.gui.widgets as widgets
|
import source.gui.widgets as widgets
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from source.classes.Empty import Empty
|
||||||
|
|
||||||
def multiworld_page(parent,settings):
|
def multiworld_page(parent,settings):
|
||||||
# Multiworld
|
# Multiworld
|
||||||
@@ -26,16 +27,33 @@ def multiworld_page(parent,settings):
|
|||||||
|
|
||||||
## List of Player Names
|
## List of Player Names
|
||||||
# This one's more-complicated, build it and stuff it
|
# This one's more-complicated, build it and stuff it
|
||||||
key = "names"
|
# widget ID
|
||||||
self.widgets[key] = Frame(self.frames["widgets"])
|
widget = "names"
|
||||||
self.widgets[key].label = Label(self.widgets[key], text='Player names')
|
|
||||||
self.widgets[key].storageVar = StringVar(value=settings["names"])
|
# Empty object
|
||||||
|
self.widgets[widget] = Empty()
|
||||||
|
# pieces
|
||||||
|
self.widgets[widget].pieces = {}
|
||||||
|
|
||||||
|
# frame
|
||||||
|
self.widgets[widget].pieces["frame"] = Frame(self.frames["widgets"])
|
||||||
|
# frame: label
|
||||||
|
self.widgets[widget].pieces["frame"].label = Label(self.widgets[widget].pieces["frame"], text='Player names')
|
||||||
|
# storage var
|
||||||
|
self.widgets[widget].storageVar = StringVar(value=settings["names"])
|
||||||
|
|
||||||
|
# FIXME: Got some strange behavior here; both Entry-like objects react to mousewheel on Spinbox
|
||||||
def saveMultiNames(caller,_,mode):
|
def saveMultiNames(caller,_,mode):
|
||||||
settings["names"] = self.widgets[key].storageVar.get()
|
settings["names"] = self.widgets["names"].storageVar.get()
|
||||||
self.widgets[key].storageVar.trace_add("write",saveMultiNames)
|
self.widgets[widget].storageVar.trace_add("write",saveMultiNames)
|
||||||
self.widgets[key].textbox = Entry(self.widgets[key], textvariable=self.widgets[key].storageVar)
|
# textbox
|
||||||
self.widgets[key].label.pack(side=LEFT)
|
self.widgets[widget].pieces["textbox"] = Entry(self.widgets[widget].pieces["frame"], textvariable=self.widgets[widget].storageVar)
|
||||||
self.widgets[key].textbox.pack(side=LEFT, fill=X, expand=True)
|
|
||||||
self.widgets[key].pack(anchor=N, fill=X, expand=True)
|
# frame label: pack
|
||||||
|
self.widgets[widget].pieces["frame"].label.pack(side=LEFT, anchor=N)
|
||||||
|
# textbox: pack
|
||||||
|
self.widgets[widget].pieces["textbox"].pack(side=LEFT, anchor=N, fill=X, expand=True)
|
||||||
|
# frame: pack
|
||||||
|
self.widgets[widget].pieces["frame"].pack(side=LEFT, anchor=N, fill=X, expand=True)
|
||||||
|
|
||||||
return self,settings
|
return self,settings
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, RIGHT, X
|
from tkinter import Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, RIGHT, X
|
||||||
|
from source.classes.Empty import Empty
|
||||||
# Need a dummy class
|
|
||||||
class Empty():
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Override Spinbox to include mousewheel support for changing value
|
# Override Spinbox to include mousewheel support for changing value
|
||||||
class mySpinbox(Spinbox):
|
class mySpinbox(Spinbox):
|
||||||
@@ -31,27 +28,81 @@ def make_checkbox(self, parent, label, storageVar, manager, managerAttrs):
|
|||||||
|
|
||||||
# Make an OptionMenu with a label and pretty option labels
|
# Make an OptionMenu with a label and pretty option labels
|
||||||
def make_selectbox(self, parent, label, options, storageVar, manager, managerAttrs):
|
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 = Frame(parent, name="selectframe-" + label.lower())
|
||||||
self.storageVar = storageVar
|
|
||||||
self.storageVar.trace_add("write",change_selected)
|
labels = options
|
||||||
|
|
||||||
|
if isinstance(options,dict):
|
||||||
|
labels = options.keys()
|
||||||
|
|
||||||
self.labelVar = StringVar()
|
self.labelVar = StringVar()
|
||||||
|
self.storageVar = storageVar
|
||||||
|
self.selectbox = OptionMenu(self, self.labelVar, *labels)
|
||||||
|
self.selectbox.options = {}
|
||||||
|
|
||||||
|
if isinstance(options,dict):
|
||||||
|
self.selectbox.options["labels"] = list(options.keys())
|
||||||
|
self.selectbox.options["values"] = list(options.values())
|
||||||
|
else:
|
||||||
|
self.selectbox.options["labels"] = ["" for i in range(0,len(options))]
|
||||||
|
self.selectbox.options["values"] = options
|
||||||
|
|
||||||
|
def change_thing(thing, *args):
|
||||||
|
labels = self.selectbox.options["labels"]
|
||||||
|
values = self.selectbox.options["values"]
|
||||||
|
check = ""
|
||||||
|
lbl = ""
|
||||||
|
val = ""
|
||||||
|
idx = 0
|
||||||
|
|
||||||
|
if thing == "storage":
|
||||||
|
check = self.labelVar.get()
|
||||||
|
elif thing == "label":
|
||||||
|
check = self.storageVar.get()
|
||||||
|
|
||||||
|
if check in labels:
|
||||||
|
idx = labels.index(check)
|
||||||
|
if check in values:
|
||||||
|
idx = values.index(check)
|
||||||
|
|
||||||
|
lbl = labels[idx]
|
||||||
|
val = values[idx]
|
||||||
|
|
||||||
|
if thing == "storage":
|
||||||
|
self.storageVar.set(val)
|
||||||
|
elif thing == "label":
|
||||||
|
self.labelVar.set(lbl)
|
||||||
|
self.selectbox["menu"].entryconfigure(idx,label=lbl)
|
||||||
|
self.selectbox.configure(state="active")
|
||||||
|
|
||||||
|
|
||||||
|
def change_storage(*args):
|
||||||
|
change_thing("storage", *args)
|
||||||
|
def change_selected(*args):
|
||||||
|
change_thing("label", *args)
|
||||||
|
|
||||||
|
self.storageVar.trace_add("write",change_selected)
|
||||||
self.labelVar.trace_add("write",change_storage)
|
self.labelVar.trace_add("write",change_storage)
|
||||||
self.label = Label(self, text=label)
|
self.label = Label(self, text=label)
|
||||||
|
|
||||||
if managerAttrs is not None and "label" in managerAttrs:
|
if managerAttrs is not None and "label" in managerAttrs:
|
||||||
self.label.pack(managerAttrs["label"])
|
self.label.pack(managerAttrs["label"])
|
||||||
else:
|
else:
|
||||||
self.label.pack()
|
self.label.pack()
|
||||||
self.selectbox = OptionMenu(self, self.labelVar, *options.keys())
|
|
||||||
self.selectbox.config(width=20)
|
self.selectbox.config(width=20)
|
||||||
self.labelVar.set(managerAttrs["default"] if "default" in managerAttrs else list(options.keys())[0])
|
idx = 0
|
||||||
|
default = self.selectbox.options["values"][idx]
|
||||||
|
if "default" in managerAttrs:
|
||||||
|
default = managerAttrs["default"]
|
||||||
|
labels = self.selectbox.options["labels"]
|
||||||
|
values = self.selectbox.options["values"]
|
||||||
|
if default in values:
|
||||||
|
idx = values.index(default)
|
||||||
|
self.labelVar.set(labels[idx])
|
||||||
|
self.storageVar.set(values[idx])
|
||||||
|
self.selectbox["menu"].entryconfigure(idx,label=labels[idx])
|
||||||
|
|
||||||
if managerAttrs is not None and "selectbox" in managerAttrs:
|
if managerAttrs is not None and "selectbox" in managerAttrs:
|
||||||
self.selectbox.pack(managerAttrs["selectbox"])
|
self.selectbox.pack(managerAttrs["selectbox"])
|
||||||
else:
|
else:
|
||||||
@@ -144,6 +195,7 @@ def make_widget_from_dict(self, defn, parent):
|
|||||||
managerAttrs = defn["managerAttrs"] if "managerAttrs" in defn else None
|
managerAttrs = defn["managerAttrs"] if "managerAttrs" in defn else None
|
||||||
options = defn["options"] if "options" in defn else None
|
options = defn["options"] if "options" in defn else None
|
||||||
widget = make_widget(self, type, parent, label, None, manager, managerAttrs, options)
|
widget = make_widget(self, type, parent, label, None, manager, managerAttrs, options)
|
||||||
|
widget.type = type
|
||||||
return widget
|
return widget
|
||||||
|
|
||||||
# Make a set of generic widgets from a dict
|
# Make a set of generic widgets from a dict
|
||||||
|
|||||||
Reference in New Issue
Block a user