From 1315eb55cf2933c153f3602fb659f13877c64088 Mon Sep 17 00:00:00 2001 From: Bonta-kun <40473493+Bonta0@users.noreply.github.com> Date: Mon, 16 Dec 2019 21:46:47 +0100 Subject: [PATCH] Individual settings: map/compass/key/bk shuffle --- BaseClasses.py | 26 +++++++++++++------------- Dungeons.py | 12 ++++++------ EntranceRandomizer.py | 4 +++- Fill.py | 7 +++---- ItemList.py | 8 ++++---- Main.py | 39 +++++++++++++++++++++------------------ Rom.py | 34 +++++++++++++++++----------------- Rules.py | 4 ++-- 8 files changed, 69 insertions(+), 65 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index d021d18f..d841da36 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -63,10 +63,10 @@ class World(object): self.quickswap = quickswap self.fastmenu = fastmenu self.disable_music = disable_music - self.mapshuffle = False - self.compassshuffle = False - self.keyshuffle = False - self.bigkeyshuffle = False + self.mapshuffle = {player: False for player in range(1, players + 1)} + self.compassshuffle = {player: False for player in range(1, players + 1)} + self.keyshuffle = {player: False for player in range(1, players + 1)} + self.bigkeyshuffle = {player: False for player in range(1, players + 1)} self.retro = retro self.custom = custom self.customitemarray = customitemarray @@ -364,7 +364,7 @@ class CollectionState(object): checked_locations = 0 while new_locations: reachable_events = [location for location in locations if location.event and - (not key_only or (not self.world.keyshuffle and location.item.smallkey) or (not self.world.bigkeyshuffle and location.item.bigkey)) + (not key_only or (not self.world.keyshuffle[location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey)) and location.can_reach(self)] for event in reachable_events: if (event.name, event.player) not in self.events: @@ -685,10 +685,10 @@ class Region(object): return False def can_fill(self, item): - inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle) - or (item.bigkey and not self.world.bigkeyshuffle) - or (item.map and not self.world.mapshuffle) - or (item.compass and not self.world.compassshuffle)) + inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle[item.player]) + or (item.bigkey and not self.world.bigkeyshuffle[item.player]) + or (item.map and not self.world.mapshuffle[item.player]) + or (item.compass and not self.world.compassshuffle[item.player])) sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)' if sewer_hack or inside_dungeon_item: return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player @@ -1095,10 +1095,10 @@ class Spoiler(object): outfile.write('Accessibility: %s\n' % self.metadata['accessibility']) outfile.write('L\\R Quickswap enabled: %s\n' % ('Yes' if self.world.quickswap else 'No')) outfile.write('Menu speed: %s\n' % self.world.fastmenu) - outfile.write('Map shuffle: %s\n' % ('Yes' if self.metadata['mapshuffle'] else 'No')) - outfile.write('Compass shuffle: %s\n' % ('Yes' if self.metadata['compassshuffle'] else 'No')) - outfile.write('Small Key shuffle: %s\n' % ('Yes' if self.metadata['keyshuffle'] else 'No')) - outfile.write('Big Key shuffle: %s\n' % ('Yes' if self.metadata['bigkeyshuffle'] else 'No')) + outfile.write('Map shuffle: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['mapshuffle'].items()}) + 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('Players: %d' % self.world.players) if self.entrances: outfile.write('\n\nEntrances:\n\n') diff --git a/Dungeons.py b/Dungeons.py index a176af13..7237af00 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -136,16 +136,16 @@ def fill_dungeons_restrictive(world, shuffled_locations): # with shuffled dungeon items they are distributed as part of the normal item pool for item in world.get_items(): - if (item.smallkey and world.keyshuffle) or (item.bigkey and world.bigkeyshuffle): + if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]): all_state_base.collect(item, True) item.advancement = True - elif (item.map and world.mapshuffle) or (item.compass and world.compassshuffle): + elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]): item.priority = True - dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle) - or (item.bigkey and not world.bigkeyshuffle) - or (item.map and not world.mapshuffle) - or (item.compass and not world.compassshuffle))] + dungeon_items = [item for item in get_dungeon_item_pool(world) if ((item.smallkey and not world.keyshuffle[item.player]) + or (item.bigkey and not world.bigkeyshuffle[item.player]) + or (item.map and not world.mapshuffle[item.player]) + or (item.compass and not world.compassshuffle[item.player]))] # sort in the order Big Key, Small Key, Other before placing dungeon items sort_order = {"BigKey": 3, "SmallKey": 2} diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index e97ddb59..3782a001 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -278,7 +278,9 @@ def parse_arguments(argv, no_defaults=False): for player in range(1, multiargs.multi + 1): playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True) - for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid']: + for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', + 'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', + 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle']: 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/Fill.py b/Fill.py index dbb5fd78..1ee3d859 100644 --- a/Fill.py +++ b/Fill.py @@ -243,8 +243,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None fill_locations.reverse() # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots - if world.keyshuffle: - progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.mode[item.player] == 'standard' else 0) + progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.mode[item.player] == 'standard' and world.keyshuffle[item.player] else 0) fill_restrictive(world, world.state, fill_locations, progitempool) @@ -355,7 +354,7 @@ def balance_multiworld_progression(world): candidate_items = [] while True: for location in balancing_sphere: - if location.event and (world.keyshuffle or not location.item.smallkey) and (world.bigkeyshuffle or not location.item.bigkey): + if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey): balancing_state.collect(location.item, True, location) if location.item.player in balancing_players and not location.locked: candidate_items.append(location) @@ -411,7 +410,7 @@ def balance_multiworld_progression(world): sphere_locations.append(location) for location in sphere_locations: - if location.event and (world.keyshuffle or not location.item.smallkey) and (world.bigkeyshuffle or not location.item.bigkey): + if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey): state.collect(location.item, True, location) checked_locations.extend(sphere_locations) diff --git a/ItemList.py b/ItemList.py index 2ad1fa16..69114363 100644 --- a/ItemList.py +++ b/ItemList.py @@ -219,10 +219,10 @@ def generate_itempool(world, player): world.treasure_hunt_icon = treasure_hunt_icon world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player - and ((item.smallkey and world.keyshuffle) - or (item.bigkey and world.bigkeyshuffle) - or (item.map and world.mapshuffle) - or (item.compass and world.compassshuffle))]) + and ((item.smallkey and world.keyshuffle[player]) + or (item.bigkey and world.bigkeyshuffle[player]) + or (item.map and world.mapshuffle[player]) + or (item.compass and world.compassshuffle[player]))]) # logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # rather than making all hearts/heart pieces progression items (which slows down generation considerably) diff --git a/Main.py b/Main.py index f54ca5f3..1f45700f 100644 --- a/Main.py +++ b/Main.py @@ -34,19 +34,10 @@ def main(args, seed=None): world.seed = int(seed) random.seed(world.seed) - world.mapshuffle = args.mapshuffle - world.compassshuffle = args.compassshuffle - world.keyshuffle = args.keyshuffle - world.bigkeyshuffle = args.bigkeyshuffle - - mcsb_name = '' - if all([world.mapshuffle, world.compassshuffle, world.keyshuffle, world.bigkeyshuffle]): - mcsb_name = '-keysanity' - elif [world.mapshuffle, world.compassshuffle, world.keyshuffle, world.bigkeyshuffle].count(True) == 1: - mcsb_name = '-mapshuffle' if world.mapshuffle else '-compassshuffle' if world.compassshuffle else '-keyshuffle' if world.keyshuffle else '-bigkeyshuffle' - elif any([world.mapshuffle, world.compassshuffle, world.keyshuffle, world.bigkeyshuffle]): - mcsb_name = '-%s%s%s%sshuffle' % ('M' if world.mapshuffle else '', 'C' if world.compassshuffle else '', 'S' if world.keyshuffle else '', 'B' if world.bigkeyshuffle else '') - + world.mapshuffle = args.mapshuffle.copy() + world.compassshuffle = args.compassshuffle.copy() + world.keyshuffle = args.keyshuffle.copy() + world.bigkeyshuffle = args.bigkeyshuffle.copy() 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() @@ -94,7 +85,8 @@ def main(args, seed=None): logger.info('Placing Dungeon Items.') shuffled_locations = None - if args.algorithm in ['balanced', 'vt26'] or args.mapshuffle or args.compassshuffle or args.keyshuffle or args.bigkeyshuffle: + if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) + + list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())): shuffled_locations = world.get_unfilled_locations() random.shuffle(shuffled_locations) fill_dungeons_restrictive(world, shuffled_locations) @@ -182,6 +174,17 @@ def main(args, seed=None): rom.write_bytes(int(addr), values) apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite, player_names) + + 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 '') + outfilesuffix = ('%s%s_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (f'_P{player}' if world.players > 1 else '', f'_{player_names[player]}' if player in player_names else '', world.logic[player], world.difficulty[player], world.difficulty_adjustments[player], @@ -235,10 +238,10 @@ def copy_world(world): ret.difficulty_requirements = world.difficulty_requirements.copy() ret.fix_fake_world = world.fix_fake_world ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms - ret.mapshuffle = world.mapshuffle - ret.compassshuffle = world.compassshuffle - ret.keyshuffle = world.keyshuffle - ret.bigkeyshuffle = world.bigkeyshuffle + ret.mapshuffle = world.mapshuffle.copy() + ret.compassshuffle = world.compassshuffle.copy() + ret.keyshuffle = world.keyshuffle.copy() + ret.bigkeyshuffle = world.bigkeyshuffle.copy() 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() diff --git a/Rom.py b/Rom.py index e8918aa6..12be1b7c 100644 --- a/Rom.py +++ b/Rom.py @@ -490,14 +490,14 @@ def patch_rom(world, player, rom, enemized): # patch music music_addresses = dungeon_music_addresses[location.name] - if world.mapshuffle: + if world.mapshuffle[player]: music = random.choice([0x11, 0x16]) else: music = 0x11 if 'Pendant' in location.item.name else 0x16 for music_address in music_addresses: rom.write_byte(music_address, music) - if world.mapshuffle: + if world.mapshuffle[player]: rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle # patch entrance/exits/holes @@ -839,7 +839,7 @@ def patch_rom(world, player, rom, enemized): ERtimeincrease = 10 else: ERtimeincrease = 20 - if world.keyshuffle or world.bigkeyshuffle or world.mapshuffle: + if world.keyshuffle[player] or world.bigkeyshuffle[player] or world.mapshuffle[player]: ERtimeincrease = ERtimeincrease + 15 if world.clock_mode == 'off': rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode @@ -956,24 +956,24 @@ def patch_rom(world, player, rom, enemized): rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" else 0x00) # block HC upstairs doors in rain state in standard mode - rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle else 0x00) - | (0x02 if world.compassshuffle else 0x00) - | (0x04 if world.mapshuffle else 0x00) - | (0x08 if world.bigkeyshuffle else 0x00))) # free roaming item text boxes - rom.write_byte(0x18003B, 0x01 if world.mapshuffle else 0x00) # maps showing crystals on overworld + rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle[player] else 0x00) + | (0x02 if world.compassshuffle[player] else 0x00) + | (0x04 if world.mapshuffle[player] else 0x00) + | (0x08 if world.bigkeyshuffle[player] else 0x00))) # free roaming item text boxes + rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld # compasses showing dungeon count if world.clock_mode != 'off': rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location - elif world.compassshuffle: + elif world.compassshuffle[player]: rom.write_byte(0x18003C, 0x01) # show on pickup else: rom.write_byte(0x18003C, 0x00) - rom.write_byte(0x180045, ((0x01 if world.keyshuffle else 0x00) - | (0x02 if world.bigkeyshuffle else 0x00) - | (0x04 if world.compassshuffle else 0x00) - | (0x08 if world.mapshuffle else 0x00))) # free roaming items in menu + rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00) + | (0x02 if world.bigkeyshuffle[player] else 0x00) + | (0x04 if world.compassshuffle[player] else 0x00) + | (0x08 if world.mapshuffle[player] else 0x00))) # free roaming items in menu # Map reveals reveal_bytes = { @@ -998,8 +998,8 @@ def patch_rom(world, player, rom, enemized): return reveal_bytes.get(location.parent_region.dungeon.name, 0x0000) return 0x0000 - write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.mapshuffle else 0x0000) # Sahasrahla reveal - write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.mapshuffle else 0x0000) # Bomb Shop Reveal + write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.mapshuffle[player] else 0x0000) # Sahasrahla reveal + write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.mapshuffle[player] else 0x0000) # Bomb Shop Reveal rom.write_byte(0x180172, 0x01 if world.retro else 0x00) # universal keys rom.write_byte(0x180175, 0x01 if world.retro else 0x00) # rupee bow @@ -1493,9 +1493,9 @@ def write_strings(rom, world, player): # Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well. items_to_hint = RelevantItems.copy() - if world.keyshuffle: + if world.keyshuffle[player]: items_to_hint.extend(SmallKeys) - if world.bigkeyshuffle: + if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) random.shuffle(items_to_hint) hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 diff --git a/Rules.py b/Rules.py index 99810d96..052865ce 100644 --- a/Rules.py +++ b/Rules.py @@ -804,7 +804,7 @@ def set_trock_key_rules(world, player): non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'] - if not world.keyshuffle: + if not world.keyshuffle[player]: non_big_key_locations += ['Turtle Rock - Big Key Chest'] else: set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state.has_key('Small Key (Turtle Rock)', player, 2) if item_in_locations(state, 'Big Key (Turtle Rock)', player, [('Turtle Rock - Compass Chest', player), ('Turtle Rock - Roller Room - Left', player), ('Turtle Rock - Roller Room - Right', player)]) else state.has_key('Small Key (Turtle Rock)', player, 4)) @@ -814,7 +814,7 @@ def set_trock_key_rules(world, player): non_big_key_locations += ['Turtle Rock - Crystaroller Room', 'Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'] - if not world.keyshuffle: + if not world.keyshuffle[player]: non_big_key_locations += ['Turtle Rock - Big Key Chest', 'Turtle Rock - Chain Chomps'] # set big key restrictions