Split enemy shuffle into its components

This commit is contained in:
Fabian Dill 2020-08-19 23:24:17 +02:00
parent c2e2c2d5f9
commit cb4fa6dd33
7 changed files with 126 additions and 54 deletions

View File

@ -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()]))

View File

@ -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})

55
Gui.py
View File

@ -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())

10
Main.py
View File

@ -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)

View File

@ -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',

9
Rom.py
View File

@ -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,

View File

@ -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