diff --git a/BaseClasses.py b/BaseClasses.py index c515d9c8..e43e3363 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -817,7 +817,8 @@ class Boss(object): return self.defeat_rule(state, self.player) class Location(object): - def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, player_address=None): + def __init__(self, player: int, name: str = '', address=None, crystal=False, hint_text=None, parent=None, + player_address=None): self.name = name self.parent_region = parent self.item = None @@ -825,7 +826,7 @@ class Location(object): self.address = address self.player_address = player_address self.spot_type = 'Location' - self.hint_text = hint_text if hint_text is not None else 'Hyrule' + self.hint_text: str = hint_text if hint_text else name self.recursion_count = 0 self.staleness_count = 0 self.event = False diff --git a/Main.py b/Main.py index a3920e99..4ffbb2c5 100644 --- a/Main.py +++ b/Main.py @@ -154,65 +154,83 @@ def main(args, seed=None): rom_names = [] jsonout = {} + + 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' + or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' + or args.shufflepots[player] or sprite_random_on_hit) + + rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom, extendedmsu=args.extendedmsu[player]) + + patch_rom(world, rom, player, team, use_enemizer) + + if use_enemizer and (args.enemizercli or not args.jsonout): + patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], + sprite_random_on_hit, extendedmsu=args.extendedmsu[player]) + if not args.jsonout: + rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000, args.extendedmsu[player]) + + if args.race: + patch_race_rom(rom) + + world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash) + + apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], + args.fastmenu[player], args.disablemusic[player], args.sprite[player], + args.ow_palettes[player], args.uw_palettes[player]) + + if args.jsonout: + jsonout[f'patch_t{team}_p{player}'] = rom.patches + else: + mcsb_name = '' + if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], + world.bigkeyshuffle[player]]): + mcsb_name = '-keysanity' + elif [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], + world.bigkeyshuffle[player]].count(True) == 1: + mcsb_name = '-mapshuffle' if world.mapshuffle[player] else '-compassshuffle' if world.compassshuffle[ + player] else '-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle' + elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], + world.bigkeyshuffle[player]]): + mcsb_name = '-%s%s%s%sshuffle' % ( + 'M' if world.mapshuffle[player] else '', 'C' if world.compassshuffle[player] else '', + 'S' if world.keyshuffle[player] else '', 'B' if world.bigkeyshuffle[player] else '') + + outfilepname = f'_T{team + 1}' if world.teams > 1 else '' + if world.players > 1: + outfilepname += f'_P{player}' + if world.players > 1 or world.teams > 1: + outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][ + team] != 'Player %d' % player else '' + outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], + world.difficulty_adjustments[player], + world.mode[player], world.goal[player], + "" if world.timer[player] in [False, + 'display'] else "-" + + world.timer[ + player], + world.shuffle[player], world.algorithm, + mcsb_name, + "-retro" if world.retro[player] else "", + "-prog_" + world.progressive[player] if + world.progressive[player] in ['off', + 'random'] else "", + "-nohints" if not world.hints[ + player] else "")) if not args.outputname else '' + rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')) + return (player, team, list(rom.name)) + if not args.suppress_rom: - for team in range(world.teams): - for player in range(1, world.players + 1): - 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' - or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' - or args.shufflepots[player] or sprite_random_on_hit) - - rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom, extendedmsu=args.extendedmsu[player]) - - patch_rom(world, rom, player, team, use_enemizer) - - if use_enemizer and (args.enemizercli or not args.jsonout): - patch_enemizer(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit, extendedmsu=args.extendedmsu[player]) - if not args.jsonout: - rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000, args.extendedmsu[player]) - - if args.race: - patch_race_rom(rom) - - rom_names.append((player, team, list(rom.name))) - world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash) - - apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.ow_palettes[player], args.uw_palettes[player]) - - if args.jsonout: - jsonout[f'patch_t{team}_p{player}'] = rom.patches - else: - mcsb_name = '' - if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]): - mcsb_name = '-keysanity' - elif [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]].count(True) == 1: - mcsb_name = '-mapshuffle' if world.mapshuffle[player] else '-compassshuffle' if world.compassshuffle[player] else '-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle' - elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]): - mcsb_name = '-%s%s%s%sshuffle' % ( - 'M' if world.mapshuffle[player] else '', 'C' if world.compassshuffle[player] else '', - 'S' if world.keyshuffle[player] else '', 'B' if world.bigkeyshuffle[player] else '') - - outfilepname = f'_T{team+1}' if world.teams > 1 else '' - if world.players > 1: - outfilepname += f'_P{player}' - if world.players > 1 or world.teams > 1: - outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else '' - outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], - world.difficulty_adjustments[player], - world.mode[player], world.goal[player], - "" if world.timer[player] in [False, - 'display'] else "-" + - world.timer[ - player], - world.shuffle[player], world.algorithm, - mcsb_name, - "-retro" if world.retro[player] else "", - "-prog_" + world.progressive[player] if - world.progressive[player] in ['off', - 'random'] else "", - "-nohints" if not world.hints[player] else "")) if not args.outputname else '' - rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')) - + import concurrent.futures + futures = [] + with concurrent.futures.ThreadPoolExecutor() as pool: + for team in range(world.teams): + for player in range(1, world.players + 1): + futures.append(pool.submit(_gen_rom, team, player)) + for future in futures: + rom_name = future.result() + rom_names.append(rom_name) multidata = zlib.compress(json.dumps({"names": parsed_names, "roms": rom_names, "remote_items": [player for player in range(1, world.players + 1) if diff --git a/Regions.py b/Regions.py index 0eb47798..eb482af5 100644 --- a/Regions.py +++ b/Regions.py @@ -296,19 +296,23 @@ def create_regions(world, player): world.initialize_regions() -def create_lw_region(player, name, locations=None, exits=None): +def create_lw_region(player: int, name: str, locations=None, exits=None): return _create_region(player, name, RegionType.LightWorld, 'Light World', locations, exits) -def create_dw_region(player, name, locations=None, exits=None): + +def create_dw_region(player: int, name: str, locations=None, exits=None): return _create_region(player, name, RegionType.DarkWorld, 'Dark World', locations, exits) -def create_cave_region(player, name, hint='Hyrule', locations=None, exits=None): + +def create_cave_region(player: int, name: str, hint: str, locations=None, exits=None): return _create_region(player, name, RegionType.Cave, hint, locations, exits) -def create_dungeon_region(player, name, hint='Hyrule', locations=None, exits=None): + +def create_dungeon_region(player: int, name: str, hint: str, locations=None, exits=None): return _create_region(player, name, RegionType.Dungeon, hint, locations, exits) -def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None): + +def _create_region(player: int, name: str, type: RegionType, hint: str, locations=None, exits=None): ret = Region(name, type, hint, player) if locations is None: locations = [] @@ -322,7 +326,8 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None ret.locations.append(Location(player, location, address, crystal, hint_text, ret, player_address)) return ret -def mark_light_world_regions(world, player): + +def mark_light_world_regions(world, player: int): # cross world caves may have some sections marked as both in_light_world, and in_dark_work. # That is ok. the bunny logic will check for this case and incorporate special rules. queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld) @@ -352,7 +357,7 @@ def mark_light_world_regions(world, player): queue.append(exit.connected_region) -def create_shops(world, player): +def create_shops(world, player: int): for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in shop_table.items(): if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop': locked = True diff --git a/Rom.py b/Rom.py index 058d112f..79b350e5 100644 --- a/Rom.py +++ b/Rom.py @@ -159,11 +159,12 @@ def read_rom(stream): def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, random_sprite_on_hit, extendedmsu): baserom_path = os.path.abspath(baserom_path) - basepatch_path = os.path.abspath(local_path('data/base2current.json') if not extendedmsu else local_path('data/base2current_extendedmsu.json')) + basepatch_path = os.path.abspath( + local_path('data/base2current.json') if not extendedmsu else local_path('data/base2current_extendedmsu.json')) enemizer_basepatch_path = os.path.join(os.path.dirname(enemizercli), "enemizerBasePatch.json") - randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json')) - options_path = os.path.abspath(output_path('enemizer_options.json')) - enemizer_output_path = os.path.abspath(output_path('enemizer_output.json')) + randopatch_path = os.path.abspath(output_path(f'enemizer_randopatch_{player}.json')) + options_path = os.path.abspath(output_path(f'enemizer_options_{player}.json')) + enemizer_output_path = os.path.abspath(output_path(f'enemizer_output_{player}.json')) # write options file for enemizer options = { @@ -171,7 +172,8 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, r 'RandomizeEnemiesType': 3, '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]], + 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[ + world.enemy_health[player]], 'OHKO': False, 'RandomizeEnemyDamage': world.enemy_damage[player] != 'default', 'AllowEnemyZeroDamage': True,