diff --git a/BaseClasses.py b/BaseClasses.py index 9e3192f5..7120ec79 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -104,9 +104,12 @@ class World(object): set_player_attr('bigkeyshuffle', False) set_player_attr('difficulty_requirements', None) set_player_attr('boss_shuffle', 'none') - set_player_attr('enemy_shuffle', 'none') + set_player_attr('enemy_shuffle', False) set_player_attr('enemy_health', 'default') set_player_attr('enemy_damage', 'default') + set_player_attr('killable_thieves', False) + set_player_attr('tile_shuffle', False) + set_player_attr('bush_shuffle', False) set_player_attr('beemizer', 0) set_player_attr('escape_assist', []) set_player_attr('crystals_needed_for_ganon', 7) @@ -1231,6 +1234,9 @@ class Spoiler(object): 'enemy_shuffle': self.world.enemy_shuffle, 'enemy_health': self.world.enemy_health, 'enemy_damage': self.world.enemy_damage, + 'killable_thieves': self.world.killable_thieves, + 'tile_shuffle': self.world.tile_shuffle, + 'bush_shuffle': self.world.bush_shuffle, 'beemizer': self.world.beemizer, 'progressive': self.world.progressive, 'shufflepots': self.world.shufflepots, @@ -1261,9 +1267,14 @@ class Spoiler(object): def to_file(self, filename): self.parse_data() + + def bool_to_text(variable: bool) -> str: + return 'Yes' if variable else 'No' + with open(filename, 'w', encoding="utf-8-sig") as outfile: outfile.write( - 'ALttP Berserker\'s Multiworld Version %s - Seed: %s\n\n' % (self.metadata['version'], self.world.seed)) + 'ALttP Berserker\'s Multiworld Version %s - Seed: %s\n\n' % ( + self.metadata['version'], self.world.seed)) outfile.write('Filling Algorithm: %s\n' % self.world.algorithm) outfile.write('Players: %d\n' % self.world.players) outfile.write('Teams: %d\n' % self.world.teams) @@ -1301,15 +1312,23 @@ class Spoiler(object): 'Map shuffle: %s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No')) outfile.write('Compass shuffle: %s\n' % ( 'Yes' if self.metadata['compassshuffle'][player] else 'No')) - outfile.write('Small Key shuffle: %s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No')) - outfile.write('Big Key shuffle: %s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No')) + outfile.write( + 'Small Key shuffle: %s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No')) + outfile.write('Big Key shuffle: %s\n' % ( + 'Yes' if self.metadata['bigkeyshuffle'][player] else 'No')) outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player]) - outfile.write('Enemy shuffle: %s\n' % self.metadata['enemy_shuffle'][player]) + outfile.write( + 'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player])) outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player]) outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player]) - outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No')) + outfile.write(f'Killable thieves: {bool_to_text(self.metadata["killable_thieves"])}\n') + outfile.write(f'Shuffled tiles: {bool_to_text(self.metadata["tile_shuffle"])}\n') + outfile.write(f'Shuffled bushes: {bool_to_text(self.metadata["bush_shuffle"])}\n') + outfile.write( + 'Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No')) outfile.write('Beemizer: %s\n' % self.metadata['beemizer'][player]) - outfile.write('Pot shuffle %s\n' % ('Yes' if self.metadata['shufflepots'][player] else 'No')) + outfile.write( + 'Pot shuffle %s\n' % ('Yes' if self.metadata['shufflepots'][player] else 'No')) if self.entrances: 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()])) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 263d9c23..dc4e5132 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -293,8 +293,10 @@ def parse_arguments(argv, no_defaults=False): parser.add_argument('--enemizercli', default=defval('EnemizerCLI/EnemizerCLI.Core')) parser.add_argument('--shufflebosses', default=defval('none'), choices=['none', 'basic', 'normal', 'chaos', "singularity"]) - parser.add_argument('--shuffleenemies', default=defval('none'), - choices=['none', 'shuffled', 'chaos', 'chaosthieves']) + parser.add_argument('--enemy_shuffle', action='store_true') + parser.add_argument('--killable_thieves', action='store_true') + parser.add_argument('--tile_shuffle', action='store_true') + parser.add_argument('--bush_shuffle', action='store_true') parser.add_argument('--enemy_health', default=defval('default'), choices=['default', 'easy', 'normal', 'hard', 'expert']) parser.add_argument('--enemy_damage', default=defval('default'), choices=['default', 'shuffled', 'chaos']) @@ -336,10 +338,12 @@ def parse_arguments(argv, no_defaults=False): 'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'timer', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'local_items', 'retro', 'accessibility', 'hints', 'beemizer', - 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', + 'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', - 'heartbeep', "skip_progression_balancing", "triforce_pieces_available", "triforce_pieces_required", - 'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots']: + 'heartbeep', "skip_progression_balancing", "triforce_pieces_available", + "triforce_pieces_required", + 'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves', + 'tile_shuffle', 'bush_shuffle']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) diff --git a/Gui.py b/Gui.py index 02051760..afc7ff23 100755 --- a/Gui.py +++ b/Gui.py @@ -352,63 +352,75 @@ def guiMain(args=None): enemizerPathFrame = Frame(enemizerFrame) - enemizerPathFrame.grid(row=0, column=0, columnspan=3, sticky=W+E, padx=3) + enemizerPathFrame.grid(row=0, column=0, columnspan=4, sticky=W + E, padx=3) enemizerCLIlabel = Label(enemizerPathFrame, text="EnemizerCLI path: ") enemizerCLIlabel.pack(side=LEFT) enemizerCLIpathVar = StringVar(value="EnemizerCLI/EnemizerCLI.Core") enemizerCLIpathEntry = Entry(enemizerPathFrame, textvariable=enemizerCLIpathVar) enemizerCLIpathEntry.pack(side=LEFT, expand=True, fill=X) + def EnemizerSelectPath(): path = filedialog.askopenfilename(filetypes=[("EnemizerCLI executable", "*EnemizerCLI*")]) if path: enemizerCLIpathVar.set(path) + enemizerCLIbrowseButton = Button(enemizerPathFrame, text='...', command=EnemizerSelectPath) enemizerCLIbrowseButton.pack(side=LEFT) - potShuffleVar = IntVar() - potShuffleButton = Checkbutton(enemizerFrame, text="Pot shuffle", variable=potShuffleVar) - potShuffleButton.grid(row=0, column=3) - enemizerEnemyFrame = Frame(enemizerFrame) enemizerEnemyFrame.grid(row=1, column=0, pady=5) - enemizerEnemyLabel = Label(enemizerEnemyFrame, text='Enemy shuffle') - enemizerEnemyLabel.pack(side=LEFT) - enemyShuffleVar = StringVar() - enemyShuffleVar.set('none') - enemizerEnemyOption = OptionMenu(enemizerEnemyFrame, enemyShuffleVar, 'none', 'shuffled', 'chaos', 'chaosthieves') - enemizerEnemyOption.pack(side=LEFT) + + enemyShuffleVar = IntVar() + enemizerEnemyButton = Checkbutton(enemizerEnemyFrame, text="Enemy shuffle", variable=enemyShuffleVar) + enemizerEnemyButton.pack(side=LEFT) enemizerBossFrame = Frame(enemizerFrame) enemizerBossFrame.grid(row=1, column=1) - enemizerBossLabel = Label(enemizerBossFrame, text='Boss shuffle') - enemizerBossLabel.pack(side=LEFT) enemizerBossVar = StringVar() enemizerBossVar.set('none') enemizerBossOption = OptionMenu(enemizerBossFrame, enemizerBossVar, 'none', 'basic', 'normal', 'chaos', "singularity") enemizerBossOption.pack(side=LEFT) + enemizerBossLabel = Label(enemizerBossFrame, text='Boss shuffle') + enemizerBossLabel.pack(side=LEFT) enemizerDamageFrame = Frame(enemizerFrame) enemizerDamageFrame.grid(row=1, column=2) - enemizerDamageLabel = Label(enemizerDamageFrame, text='Enemy damage') - enemizerDamageLabel.pack(side=LEFT) enemizerDamageVar = StringVar() enemizerDamageVar.set('default') enemizerDamageOption = OptionMenu(enemizerDamageFrame, enemizerDamageVar, 'default', 'shuffled', 'chaos') enemizerDamageOption.pack(side=LEFT) + enemizerDamageLabel = Label(enemizerDamageFrame, text='Enemy damage') + enemizerDamageLabel.pack(side=LEFT) enemizerHealthFrame = Frame(enemizerFrame) enemizerHealthFrame.grid(row=1, column=3) - enemizerHealthLabel = Label(enemizerHealthFrame, text='Enemy health') - enemizerHealthLabel.pack(side=LEFT) enemizerHealthVar = StringVar() enemizerHealthVar.set('default') - enemizerHealthOption = OptionMenu(enemizerHealthFrame, enemizerHealthVar, 'default', 'easy', 'normal', 'hard', 'expert') + enemizerHealthOption = OptionMenu(enemizerHealthFrame, enemizerHealthVar, 'default', 'easy', 'normal', 'hard', + 'expert') enemizerHealthOption.pack(side=LEFT) + enemizerHealthLabel = Label(enemizerHealthFrame, text='Enemy health') + enemizerHealthLabel.pack(side=LEFT) + + potShuffleVar = IntVar() + potShuffleButton = Checkbutton(enemizerFrame, text="Pot shuffle", variable=potShuffleVar) + potShuffleButton.grid(row=2, column=0, sticky=W) + + tileShuffleVar = IntVar() + tileShuffleButton = Checkbutton(enemizerFrame, text="Tile shuffle", variable=tileShuffleVar) + tileShuffleButton.grid(row=2, column=1, sticky=W) + + bushShuffleVar = IntVar() + bushShuffleButton = Checkbutton(enemizerFrame, text="Bush shuffle", variable=bushShuffleVar) + bushShuffleButton.grid(row=2, column=2, sticky=W) + + killableThievesVar = IntVar() + killable_thievesShuffleButton = Checkbutton(enemizerFrame, text="Killable Thieves", variable=killableThievesVar) + killable_thievesShuffleButton.grid(row=2, column=3, sticky=W) multiworldframe = LabelFrame(randomizerWindow, text="Multiworld", padx=5, pady=2) - worldLabel = Label(multiworldframe, text='Worlds') worldVar = StringVar() worldSpinbox = Spinbox(multiworldframe, from_=1, to=255, width=5, textvariable=worldVar) @@ -470,7 +482,10 @@ def guiMain(args=None): guiargs.hints = bool(hintsVar.get()) guiargs.enemizercli = enemizerCLIpathVar.get() guiargs.shufflebosses = enemizerBossVar.get() - guiargs.shuffleenemies = enemyShuffleVar.get() + guiargs.enemy_shuffle = enemyShuffleVar.get() + guiargs.bush_shuffle = bushShuffleVar.get() + guiargs.tile_shuffle = tileShuffleVar.get() + guiargs.killable_thieves = killableThievesVar.get() guiargs.enemy_health = enemizerHealthVar.get() guiargs.enemy_damage = enemizerDamageVar.get() guiargs.shufflepots = bool(potShuffleVar.get()) diff --git a/Main.py b/Main.py index 42e5af51..4837fd18 100644 --- a/Main.py +++ b/Main.py @@ -64,9 +64,12 @@ def main(args, seed=None): for player in range(1, world.players + 1)} world.open_pyramid = args.openpyramid.copy() world.boss_shuffle = args.shufflebosses.copy() - world.enemy_shuffle = args.shuffleenemies.copy() + world.enemy_shuffle = args.enemy_shuffle.copy() world.enemy_health = args.enemy_health.copy() world.enemy_damage = args.enemy_damage.copy() + world.killable_thieves = args.killable_thieves.copy() + world.bush_shuffle = args.bush_shuffle.copy() + world.tile_shuffle = args.tile_shuffle.copy() world.beemizer = args.beemizer.copy() world.timer = args.timer.copy() world.shufflepots = args.shufflepots.copy() @@ -180,9 +183,10 @@ def main(args, seed=None): def _gen_rom(team: int, player: int): sprite_random_on_hit = type(args.sprite[player]) is str and args.sprite[player].lower() == 'randomonhit' - use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none' + use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' - or args.shufflepots[player] or sprite_random_on_hit) + or world.shufflepots[player] or sprite_random_on_hit or world.bush_shuffle[player] + or world.killable_thieves[player] or world.tile_shuffle[player]) rom = LocalRom(args.rom) diff --git a/Mystery.py b/Mystery.py index ca33c987..e0ed3bef 100644 --- a/Mystery.py +++ b/Mystery.py @@ -329,17 +329,40 @@ def roll_settings(weights): ret.shufflebosses = {'none': 'none', 'simple': 'basic', 'full': 'normal', - 'random': 'chaos', + 'random': 'random', 'singularity': 'singularity', 'duality': 'singularity' }[get_choice('boss_shuffle', weights)] - ret.shuffleenemies = {'none': 'none', - 'shuffled': 'shuffled', - 'random': 'chaos', - 'chaosthieves': 'chaosthieves', - 'chaos': 'chaos' - }[get_choice('enemy_shuffle', weights)] + ret.enemy_shuffle = {'none': False, + 'shuffled': 'shuffled', + 'random': 'chaos', + 'chaosthieves': 'chaosthieves', + 'chaos': 'chaos', + True: True, + False: False, + None: False + }[get_choice('enemy_shuffle', weights, False)] + + ret.killable_thieves = get_choice('killable_thieves', weights, False) + ret.tile_shuffle = get_choice('tile_shuffle', weights, False) + ret.bush_shuffle = get_choice('bush_shuffle', weights, False) + + # legacy support for enemy shuffle + if type(ret.enemy_shuffle) == str: + if ret.enemy_shuffle == 'shuffled': + ret.killable_thieves = True + elif ret.enemy_shuffle == 'chaos': + ret.killable_thieves = True + ret.bush_shuffle = True + ret.tile_shuffle = True + elif ret.enemy_shuffle == "chaosthieves": + ret.killable_thieves = False + ret.bush_shuffle = True + ret.tile_shuffle = True + ret.enemy_shuffle = True + logging.info(ret.enemy_shuffle) + # end of legacy block ret.enemy_damage = {'default': 'default', 'shuffled': 'shuffled', diff --git a/Rom.py b/Rom.py index 8ac124b1..47ce4a56 100644 --- a/Rom.py +++ b/Rom.py @@ -196,9 +196,9 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite # write options file for enemizer options = { - 'RandomizeEnemies': world.enemy_shuffle[player] != 'none', + 'RandomizeEnemies': world.enemy_shuffle[player], 'RandomizeEnemiesType': 3, - 'RandomizeBushEnemyChance': 'chaos' in world.enemy_shuffle[player], + 'RandomizeBushEnemyChance': world.bush_shuffle[player], 'RandomizeEnemyHealthRange': world.enemy_health[player] != 'default', 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[ world.enemy_health[player]], @@ -247,10 +247,9 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite 'SwordGraphics': "sword_gfx/normal.gfx", 'BeeMizer': False, 'BeesLevel': 0, - 'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos', + 'RandomizeTileTrapPattern': world.tile_shuffle[player], 'RandomizeTileTrapFloorTile': False, - 'AllowKillableThief': bool(world.rom_seeds[player].randint(0, 1)) if 'thieves' in world.enemy_shuffle[player] else - world.enemy_shuffle[player] != 'none', + 'AllowKillableThief': world.killable_thieves[player], 'RandomizeSpriteOnHit': random_sprite_on_hit, 'DebugMode': False, 'DebugForceEnemy': False, diff --git a/easy.yaml b/easy.yaml index 8f1cdbd4..417b8497 100644 --- a/easy.yaml +++ b/easy.yaml @@ -145,18 +145,25 @@ item_functionality: progression_balancing: on: 1 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch around missing items. +### Enemizer Section ### boss_shuffle: none: 1 # Vanilla bosses simple: 0 # Existing bosses except Ganon and Agahnim are shuffled throughout dungeons full: 0 # 3 bosses can occur twice random: 0 # Any boss can appear any amount of times - singularity: 0 # Picks any boss that can appear anywhere and puts that boss into every arena - duality: 0 # Picks a boss that can only appear in some places and a boss that can appear anywhere, then attempts to put both in that order in every arena -enemy_shuffle: - none: 1 # Vanilla enemy placement - chaos: 0 # Enemies are randomized - random: 0 # Also shuffle bush enemies, random tile rooms, and random bush enemy spawn chance - chaosthieves: 0 # Random + thieves may not be killable + singularity: 0 # Picks a boss, tries to put it everywhere that works, if there's spaces remaining it picks a boss to fill those +enemy_shuffle: # randomize enemy placement + on: 0 + off: 1 +killable_thieves: # make thieves killable. + on: 0 # usually turned on together with enemy_shuffle to make annoying thief placement more manageable + off: 1 +tile_shuffle: # randomize the tile layouts in flying tile rooms + on: 0 + off: 1 +bush_shuffle: # randomize the chance that bushes have enemies and the enemies under said bush + on: 0 + off: 1 enemy_damage: default: 1 # Vanilla enemy damage shuffled: 0 # Enemies deal 0 to 4 hearts and armor helps @@ -169,6 +176,7 @@ enemy_health: pot_shuffle: 'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile 'off': 1 # Default pot item locations +### End of Enemizer Section ### beemizer: # Remove items from the global item pool and replace them with single bees and bee traps 0: 1 # No bee traps are placed 1: 0 # 25% of the non-essential item pool is replaced with bee traps