diff --git a/Main.py b/Main.py index f417d0e7..9b7c6d52 100644 --- a/Main.py +++ b/Main.py @@ -77,7 +77,7 @@ def main(args, seed=None): world.triforce_pieces_required = args.triforce_pieces_required.copy() world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()} - world.rom_seeds = {player: world.random.randint(0, 999999999) for player in range(1, world.players + 1)} + world.rom_seeds = {player: random.Random(world.random.randint(0, 999999999)) for player in range(1, world.players + 1)} logger.info('ALttP Berserker\'s Multiworld Version %s - Seed: %s\n', __version__, world.seed) @@ -199,7 +199,7 @@ def main(args, seed=None): 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]) + args.ow_palettes[player], args.uw_palettes[player], world, player) mcsb_name = '' if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], diff --git a/Rom.py b/Rom.py index 6a1c74ea..030f84dd 100644 --- a/Rom.py +++ b/Rom.py @@ -236,9 +236,8 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite 'BeesLevel': 0, 'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos', 'RandomizeTileTrapFloorTile': False, - 'AllowKillableThief': bool(world.random.randint(0, 1)) if 'thieves' in world.enemy_shuffle[player] else + 'AllowKillableThief': bool(world.rom_seeds[player].randint(0, 1)) if 'thieves' in world.enemy_shuffle[player] else world.enemy_shuffle[player] != 'none', - # TODO: this is currently non-deterministic due to being called in a thread 'RandomizeSpriteOnHit': random_sprite_on_hit, 'DebugMode': False, 'DebugForceEnemy': False, @@ -276,7 +275,7 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite subprocess.check_call([os.path.abspath(enemizercli), '--rom', randopatch_path, - '--seed', str(world.rom_seeds[player]), + '--seed', str(world.rom_seeds[player].randint(0, 999999999)), '--binary', '--enemizer', options_path, '--output', enemizer_output_path], @@ -290,7 +289,7 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite if sprites: while len(sprites) < 32: sprites.extend(sprites) - world.random.shuffle(sprites) + world.rom_seeds[player].shuffle(sprites) for i, path in enumerate(sprites[:32]): sprite = Sprite(path) @@ -317,11 +316,11 @@ def _populate_sprite_table(): if sprite.valid: _sprite_table[sprite.name.lower()] = filepath -def get_sprite_from_name(name): +def get_sprite_from_name(name, local_random=random): _populate_sprite_table() name = name.lower() if name in ['random', 'randomonhit']: - return Sprite(random.choice(list(_sprite_table.values()))) + return Sprite(local_random.choice(list(_sprite_table.values()))) return Sprite(_sprite_table[name]) if name in _sprite_table else None class Sprite(object): @@ -483,7 +482,7 @@ class Sprite(object): return array_chunk(palette_as_colors, 15) def patch_rom(world, rom, player, team, enemized): - local_random = random.Random(world.rom_seeds[player]) + local_random = world.rom_seeds[player] # progressive bow silver arrow hint hack prog_bow_locs = world.find_items('Progressive Bow', player) @@ -1365,9 +1364,10 @@ def hud_format_text(text): return output[:32] -def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes): +def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes, world=None, player=1): + local_random = random if not world else world.rom_seeds[player] if sprite and not isinstance(sprite, Sprite): - sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite) + sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite, local_random) # enable instant item menu if fastmenu == 'instant': @@ -1405,7 +1405,7 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr # set heart color if color == 'random': - color = random.choice(['red', 'blue', 'green', 'yellow']) + color = local_random.choice(['red', 'blue', 'green', 'yellow']) rom.write_byte(0x6FA1E, {'red': 0x24, 'blue': 0x2C, 'green': 0x3C, 'yellow': 0x28}[color]) rom.write_byte(0x6FA20, {'red': 0x24, 'blue': 0x2C, 'green': 0x3C, 'yellow': 0x28}[color]) rom.write_byte(0x6FA22, {'red': 0x24, 'blue': 0x2C, 'green': 0x3C, 'yellow': 0x28}[color]) @@ -1424,13 +1424,13 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr default_ow_palettes(rom) if ow_palettes == 'random': - randomize_ow_palettes(rom) + randomize_ow_palettes(rom, local_random) elif ow_palettes == 'blackout': blackout_ow_palettes(rom) default_uw_palettes(rom) if uw_palettes == 'random': - randomize_uw_palettes(rom) + randomize_uw_palettes(rom, local_random) elif uw_palettes == 'blackout': blackout_uw_palettes(rom) @@ -1460,11 +1460,11 @@ def default_ow_palettes(rom): for address in [0x067FB4, 0x067F94, 0x067FC6, 0x067FE6, 0x067FE1, 0x05FEA9, 0x05FEB3]: rom.write_bytes(address, rom.orig_buffer[address:address+2]) -def randomize_ow_palettes(rom): +def randomize_ow_palettes(rom, local_random): grass, grass2, grass3, dirt, dirt2, water, clouds, dwdirt,\ - dwgrass, dwwater, dwdmdirt, dwdmgrass, dwdmclouds1, dwdmclouds2 = [[random.randint(60, 215) for _ in range(3)] for _ in range(14)] - dwtree = [c + random.randint(-20, 10) for c in dwgrass] - treeleaf = [c + random.randint(-20, 10) for c in grass] + dwgrass, dwwater, dwdmdirt, dwdmgrass, dwdmclouds1, dwdmclouds2 = [[local_random.randint(60, 215) for _ in range(3)] for _ in range(14)] + dwtree = [c + local_random.randint(-20, 10) for c in dwgrass] + treeleaf = [c + local_random.randint(-20, 10) for c in grass] patches = {0x067FB4: (grass, 0), 0x067F94: (grass, 0), 0x067FC6: (grass, 0), 0x067FE6: (grass, 0), 0x067FE1: (grass, 3), 0x05FEA9: (grass, 0), 0x05FEB3: (dwgrass, 1), 0x0DD4AC: (grass, 2), 0x0DE6DE: (grass2, 2), 0x0DE6E0: (grass2, 1), 0x0DD4AE: (grass2, 1), 0x0DE9FA: (grass2, 1), 0x0DEA0E: (grass2, 1), 0x0DE9FE: (grass2, 0), @@ -1518,9 +1518,9 @@ def default_uw_palettes(rom): return rom.write_bytes(0xDD734, rom.orig_buffer[0xDD734:0xDE544]) -def randomize_uw_palettes(rom): +def randomize_uw_palettes(rom, local_random): for dungeon in range(20): - wall, pot, chest, floor1, floor2, floor3 = [[random.randint(60, 240) for _ in range(3)] for _ in range(6)] + wall, pot, chest, floor1, floor2, floor3 = [[local_random.randint(60, 240) for _ in range(3)] for _ in range(6)] for i in range(5): shade = 10 - (i * 2) @@ -1579,6 +1579,8 @@ def write_string_to_rom(rom, target, string): def write_strings(rom, world, player, team): + local_random = world.rom_seeds[player] + tt = TextTable() tt.removeUnwantedText() @@ -1607,9 +1609,9 @@ def write_strings(rom, world, player, team): if world.hints[player]: tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!' hint_locations = HintLocations.copy() - world.random.shuffle(hint_locations) + local_random.shuffle(hint_locations) all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player] - world.random.shuffle(all_entrances) + local_random.shuffle(all_entrances) #First we take care of the one inconvenient dungeon in the appropriately simple shuffles. entrances_to_hint = {} @@ -1684,12 +1686,12 @@ def write_strings(rom, world, player, team): locations_to_hint = InconvenientLocations.copy() if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: locations_to_hint.extend(InconvenientVanillaLocations) - world.random.shuffle(locations_to_hint) + local_random.shuffle(locations_to_hint) hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5 del locations_to_hint[hint_count:] for location in locations_to_hint: if location == 'Swamp Left': - if world.random.randint(0, 1) == 0: + if local_random.randint(0, 1) == 0: first_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item) second_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item) else: @@ -1698,7 +1700,7 @@ def write_strings(rom, world, player, team): this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.') tt[hint_locations.pop(0)] = this_hint elif location == 'Mire Left': - if world.random.randint(0, 1) == 0: + if local_random.randint(0, 1) == 0: first_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item) second_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item) else: @@ -1737,12 +1739,12 @@ def write_strings(rom, world, player, team): items_to_hint.extend(SmallKeys) if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) - world.random.shuffle(items_to_hint) + local_random.shuffle(items_to_hint) hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 while hint_count > 0: this_item = items_to_hint.pop(0) this_location = world.find_items(this_item, player) - world.random.shuffle(this_location) + local_random.shuffle(this_location) #This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool. if this_location: if this_location[0].name == 'Skull Woods - Pinball Room': @@ -1754,7 +1756,7 @@ def write_strings(rom, world, player, team): # All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint isn't selected twice. junk_hints = junk_texts.copy() - world.random.shuffle(junk_hints) + local_random.shuffle(junk_hints) for location in hint_locations: tt[location] = junk_hints.pop(0) @@ -1762,7 +1764,7 @@ def write_strings(rom, world, player, team): silverarrows = world.find_items('Silver Bow', player) - world.random.shuffle(silverarrows) + local_random.shuffle(silverarrows) silverarrow_hint = (' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint @@ -1776,7 +1778,7 @@ def write_strings(rom, world, player, team): tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint if any(prog_bow_locs): - silverarrow_hint = (' %s?' % hint_text(world.random.choice(prog_bow_locs)).replace('Ganon\'s', + silverarrow_hint = (' %s?' % hint_text(local_random.choice(prog_bow_locs)).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint @@ -1798,13 +1800,13 @@ def write_strings(rom, world, player, team): if world.goal[player] in ['dungeons']: tt['sign_ganon'] = 'You need to complete all the dungeons.' - tt['uncle_leaving_text'] = Uncle_texts[world.random.randint(0, len(Uncle_texts) - 1)] - tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[world.random.randint(0, len(Triforce_texts) - 1)] - tt['bomb_shop_big_bomb'] = BombShop2_texts[world.random.randint(0, len(BombShop2_texts) - 1)] + tt['uncle_leaving_text'] = Uncle_texts[local_random.randint(0, len(Uncle_texts) - 1)] + tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[local_random.randint(0, len(Triforce_texts) - 1)] + tt['bomb_shop_big_bomb'] = BombShop2_texts[local_random.randint(0, len(BombShop2_texts) - 1)] # this is what shows after getting the green pendant item in rando - tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[world.random.randint(0, len(Sahasrahla2_texts) - 1)] - tt['blind_by_the_light'] = Blind_texts[world.random.randint(0, len(Blind_texts) - 1)] + tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[local_random.randint(0, len(Sahasrahla2_texts) - 1)] + tt['blind_by_the_light'] = Blind_texts[local_random.randint(0, len(Blind_texts) - 1)] if world.goal[player] in ['triforcehunt', 'localtriforcehunt']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' @@ -1821,7 +1823,7 @@ def write_strings(rom, world, player, team): tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!' else: - tt['ganon_fall_in'] = Ganon1_texts[world.random.randint(0, len(Ganon1_texts) - 1)] + tt['ganon_fall_in'] = Ganon1_texts[local_random.randint(0, len(Ganon1_texts) - 1)] tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!' tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!' if world.goal[player] == 'ganontriforcehunt' and world.players > 1: @@ -1829,7 +1831,7 @@ def write_strings(rom, world, player, team): elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: tt['sign_ganon'] = 'You need to find %d Triforce pieces to defeat Ganon.' % world.treasure_hunt_count[player] - tt['kakariko_tavern_fisherman'] = TavernMan_texts[world.random.randint(0, len(TavernMan_texts) - 1)] + tt['kakariko_tavern_fisherman'] = TavernMan_texts[local_random.randint(0, len(TavernMan_texts) - 1)] pedestalitem = world.get_location('Master Sword Pedestal', player).item pedestal_text = 'Some Hot Air' if pedestalitem is None else hint_text(pedestalitem, True) if pedestalitem.pedestal_hint_text is not None else 'Unknown Item' @@ -1860,38 +1862,38 @@ def write_strings(rom, world, player, team): credits = Credits() sickkiditem = world.get_location('Sick Kid', player).item - sickkiditem_text = world.random.choice( + sickkiditem_text = local_random.choice( SickKid_texts) if sickkiditem is None or sickkiditem.sickkid_credit_text is None else sickkiditem.sickkid_credit_text zoraitem = world.get_location('King Zora', player).item - zoraitem_text = world.random.choice( + zoraitem_text = local_random.choice( Zora_texts) if zoraitem is None or zoraitem.zora_credit_text is None else zoraitem.zora_credit_text magicshopitem = world.get_location('Potion Shop', player).item - magicshopitem_text = world.random.choice( + magicshopitem_text = local_random.choice( MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text fluteboyitem = world.get_location('Flute Spot', player).item - fluteboyitem_text = world.random.choice( + fluteboyitem_text = local_random.choice( FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text - credits.update_credits_line('castle', 0, world.random.choice(KingsReturn_texts)) - credits.update_credits_line('sanctuary', 0, world.random.choice(Sanctuary_texts)) + credits.update_credits_line('castle', 0, local_random.choice(KingsReturn_texts)) + credits.update_credits_line('sanctuary', 0, local_random.choice(Sanctuary_texts)) credits.update_credits_line('kakariko', 0, - world.random.choice(Kakariko_texts).format(world.random.choice(Sahasrahla_names))) - credits.update_credits_line('desert', 0, world.random.choice(DesertPalace_texts)) - credits.update_credits_line('hera', 0, world.random.choice(MountainTower_texts)) - credits.update_credits_line('house', 0, world.random.choice(LinksHouse_texts)) + local_random.choice(Kakariko_texts).format(local_random.choice(Sahasrahla_names))) + credits.update_credits_line('desert', 0, local_random.choice(DesertPalace_texts)) + credits.update_credits_line('hera', 0, local_random.choice(MountainTower_texts)) + credits.update_credits_line('house', 0, local_random.choice(LinksHouse_texts)) credits.update_credits_line('zora', 0, zoraitem_text) credits.update_credits_line('witch', 0, magicshopitem_text) - credits.update_credits_line('lumberjacks', 0, world.random.choice(Lumberjacks_texts)) + credits.update_credits_line('lumberjacks', 0, local_random.choice(Lumberjacks_texts)) credits.update_credits_line('grove', 0, fluteboyitem_text) - credits.update_credits_line('well', 0, world.random.choice(WishingWell_texts)) - credits.update_credits_line('smithy', 0, world.random.choice(Blacksmiths_texts)) + credits.update_credits_line('well', 0, local_random.choice(WishingWell_texts)) + credits.update_credits_line('smithy', 0, local_random.choice(Blacksmiths_texts)) credits.update_credits_line('kakariko2', 0, sickkiditem_text) - credits.update_credits_line('bridge', 0, world.random.choice(DeathMountain_texts)) - credits.update_credits_line('woods', 0, world.random.choice(LostWoods_texts)) + credits.update_credits_line('bridge', 0, local_random.choice(DeathMountain_texts)) + credits.update_credits_line('woods', 0, local_random.choice(LostWoods_texts)) credits.update_credits_line('pedestal', 0, pedestal_credit_text) (pointers, data) = credits.get_bytes()