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