diff --git a/BaseClasses.py b/BaseClasses.py index 4658a3a7..c66035a3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -8,7 +8,7 @@ from Utils import int16_as_bytes class World(object): - def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, retro, custom, customitemarray, boss_shuffle, hints): + def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, retro, custom, customitemarray, hints): self.players = players self.shuffle = shuffle.copy() self.logic = logic.copy() @@ -73,7 +73,10 @@ class World(object): self.can_take_damage = True self.difficulty_requirements = {player: None for player in range(1, players + 1)} self.fix_fake_world = True - self.boss_shuffle = boss_shuffle + self.boss_shuffle = {player: 'none' for player in range(1, players + 1)} + self.enemy_shuffle = {player: 'none' for player in range(1, players + 1)} + self.enemy_health = {player: 'default' for player in range(1, players + 1)} + self.enemy_damage = {player: 'default' for player in range(1, players + 1)} self.escape_assist = {player: [] for player in range(1, players + 1)} self.hints = hints.copy() self.crystals_needed_for_ganon = {} @@ -1059,6 +1062,10 @@ class Spoiler(object): 'compassshuffle': self.world.compassshuffle, 'keyshuffle': self.world.keyshuffle, 'bigkeyshuffle': self.world.bigkeyshuffle, + 'boss_shuffle': self.world.boss_shuffle, + 'enemy_shuffle': self.world.enemy_shuffle, + 'enemy_health': self.world.enemy_health, + 'enemy_damage': self.world.enemy_damage, 'players': self.world.players } @@ -1072,8 +1079,7 @@ class Spoiler(object): out['Shops'] = self.shops out['playthrough'] = self.playthrough out['paths'] = self.paths - if self.world.boss_shuffle != 'none': - out['Bosses'] = self.bosses + out['Bosses'] = self.bosses out['meta'] = self.metadata return json.dumps(out) @@ -1101,6 +1107,10 @@ class Spoiler(object): outfile.write('Compass shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['compassshuffle'].items()}) outfile.write('Small Key shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['keyshuffle'].items()}) outfile.write('Big Key shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['bigkeyshuffle'].items()}) + outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle']) + outfile.write('Enemy shuffle: %s\n' % self.metadata['enemy_shuffle']) + outfile.write('Enemy health: %s\n' % self.metadata['enemy_health']) + outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage']) outfile.write('Hints: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['hints'].items()}) outfile.write('Players: %d' % self.world.players) if self.entrances: diff --git a/Bosses.py b/Bosses.py index ff9d0be4..8d5f6a64 100644 --- a/Bosses.py +++ b/Bosses.py @@ -138,7 +138,7 @@ def can_place_boss(world, player, boss, dungeon_name, level=None): return True def place_bosses(world, player): - if world.boss_shuffle == 'none': + if world.boss_shuffle[player] == 'none': return # Most to least restrictive order if world.mode[player] != 'inverted': @@ -177,7 +177,7 @@ def place_bosses(world, player): all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']] - if world.boss_shuffle in ["basic", "normal"]: + if world.boss_shuffle[player] in ["basic", "normal"]: # temporary hack for swordless kholdstare: if world.swords[player] == 'swordless': world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player) @@ -185,7 +185,7 @@ def place_bosses(world, player): boss_locations.remove(['Ice Palace', None]) placeable_bosses.remove('Kholdstare') - if world.boss_shuffle == "basic": # vanilla bosses shuffled + if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm'] else: # all bosses present, the three duplicates chosen at random bosses = all_bosses + [random.choice(placeable_bosses) for _ in range(3)] @@ -202,7 +202,7 @@ def place_bosses(world, player): logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) - elif world.boss_shuffle == "chaos": #all bosses chosen at random + elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random for [loc, level] in boss_locations: loc_text = loc + (' ('+level+')' if level else '') try: diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 5840be89..f7b6ec22 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -281,7 +281,8 @@ def parse_arguments(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', - 'retro', 'accessibility', 'hints']: + 'retro', 'accessibility', 'hints', + 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage']: 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/Main.py b/Main.py index 6808577e..c512bed4 100644 --- a/Main.py +++ b/Main.py @@ -25,7 +25,7 @@ def main(args, seed=None): start = time.process_time() # initialize the world - world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints) + world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.retro, args.custom, args.customitemarray, args.hints) logger = logging.getLogger('') if seed is None: random.seed(None) @@ -41,6 +41,10 @@ def main(args, seed=None): world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) 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_health = args.enemy_health.copy() + world.enemy_damage = args.enemy_damage.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} @@ -49,7 +53,7 @@ def main(args, seed=None): for player in range(1, world.players + 1): world.difficulty_requirements[player] = difficulties[world.difficulty[player]] - if world.mode[player] == 'standard' and (args.shuffleenemies != 'none' or args.enemy_health not in ['default', 'easy']): + if world.mode[player] == 'standard' and (world.enemy_shuffle[player] != 'none' or world.enemy_health[player] not in ['default', 'easy']): world.escape_assist[player].append(['bombs']) # enemized escape assumes infinite bombs available and will likely be unbeatable without it if world.mode[player] != 'inverted': @@ -128,8 +132,6 @@ def main(args, seed=None): player_names = parse_names_string(args.names) outfilebase = 'ER_%s' % (args.outputname if args.outputname else world.seed) - use_enemizer = args.enemizercli and (args.shufflebosses != 'none' or args.shuffleenemies != 'none' or args.enemy_health != 'default' or args.enemy_health != 'default' or args.enemy_damage or args.shufflepalette or args.shufflepots) - jsonout = {} if not args.suppress_rom: from MultiServer import MultiWorld @@ -137,6 +139,9 @@ def main(args, seed=None): multidata.players = world.players for player in range(1, world.players + 1): + use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none' + or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' + or args.shufflepalette or args.shufflepots) local_rom = None if args.jsonout: @@ -151,7 +156,7 @@ def main(args, seed=None): enemizer_patch = [] if use_enemizer: - enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shuffleenemies, args.enemy_health, args.enemy_damage, args.shufflepalette, args.shufflepots) + enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shufflepalette, args.shufflepots) multidata.rom_names[player] = list(rom.name) for location in world.get_filled_locations(player): @@ -218,7 +223,7 @@ def main(args, seed=None): def copy_world(world): # ToDo: Not good yet - ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints) + ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.retro, world.custom, world.customitemarray, world.hints) ret.required_medallions = world.required_medallions.copy() ret.swamp_patch_required = world.swamp_patch_required.copy() ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() @@ -245,6 +250,10 @@ def copy_world(world): ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.open_pyramid = world.open_pyramid.copy() + ret.boss_shuffle = world.boss_shuffle.copy() + ret.enemy_shuffle = world.enemy_shuffle.copy() + ret.enemy_health = world.enemy_health.copy() + ret.enemy_damage = world.enemy_damage.copy() for player in range(1, world.players + 1): if world.mode[player] != 'inverted': diff --git a/Plando.py b/Plando.py index 0a88cf4a..3011a023 100755 --- a/Plando.py +++ b/Plando.py @@ -23,7 +23,7 @@ def main(args): start_time = time.process_time() # initialize the world - world = World(1, 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None, 'none', False) + world = World(1, 'vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None, False) logger = logging.getLogger('') hasher = hashlib.md5() diff --git a/Rom.py b/Rom.py index 99362f96..5b911a18 100644 --- a/Rom.py +++ b/Rom.py @@ -161,7 +161,7 @@ def read_rom(stream): buffer = buffer[0x200:] return buffer -def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shuffleenemies, enemy_health, enemy_damage, shufflepalette, shufflepots): +def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepalette, shufflepots): baserom_path = os.path.abspath(baserom_path) basepatch_path = os.path.abspath(local_path('data/base2current.json')) randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json')) @@ -170,16 +170,16 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shuffleene # write options file for enemizer options = { - 'RandomizeEnemies': shuffleenemies != 'none', + 'RandomizeEnemies': world.enemy_shuffle[player] != 'none', 'RandomizeEnemiesType': 3, - 'RandomizeBushEnemyChance': shuffleenemies == 'chaos', - 'RandomizeEnemyHealthRange': enemy_health != 'default', - 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[enemy_health], + 'RandomizeBushEnemyChance': world.enemy_shuffle[player] == 'chaos', + 'RandomizeEnemyHealthRange': world.enemy_health[player] != 'default', + 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[world.enemy_health[player]], 'OHKO': False, - 'RandomizeEnemyDamage': enemy_damage != 'default', + 'RandomizeEnemyDamage': world.enemy_damage[player] != 'default', 'AllowEnemyZeroDamage': True, - 'ShuffleEnemyDamageGroups': enemy_damage != 'default', - 'EnemyDamageChaosMode': enemy_damage == 'chaos', + 'ShuffleEnemyDamageGroups': world.enemy_damage[player] != 'default', + 'EnemyDamageChaosMode': world.enemy_damage[player] == 'chaos', 'EasyModeEscape': False, 'EnemiesAbsorbable': False, 'AbsorbableSpawnRate': 10, @@ -218,9 +218,9 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shuffleene 'SwordGraphics': "sword_gfx/normal.gfx", 'BeeMizer': False, 'BeesLevel': 0, - 'RandomizeTileTrapPattern': shuffleenemies == 'chaos', + 'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos', 'RandomizeTileTrapFloorTile': False, - 'AllowKillableThief': bool(random.randint(0,1)) if shuffleenemies == 'chaos' else shuffleenemies != 'none', + 'AllowKillableThief': bool(random.randint(0,1)) if world.enemy_shuffle[player] == 'chaos' else world.enemy_shuffle[player] != 'none', 'RandomizeSpriteOnHit': False, 'DebugMode': False, 'DebugForceEnemy': False,