diff --git a/BaseClasses.py b/BaseClasses.py index 8ff91295..a601a3c5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -22,7 +22,7 @@ from source.dungeon.RoomObject import RoomObject class World(object): def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, - timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints): + timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints): self.players = players self.teams = 1 self.shuffle = shuffle.copy() @@ -64,7 +64,6 @@ class World(object): self.fix_trock_exit = {} self.shuffle_ganon = shuffle_ganon self.fix_gtower_exit = self.shuffle_ganon - self.retro = retro.copy() self.custom = custom self.customitemarray = customitemarray self.can_take_damage = True @@ -91,10 +90,6 @@ class World(object): self.pot_contents = {} for player in range(1, players + 1): - # If World State is Retro, set to Open and set Retro flag - if self.mode[player] == "retro": - self.mode[player] = "open" - self.retro[player] = True def set_player_attr(attr, val): self.__dict__.setdefault(attr, {})[player] = val set_player_attr('_region_cache', {}) @@ -118,10 +113,12 @@ class World(object): set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['crossed', 'insanity']) set_player_attr('mapshuffle', False) set_player_attr('compassshuffle', False) - set_player_attr('keyshuffle', False) + set_player_attr('keyshuffle', 'standard') set_player_attr('bigkeyshuffle', False) set_player_attr('restrict_boss_items', 'none') set_player_attr('bombbag', False) + set_player_attr('flute_mode', False) + set_player_attr('bow_mode', False) set_player_attr('difficulty_requirements', None) set_player_attr('boss_shuffle', 'none') set_player_attr('enemy_shuffle', 'none') @@ -134,6 +131,7 @@ class World(object): set_player_attr('crystals_ganon_orig', {}) set_player_attr('crystals_gt_orig', {}) set_player_attr('open_pyramid', False) + set_player_attr('take_any', 'none') set_player_attr('treasure_hunt_icon', 'Triforce Piece') set_player_attr('treasure_hunt_count', 0) set_player_attr('treasure_hunt_total', 0) @@ -154,6 +152,11 @@ class World(object): set_player_attr('exp_cache', defaultdict(dict)) set_player_attr('enabled_entrances', {}) + def finish_init(self): + for player in range(1, self.players + 1): + if self.mode[player] == 'retro': + self.mode[player] == 'open' + def get_name_string_for_object(self, obj): return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})' @@ -375,7 +378,8 @@ class World(object): def push_precollected(self, item): item.world = self - if (item.smallkey and self.keyshuffle[item.player]) or (item.bigkey and self.bigkeyshuffle[item.player]): + if ((item.smallkey and self.keyshuffle[item.player] != 'none') + or (item.bigkey and self.bigkeyshuffle[item.player])): item.advancement = True self.precollected_items.append(item) self.state.collect(item, True) @@ -587,7 +591,7 @@ class CollectionState(object): if key_logic.sm_doors[door]: self.reached_doors[player].add(key_logic.sm_doors[door].name) if not connection.can_reach(self): - checklist_key = 'Universal' if self.world.retro[player] else dungeon_name + checklist_key = 'Universal' if self.world.keyshuffle[player] == 'universal' else dungeon_name checklist = self.dungeons_to_check[player][checklist_key] checklist[connection.name] = (connection, crystal_state) elif door.name not in self.opened_doors[player]: @@ -759,7 +763,7 @@ class CollectionState(object): return None def set_dungeon_limits(self, player, dungeon_name): - if self.world.retro[player] and self.world.mode[player] == 'standard': + if self.world.keyshuffle[player] == 'universal' and self.world.mode[player] == 'standard': self.dungeon_limits = ['Hyrule Castle', 'Agahnims Tower'] else: self.dungeon_limits = [dungeon_name] @@ -935,7 +939,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[location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey)) + (not key_only or (self.world.keyshuffle[location.item.player] == 'none' and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey)) and location.can_reach(self)] reachable_events = self._do_not_flood_the_keys(reachable_events) for event in reachable_events: @@ -995,7 +999,7 @@ class CollectionState(object): return self.prog_items[item, player] >= count def has_sm_key(self, item, player, count=1): - if self.world.retro[player]: + if self.world.keyshuffle[player] == 'universal': if self.world.mode[player] == 'standard' and self.world.doorShuffle[player] == 'vanilla' and item == 'Small Key (Escape)': return True # Cannot access the shop until escape is finished. This is safe because the key is manually placed in make_custom_item_pool return self.can_buy_unlimited('Small Key (Universal)', player) @@ -1091,7 +1095,7 @@ class CollectionState(object): or self.has('Cane of Somaria', player)) def can_shoot_arrows(self, player): - if self.world.retro[player]: + if self.world.bow_mode[player] in ['retro', 'retro_silvers']: #todo: Non-progressive silvers grant wooden arrows, but progressive bows do not. Always require shop arrows to be safe return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player)) return self.has('Bow', player) @@ -1373,7 +1377,7 @@ class Region(object): return False def can_fill(self, item): - inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle[item.player]) + inside_dungeon_item = ((item.smallkey and self.world.keyshuffle[item.player] == 'none') 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])) @@ -2239,7 +2243,7 @@ class Item(object): return item_dungeon def is_inside_dungeon_item(self, world): - return ((self.smallkey and not world.keyshuffle[self.player]) + return ((self.smallkey and world.keyshuffle[self.player] == 'none') or (self.bigkey and not world.bigkeyshuffle[self.player]) or (self.compass and not world.compassshuffle[self.player]) or (self.map and not world.mapshuffle[self.player])) @@ -2371,15 +2375,16 @@ class Spoiler(object): self.metadata = {'version': ERVersion, 'logic': self.world.logic, 'mode': self.world.mode, - 'retro': self.world.retro, 'bombbag': self.world.bombbag, 'weapons': self.world.swords, 'flute_mode': self.world.flute_mode, + 'bow_mode': self.world.bow_mode, 'goal': self.world.goal, 'shuffle': self.world.shuffle, 'shuffleganon': self.world.shuffle_ganon, 'shufflelinks': self.world.shufflelinks, 'shuffletavern': self.world.shuffletavern, + 'take_any': self.world.take_any, 'overworld_map': self.world.overworld_map, 'door_shuffle': self.world.doorShuffle, 'intensity': self.world.intensity, @@ -2561,7 +2566,6 @@ class Spoiler(object): outfile.write(f'Settings Code: {self.metadata["code"][player]}\n') outfile.write('Logic: %s\n' % self.metadata['logic'][player]) outfile.write('Mode: %s\n' % self.metadata['mode'][player]) - outfile.write('Retro: %s\n' % ('Yes' if self.metadata['retro'][player] else 'No')) outfile.write('Swords: %s\n' % self.metadata['weapons'][player]) outfile.write('Goal: %s\n' % self.metadata['goal'][player]) if self.metadata['goal'][player] in ['triforcehunt', 'trinity']: @@ -2574,6 +2578,7 @@ class Spoiler(object): outfile.write('Difficulty: %s\n' % self.metadata['item_pool'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) outfile.write(f"Flute Mode: {self.metadata['flute_mode'][player]}\n") + outfile.write(f"Bow Mode: {self.metadata['bow_mode'][player]}\n") outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][player])}\n") outfile.write(f"Bombbag: {yn(self.metadata['bombbag'][player])}\n") outfile.write(f"Pseudoboots: {yn(self.metadata['pseudoboots'][player])}\n") @@ -2583,6 +2588,8 @@ class Spoiler(object): outfile.write(f"Back of Tavern Shuffled: {yn(self.metadata['shuffletavern'][player])}\n") outfile.write(f"GT/Ganon Shuffled: {yn(self.metadata['shuffleganon'])}\n") outfile.write(f"Overworld Map: {self.metadata['overworld_map'][player]}\n") + outfile.write(f"Take Any Caves: {self.metadata['take_any'][player]}\n") + outfile.write(f"Overworld Map: {self.metadata['overworld_map'][player]}\n") if self.metadata['goal'][player] != 'trinity': outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No')) outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) @@ -2597,7 +2604,7 @@ class Spoiler(object): outfile.write(f"Pot Shuffle (Legacy): {yn(self.metadata['potshuffle'][player])}\n") outfile.write('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(f"Small Key shuffle: {self.metadata['keyshuffle'][player]}\n") 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]) @@ -2835,7 +2842,7 @@ dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3} er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8, 'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6} -# byte 1: LLLW WSSR (logic, mode, sword, retro) +# byte 1: LLLW WSS? (logic, mode, sword) logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4} world_mode = {"open": 0, "standard": 1, "inverted": 2} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} @@ -2861,7 +2868,7 @@ counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3} # byte 6: CCCC CPAA (crystals ganon, pyramid, access access_mode = {"items": 0, "locations": 1, "none": 2} -# byte 7: BSMC DDEE (big, small, maps, compass, door_type, enemies) +# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies) door_type_mode = {'original': 0, 'big': 1, 'all': 2, 'chaos': 3} enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3} @@ -2878,15 +2885,18 @@ boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique # byte 10: settings_version -# byte 11: F???, ???? (flute_mode) +# byte 11: FBBB, TTSS (flute_mode, bow_mode, take_any, small_key_mode) flute_mode = {'normal': 0, 'active': 1} +keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes? +take_any_mode = {'none': 0, 'random': 1, 'fixed': 2} +bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silver': 3} # additions # psuedoboots does not effect code # sfx_shuffle and other adjust items does not effect settings code # Bump this when making changes that are not backwards compatible (nearly all of them) -settings_version = 0 +settings_version = 1 class Settings(object): @@ -2897,7 +2907,7 @@ class Settings(object): (dr_mode[w.doorShuffle[p]] << 5) | er_mode[w.shuffle[p]], (logic_mode[w.logic[p]] << 5) | (world_mode[w.mode[p]] << 3) - | (sword_mode[w.swords[p]] << 1) | (1 if w.retro[p] else 0), + | (sword_mode[w.swords[p]] << 1), (goal_mode[w.goal[p]] << 5) | (diff_mode[w.difficulty[p]] << 3) | (func_mode[w.difficulty_adjustments[p]] << 1) | (1 if w.hints[p] else 0), @@ -2915,7 +2925,7 @@ class Settings(object): ((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3) | (0x4 if w.open_pyramid[p] else 0) | access_mode[w.accessibility[p]], - (0x80 if w.bigkeyshuffle[p] else 0) | (0x40 if w.keyshuffle[p] else 0) + (0x80 if w.bigkeyshuffle[p] else 0) | (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0) | (door_type_mode[w.door_type_mode[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]), @@ -2926,14 +2936,16 @@ class Settings(object): settings_version, - flute_mode[w.flute_mode[p]] << 7]) + (flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4 + | take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]) + ]) return base64.b64encode(code, "+-".encode()).decode() @staticmethod def adjust_args_from_code(code, player, args): settings, p = base64.b64decode(code.encode(), "+-".encode()), player - if len(settings) < 11: + if len(settings) < 12: raise Exception('Provided code is incompatible with this version') if settings[10] != settings_version: raise Exception('Provided code is incompatible with this version') @@ -2950,7 +2962,7 @@ class Settings(object): args.item_functionality[p] = r(func_mode)[(settings[2] & 0x6) >> 1] args.goal[p] = r(goal_mode)[(settings[2] & 0xE0) >> 5] args.accessibility[p] = r(access_mode)[settings[6] & 0x3] - args.retro[p] = True if settings[1] & 0x01 else False + # args.retro[p] = True if settings[1] & 0x01 else False args.hints[p] = True if settings[2] & 0x01 else False args.shopsanity[p] = True if settings[3] & 0x80 else False args.decoupledoors[p] = True if settings[3] & 0x40 else False @@ -2973,7 +2985,7 @@ class Settings(object): args.openpyramid[p] = True if settings[6] & 0x4 else False args.bigkeyshuffle[p] = True if settings[7] & 0x80 else False - args.keyshuffle[p] = True if settings[7] & 0x40 else False + # args.keyshuffle[p] = True if settings[7] & 0x40 else False args.mapshuffle[p] = True if settings[7] & 0x20 else False args.compassshuffle[p] = True if settings[7] & 0x10 else False args.door_type_mode[p] = r(door_type_mode)[(settings[7] & 0xc) >> 2] @@ -2990,6 +3002,9 @@ class Settings(object): args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)] if len(settings) > 11: args.flute_mode[p] = r(flute_mode)[(settings[11] & 0x80) >> 7] + args.bow_mode[p] = r(bow_mode)[(settings[11] & 0x70) >> 4] + args.take_any[p] = r(take_any_mode)[(settings[11] & 0xC) >> 2] + args.keyshuffle[p] = r(keyshuffle_mode)[settings[11] & 0x3] class KeyRuleType(FastEnum): diff --git a/CLI.py b/CLI.py index 44c7b76c..942bd7f2 100644 --- a/CLI.py +++ b/CLI.py @@ -103,19 +103,28 @@ def parse_cli(argv, no_defaults=False): ret = parser.parse_args(argv) if ret.keysanity: - ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4 + ret.mapshuffle, ret.compassshuffle, ret.bigkeyshuffle = [True] * 3 + ret.keyshuffle = 'wild' if ret.keydropshuffle: ret.dropshuffle = True ret.pottery = 'keys' if ret.pottery == 'none' else ret.pottery + if ret.retro or ret.mode == 'retro': + if ret.bow_mode == 'progressive': + ret.bow_mode = 'retro' + elif ret.bow_mode == 'silvers': + ret.bow_mode = 'retro_silvers' + ret.take_any = 'random' if ret.take_any == 'none' else ret.take_any + ret.keyshuffle = 'universal' + if player_num: defaults = copy.deepcopy(ret) for player in range(1, player_num + 1): playerargs = parse_cli(shlex.split(getattr(ret, f"p{player}")), True) for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', - 'flute_mode', + 'flute_mode', 'bow_mode', 'take_any', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'usestartinventory', 'bombbag', 'overworld_map', 'restrict_boss_items', @@ -158,6 +167,7 @@ def parse_settings(): "crystals_ganon": "7", "swords": "random", 'flute_mode': 'normal', + 'bow_mode': 'progressive', "difficulty": "normal", "item_functionality": "normal", "timer": "none", @@ -175,6 +185,7 @@ def parse_settings(): "shufflelinks": False, "shuffletavern": False, "overworld_map": "default", + 'take_any': 'none', "pseudoboots": False, "shuffleenemies": "none", @@ -189,11 +200,11 @@ def parse_settings(): 'pottery': 'none', 'colorizepots': False, 'shufflepots': False, - "mapshuffle": False, - "compassshuffle": False, - "keyshuffle": False, - "bigkeyshuffle": False, - "keysanity": False, + 'mapshuffle': False, + 'compassshuffle': False, + 'keyshuffle': 'none', + 'bigkeyshuffle': False, + 'keysanity': False, "door_shuffle": "basic", "intensity": 2, 'door_type_mode': 'original', diff --git a/DoorShuffle.py b/DoorShuffle.py index 89463230..0d56882e 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -470,7 +470,7 @@ def choose_portals(world, player): for dungeon, info in shuffled_info: outstanding_portals = list(dungeon_portals[dungeon]) hc_flag = std_flag and dungeon == 'Hyrule Castle' - rupee_bow_flag = hc_flag and world.retro[player] # rupee bow + rupee_bow_flag = hc_flag and world.bow_mode[player].startswith('retro') # rupee bow if hc_flag: sanc = world.get_portal('Sanctuary', player) sanc.destination = True @@ -843,7 +843,7 @@ def main_dungeon_pool(dungeon_pool, world, player): all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items)) target_items = 34 - if world.retro[player]: + if world.keyshuffle[player] == 'universal': target_items += 1 if world.dropshuffle[player] else 0 # the hc big key else: target_items += 29 # small keys in chests @@ -1248,7 +1248,7 @@ def cross_dungeon(world, player): assign_cross_keys(dungeon_builders, world, player) all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items)) target_items = 34 - if world.retro[player]: + if world.keyshuffle[player] == 'universal': target_items += 1 if world.dropshuffle[player] else 0 # the hc big key else: target_items += 29 # small keys in chests @@ -1336,7 +1336,7 @@ def filter_key_door_pool(pool, selected_custom): def assign_cross_keys(dungeon_builders, world, player): logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors")) start = time.process_time() - if world.retro[player]: + if world.keyshuffle[player] == 'universal': remaining = 29 if world.dropshuffle[player]: remaining += 13 @@ -1424,7 +1424,7 @@ def assign_cross_keys(dungeon_builders, world, player): # Last Step: Adjust Small Key Dungeon Pool for name, builder in dungeon_builders.items(): reassign_key_doors(builder, world, player) - if not world.retro[player]: + if world.keyshuffle[player] != 'universal': log_key_logic(builder.name, world.key_logic[player][builder.name]) actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0) dungeon = world.get_dungeon(name, player) @@ -1963,7 +1963,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl # time to re-assign reassign_key_doors(small_map, world, player) for dungeon_name in pool: - if not world.retro[player]: + if world.keyshuffle[player] != 'universal': builder = world.dungeon_layouts[player][dungeon_name] log_key_logic(builder.name, world.key_logic[player][builder.name]) if world.doorShuffle[player] != 'basic': @@ -2470,7 +2470,7 @@ def reassign_big_key_doors(bk_map, world, player): pass # we don't have spiral stairs candidates yet that aren't already key doors elif d.type is DoorType.Normal: change_door_to_big_key(d, world, player) - if not world.decoupledoors[player] and d.dest: + if not world.decoupledoors[player] and d.dest and world.door_type_mode[player] != 'original': if d.dest.type in [DoorType.Normal]: dest_room = world.get_room(d.dest.roomIndex, player) if stateful_door(d.dest, dest_room.kind(d.dest)): diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 4404d53f..445ed9b3 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1355,7 +1355,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge sanc_builder = random.choice(lw_builders) assign_sector(sanc, sanc_builder, candidate_sectors, global_pole) - bow_sectors, retro_std_flag = {}, world.retro[player] and world.mode[player] == 'standard' + bow_sectors, retro_std_flag = {}, world.bow_mode[player].startswith('retro') and world.mode[player] == 'standard' free_location_sectors = {} crystal_switches = {} crystal_barriers = {} diff --git a/Dungeons.py b/Dungeons.py index 59a6a963..9d862b05 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -5,7 +5,7 @@ from Items import ItemFactory def create_dungeons(world, player): def make_dungeon(name, id, default_boss, dungeon_regions, big_key, small_keys, dungeon_items): - dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro[player] else small_keys, + dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.keyshuffle[player] == 'universal' else small_keys, dungeon_items, player, id) dungeon.boss = BossFactory(default_boss, player) for region in dungeon.regions: diff --git a/Fill.py b/Fill.py index a8eecc3a..db18996f 100644 --- a/Fill.py +++ b/Fill.py @@ -39,7 +39,8 @@ 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[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]): + if ((item.smallkey and world.keyshuffle[item.player] != 'none') + or (item.bigkey and world.bigkeyshuffle[item.player])): item.advancement = True elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]): item.priority = True @@ -50,7 +51,7 @@ def fill_dungeons_restrictive(world, shuffled_locations): (bigs if i.bigkey else smalls if i.smallkey else others).append(i) unplaced_smalls = list(smalls) for i in world.itempool: - if i.smallkey and world.keyshuffle[i.player]: + if i.smallkey and world.keyshuffle[i.player] != 'none': unplaced_smalls.append(i) def fill(base_state, items, key_pool): @@ -160,7 +161,7 @@ def valid_key_placement(item, location, key_pool, world): if not valid_reserved_placement(item, location, world): return False if ((not item.smallkey and not item.bigkey) or item.player != location.player - or world.retro[item.player] or world.logic[item.player] == 'nologic'): + or world.keyshuffle[item.player] == 'universal' or world.logic[item.player] == 'nologic'): return True dungeon = location.parent_region.dungeon if dungeon: @@ -215,7 +216,7 @@ def track_dungeon_items(item, location, world): def is_dungeon_item(item, world): - return ((item.smallkey and not world.keyshuffle[item.player]) + return ((item.smallkey and world.keyshuffle[item.player] == 'none') or (item.bigkey and not world.bigkeyshuffle[item.player]) or (item.compass and not world.compassshuffle[item.player]) or (item.map and not world.mapshuffle[item.player])) @@ -419,7 +420,8 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots # todo: crossed - progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0) + progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' + and world.keyshuffle[item.player] != 'none' and world.mode[item.player] == 'standard' else 0) key_pool = [x for x in progitempool if x.smallkey] # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia @@ -498,7 +500,7 @@ def ensure_good_pots(world, write_skips=False): else: loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player) # do the arrow retro check - if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: + if world.bow_mode[loc.item.player].startswith('retro') and loc.item.name in {'Arrows (5)', 'Arrows (10)'}: loc.item = ItemFactory('Rupees (5)', loc.item.player) # don't write out all pots to spoiler if write_skips: @@ -663,7 +665,7 @@ def balance_multiworld_progression(world): candidate_items = collections.defaultdict(set) while True: for location in balancing_sphere: - 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): + if location.event and (world.keyshuffle[location.item.player] != 'none' or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey): balancing_state.collect(location.item, True, location) player = location.item.player if player in balancing_players and not location.locked and location.player != player: @@ -738,7 +740,7 @@ def balance_multiworld_progression(world): sphere_locations.add(location) for location in sphere_locations: - 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): + if location.event and (world.keyshuffle[location.item.player] != 'none' or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey): state.collect(location.item, True, location) checked_locations |= sphere_locations @@ -812,7 +814,9 @@ def balance_money_progression(world): return True if item.name in ['Progressive Armor', 'Blue Mail', 'Red Mail']: return True - if world.retro[player] and (item.name in ['Single Arrow', 'Small Key (Universal)']): + if world.keyshuffle[player] == 'universal' and item.name == 'Small Key (Universal)': + return True + if world.bow_mode[player].startswith('retro') and item.name == 'Single Arrow': return True if location.name in pay_for_locations: return True diff --git a/InitialSram.py b/InitialSram.py index 63c822cd..8d0ade11 100644 --- a/InitialSram.py +++ b/InitialSram.py @@ -73,7 +73,7 @@ class InitialSram: if startingstate.has('Bow', player): equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1 equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases - if not world.retro[player]: + if not world.bow_mode[player].startswith('retro'): equip[0x38E] |= 0x80 if startingstate.has('Silver Arrows', player): equip[0x38E] |= 0x40 @@ -188,7 +188,7 @@ class InitialSram: elif item.name in bombs: starting_bombs += bombs[item.name] elif item.name in arrows: - if world.retro[player]: + if world.bow_mode[player].startswith('retro'): equip[0x38E] |= 0x80 starting_arrows = 1 else: diff --git a/ItemList.py b/ItemList.py index 1dffb859..c1981365 100644 --- a/ItemList.py +++ b/ItemList.py @@ -264,10 +264,10 @@ def generate_itempool(world, player): (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = make_customizer_pool(world, player) skip_pool_adjustments = True elif world.custom: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bombbag[player], world.customitemarray) + (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world, player, world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.bombbag[player], world.customitemarray) world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999) else: - (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bombbag[player], world.doorShuffle[player], world.logic[player], world.flute_mode[player] == 'active') + (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world, player, world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.bombbag[player], world.doorShuffle[player], world.logic[player], world.flute_mode[player] == 'active') if player in world.pool_adjustment.keys() and not skip_pool_adjustments: amt = world.pool_adjustment[player] @@ -295,7 +295,8 @@ def generate_itempool(world, player): if not found_sword and world.swords[player] != 'swordless': found_sword = True possible_weapons.append(item) - if item in ['Progressive Bow', 'Bow'] and not found_bow and not world.retro[player]: + if (item in ['Progressive Bow', 'Bow'] and not found_bow + and not world.bow_mode[player].startswith('retro')): found_bow = True possible_weapons.append(item) if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: @@ -349,7 +350,7 @@ def generate_itempool(world, player): world.treasure_hunt_icon[player] = 'Triforce Piece' world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player - and ((item.smallkey and world.keyshuffle[player]) + and ((item.smallkey and world.keyshuffle[player] != 'none') or (item.bigkey and world.bigkeyshuffle[player]) or (item.map and world.mapshuffle[player]) or (item.compass and world.compassshuffle[player]))]) @@ -409,8 +410,9 @@ def generate_itempool(world, player): set_up_shops(world, player) - if world.retro[player]: + if world.take_any[player] != 'none': set_up_take_anys(world, player, skip_pool_adjustments) + if world.keyshuffle[player] == 'universal': if world.dropshuffle[player] and not skip_pool_adjustments: world.itempool += [ItemFactory('Small Key (Universal)', player)] * 13 if world.pottery[player] not in ['none', 'cave'] and not skip_pool_adjustments: @@ -432,6 +434,10 @@ take_any_locations = [ 'Palace of Darkness Hint', 'East Dark World Hint', 'Archery Game', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint'] +fixed_take_anys = [ + 'Desert Healer Fairy', 'Swamp Healer Fairy', 'Dark Death Mountain Healer Fairy', + 'Dark Lake Hylia Ledge Healer Fairy', 'Bonk Fairy (Dark)'] + def set_up_take_anys(world, player, skip_adjustments=False): if world.mode[player] == 'inverted': @@ -440,9 +446,12 @@ def set_up_take_anys(world, player, skip_adjustments=False): if 'Archery Game' in take_any_locations: take_any_locations.remove('Archery Game') - take_any_candidates = [x for x in take_any_locations if len(world.get_region(x, player).locations) == 0] - - regions = random.sample(take_any_candidates, 5) + if world.take_any[player] == 'random': + take_any_candidates = [x for x in take_any_locations if len(world.get_region(x, player).locations) == 0] + regions = random.sample(take_any_candidates, 5) + elif world.take_any[player] == 'fixed': + regions = list(fixed_take_anys) + random.shuffle(regions) old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player) world.regions.append(old_man_take_any) @@ -539,38 +548,47 @@ def fill_prizes(world, attempts=15): def set_up_shops(world, player): - if world.retro[player]: + retro_bow = world.bow_mode[player].startswith('retro') + universal_keys = world.keyshuffle[player] == 'universal' + if retro_bow or universal_keys: if world.shopsanity[player]: - removals = [next(item for item in world.itempool if item.name == 'Arrows (10)' and item.player == player)] - red_pots = [item for item in world.itempool if item.name == 'Red Potion' and item.player == player][:5] - shields_n_hearts = [item for item in world.itempool if item.name in ['Blue Shield', 'Small Heart'] and item.player == player] - removals.extend([item for item in world.itempool if item.name == 'Arrow Upgrade (+5)' and item.player == player]) - removals.extend(red_pots) - removals.extend(random.sample(shields_n_hearts, 5)) + removals = [] + if retro_bow: + removals = [next(item for item in world.itempool if item.name == 'Arrows (10)' and item.player == player)] + removals.extend([item for item in world.itempool if item.name == 'Arrow Upgrade (+5)' and item.player == player]) + shields_n_hearts = [item for item in world.itempool if item.name in ['Blue Shield', 'Small Heart'] and item.player == player] + removals.extend(random.sample(shields_n_hearts, 5)) + if universal_keys: + red_pots = [item for item in world.itempool if item.name == 'Red Potion' and item.player == player][:5] + removals.extend(red_pots) for remove in removals: world.itempool.remove(remove) - for i in range(6): # replace the Arrows (10) and randomly selected hearts/blue shield - arrow_item = ItemFactory('Single Arrow', player) - arrow_item.advancement = True - world.itempool.append(arrow_item) - for i in range(5): # replace the red potions - world.itempool.append(ItemFactory('Small Key (Universal)', player)) - world.itempool.append(ItemFactory('Rupees (50)', player)) # replaces the arrow upgrade + if retro_bow: + for i in range(6): # replace the Arrows (10) and randomly selected hearts/blue shield + arrow_item = ItemFactory('Single Arrow', player) + arrow_item.advancement = True + world.itempool.append(arrow_item) + world.itempool.append(ItemFactory('Rupees (50)', player)) # replaces the arrow upgrade + if universal_keys: + for i in range(5): # replace the red potions + world.itempool.append(ItemFactory('Small Key (Universal)', player)) # TODO: move hard+ mode changes for shields here, utilizing the new shops else: - rss = world.get_region('Red Shield Shop', player).shop - if not rss.locked: - rss.custom = True - rss.add_inventory(2, 'Single Arrow', 80) + if retro_bow: + rss = world.get_region('Red Shield Shop', player).shop + if not rss.locked: + rss.custom = True + rss.add_inventory(2, 'Single Arrow', 80) + rss.locked = True + cap_shop = world.get_region('Capacity Upgrade', player).shop + cap_shop.inventory[1] = None # remove arrow capacity upgrades in retro for shop in random.sample([s for s in world.shops[player] if not s.locked and s.region.player == player], 5): shop.custom = True shop.locked = True - shop.add_inventory(0, 'Single Arrow', 80) - shop.add_inventory(1, 'Small Key (Universal)', 100) - shop.add_inventory(2, 'Bombs (10)', 50) - rss.locked = True - cap_shop = world.get_region('Capacity Upgrade', player).shop - cap_shop.inventory[1] = None # remove arrow capacity upgrades in retro + if retro_bow: + shop.add_inventory(0, 'Single Arrow', 80) + if universal_keys: + shop.add_inventory(1, 'Small Key (Universal)', 100) if world.bombbag[player]: if world.shopsanity[player]: removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player] @@ -583,10 +601,11 @@ def set_up_shops(world, player): def customize_shops(world, player): - found_bomb_upgrade, found_arrow_upgrade = False, world.retro[player] + retro_bow = world.bow_mode[player].startswith('retro') + found_bomb_upgrade, found_arrow_upgrade = False, retro_bow possible_replacements = [] shops_to_customize = shop_to_location_table.copy() - if world.retro[player]: + if world.take_any[player] != 'none': shops_to_customize.update(retro_shops) for shop_name, loc_list in shops_to_customize.items(): shop = world.get_region(shop_name, player).shop @@ -609,7 +628,7 @@ def customize_shops(world, player): price = 0 else: price = 120 if shop_name == 'Potion Shop' and item.name == 'Red Potion' else item.price - if world.retro[player] and item.name == 'Single Arrow': + if retro_bow and item.name == 'Single Arrow': price = 80 # randomize price shop.add_inventory(idx, item.name, randomize_price(price), max_repeat, player=item.player) @@ -636,7 +655,7 @@ def customize_shops(world, player): if not found_arrow_upgrade and len(possible_replacements) > 0: choices = [] for shop, idx, loc, item in possible_replacements: - if item.name == 'Arrows (10)' or (item.name == 'Single Arrow' and not world.retro[player]): + if item.name == 'Arrows (10)' or (item.name == 'Single Arrow' and not retro_bow): choices.append((shop, idx, loc, item)) if len(choices) > 0: shop, idx, loc, item = random.choice(choices) @@ -780,16 +799,17 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)' def add_pot_contents(world, player): + retro_bow = world.bow_mode[player].startswith('retro') for super_tile, pot_list in vanilla_pots.items(): for pot in pot_list: if pot.item not in [PotItem.Hole, PotItem.Key, PotItem.Switch]: if valid_pot_location(pot, world.pot_pool[player], world, player): - item = ('Rupees (5)' if world.retro[player] and pot_items[pot.item] == 'Arrows (5)' + item = ('Rupees (5)' if retro_bow and pot_items[pot.item] == 'Arrows (5)' else pot_items[pot.item]) world.itempool.append(ItemFactory(item, player)) -def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, +def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, bombbag, door_shuffle, logic, flute_activated): pool = [] placed_items = {} @@ -862,7 +882,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, else: pool.extend(diff.basicarmor) - if want_progressives(): + if 'silvers' not in world.bow_mode[player]: pool.extend(['Progressive Bow'] * 2) elif swords != 'swordless': pool.extend(diff.basicbow) @@ -910,13 +930,14 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, if goal in ['pedestal', 'trinity'] and swords != 'vanilla': place_item('Master Sword Pedestal', 'Triforce') - if retro: - pool = [item.replace('Single Arrow','Rupees (5)') for item in pool] - pool = [item.replace('Arrows (10)','Rupees (5)') for item in pool] - pool = [item.replace('Arrow Upgrade (+5)','Rupees (5)') for item in pool] - pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool] + if world.bow_mode[player].startswith('retro'): + pool = [item.replace('Single Arrow', 'Rupees (5)') for item in pool] + pool = [item.replace('Arrows (10)', 'Rupees (5)') for item in pool] + pool = [item.replace('Arrow Upgrade (+5)', 'Rupees (5)') for item in pool] + pool = [item.replace('Arrow Upgrade (+10)', 'Rupees (5)') for item in pool] + if world.keyshuffle[player] == 'universal': pool.extend(diff.retro) - if door_shuffle != 'vanilla': # door shuffle needs more keys for retro + if door_shuffle != 'vanilla': # door shuffle needs more keys for universal keys replace = 'Rupees (20)' if difficulty == 'normal' else 'Rupees (5)' indices = [i for i, x in enumerate(pool) if x == replace] for i in range(0, min(10, len(indices))): @@ -932,7 +953,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) -def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag, customitemarray): +def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer, goal, mode, swords, bombbag, customitemarray): if isinstance(customitemarray,dict) and 1 in customitemarray: customitemarray = customitemarray[1] pool = [] @@ -1014,7 +1035,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s itemtotal = itemtotal + 1 if mode == 'standard': - if retro: + if world.keyshuffle[player] == 'universal': key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) place_item(key_location, 'Small Key (Universal)') pool.extend(['Small Key (Universal)'] * max((customitemarray["generickeys"] - 1), 0)) @@ -1028,7 +1049,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s pool.extend(['Magic Mirror'] * customitemarray["mirror"]) pool.extend(['Moon Pearl'] * customitemarray["pearl"]) - if retro: + if world.keyshuffle[player] == 'universal': itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode if itemtotal < total_items_to_place: nothings = total_items_to_place - itemtotal @@ -1066,7 +1087,7 @@ def make_customizer_pool(world, player): pool.append(random.choice(diff.bottles)) elif item_name.startswith('Small Key') and item_name != 'Small Key (Universal)': d_item = ItemFactory(item_name, player) - if not world.keyshuffle[player]: + if world.keyshuffle[player] == 'none': d_name = d_item.dungeon dungeon = world.get_dungeon(d_name, player) target_amount = max(amount, len(dungeon.small_keys)) @@ -1121,7 +1142,7 @@ def make_customizer_pool(world, player): guaranteed_items = alwaysitems + ['Magic Mirror', 'Moon Pearl'] if world.shopsanity[player]: guaranteed_items.extend(['Blue Potion', 'Green Potion', 'Red Potion']) - if world.retro[player]: + if world.keyshuffle[player] == 'universal': guaranteed_items.append('Small Key (Universal)') for item in guaranteed_items: if item not in pool: @@ -1199,34 +1220,34 @@ def set_default_triforce(goal, custom_goal, custom_total): # A quick test to ensure all combinations generate the correct amount of items. -def test(): - for difficulty in ['normal', 'hard', 'expert']: - for goal in ['ganon', 'triforcehunt', 'pedestal', 'trinity']: - for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: - for mode in ['open', 'standard', 'inverted', 'retro']: - for swords in ['random', 'assured', 'swordless', 'vanilla']: - for progressive in ['on', 'off']: - for shuffle in ['full']: - for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: - for retro in [True, False]: - for bombbag in [True, False]: - for door_shuffle in ['basic', 'crossed', 'vanilla']: - out = get_pool_core(progressive, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic) - count = len(out[0]) + len(out[1]) - - correct_count = total_items_to_place - if goal in ['pedestal', 'trinity'] and swords != 'vanilla': - # pedestal goals generate one extra item - correct_count += 1 - if retro: - correct_count += 28 - try: - assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag)) - except AssertionError as e: - print(e) - -if __name__ == '__main__': - test() +# def test(): +# for difficulty in ['normal', 'hard', 'expert']: +# for goal in ['ganon', 'triforcehunt', 'pedestal', 'trinity']: +# for timer in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']: +# for mode in ['open', 'standard', 'inverted', 'retro']: +# for swords in ['random', 'assured', 'swordless', 'vanilla']: +# for progressive in ['on', 'off']: +# for shuffle in ['full']: +# for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']: +# for retro in [True, False]: +# for bombbag in [True, False]: +# for door_shuffle in ['basic', 'crossed', 'vanilla']: +# out = get_pool_core(progressive, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic) +# count = len(out[0]) + len(out[1]) +# +# correct_count = total_items_to_place +# if goal in ['pedestal', 'trinity'] and swords != 'vanilla': +# # pedestal goals generate one extra item +# correct_count += 1 +# if retro: +# correct_count += 28 +# try: +# assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag)) +# except AssertionError as e: +# print(e) +# +# if __name__ == '__main__': +# test() def fill_specific_items(world): @@ -1270,7 +1291,7 @@ def fill_specific_items(world): def is_dungeon_item(item, world, player): - return ((item.startswith('Small Key') and not world.keyshuffle[player]) + return ((item.startswith('Small Key') and world.keyshuffle[player] == 'none') or (item.startswith('Big Key') and not world.bigkeyshuffle[player]) or (item.startswith('Compass') and not world.compassshuffle[player]) or (item.startswith('Map') and not world.mapshuffle[player])) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 4d501764..9cca37e3 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -158,7 +158,7 @@ class PlacementRule(object): left -= rule_needed return False - def is_satisfiable(self, outside_keys, unplaced_keys, big_key_loc, prize_location, cr_count): + def is_satisfiable(self, outside_keys, wild_keys, unplaced_keys, big_key_loc, prize_location, cr_count): if self.prize_relevance and prize_location: if self.prize_relevance == 'BigBomb': if prize_location.item.name not in ['Crystal 5', 'Crystal 6']: @@ -272,7 +272,7 @@ def analyze_dungeon(key_layout, world, player): key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) key_logic.bk_chests.update(find_big_key_locked_locations(key_layout.all_chest_locations)) key_logic.prize_location = dungeon_table[key_layout.sector.name].prize - if world.retro[player] and world.mode[player] != 'standard': + if world.keyshuffle[player] == 'universal' and world.mode[player] != 'standard': return original_key_counter = find_counter({}, False, key_layout, False) @@ -923,7 +923,7 @@ def self_lock_possible(counter): def available_chest_small_keys(key_counter, world, player): - if not world.keyshuffle[player] and not world.retro[player]: + if world.keyshuffle[player] == 'none': cnt = 0 for loc in key_counter.free_locations: if key_counter.big_key_opened or '- Big Chest' not in loc.name: @@ -934,7 +934,7 @@ def available_chest_small_keys(key_counter, world, player): def available_chest_small_keys_logic(key_counter, world, player, sm_restricted): - if not world.keyshuffle[player] and not world.retro[player]: + if world.keyshuffle[player] == 'none': cnt = 0 for loc in key_counter.free_locations: if loc not in sm_restricted and (key_counter.big_key_opened or '- Big Chest' not in loc.name): @@ -1446,8 +1446,9 @@ def validate_bk_layout(proposal, builder, start_regions, world, player): # Soft lock stuff def validate_key_layout(key_layout, world, player): - # retro is all good - except for hyrule castle in standard mode - if (world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) or world.logic[player] == 'nologic': + # universal key is all good - except for hyrule castle in standard mode + if world.logic[player] == 'nologic' or (world.keyshuffle[player] == 'universal' and + (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')): return True flat_proposal = key_layout.flat_prop state = ExplorationState(dungeon=key_layout.sector.name) @@ -1575,8 +1576,8 @@ def enough_small_locations(state, avail_small_loc): def determine_prize_lock(key_layout, world, player): - if ((world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) - or world.logic[player] == 'nologic'): + if world.logic[player] == 'nologic' or (world.keyshuffle[player] == 'universal' and + (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')): return # done, doesn't matter what flat_proposal = key_layout.flat_prop state = ExplorationState(dungeon=key_layout.sector.name) @@ -1608,7 +1609,7 @@ def determine_prize_lock(key_layout, world, player): def cnt_avail_small_locations(free_locations, key_only, state, world, player): - if not world.keyshuffle[player] and not world.retro[player]: + if world.keyshuffle[player] == 'none': bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0 avail_chest_keys = min(free_locations - bk_adj, state.key_locations - key_only) return max(0, avail_chest_keys + key_only - state.used_smalls) @@ -1616,7 +1617,7 @@ def cnt_avail_small_locations(free_locations, key_only, state, world, player): def cnt_avail_small_locations_by_ctr(free_locations, counter, layout, world, player): - if not world.keyshuffle[player] and not world.retro[player]: + if world.keyshuffle[player] == 'none': bk_adj = 1 if counter.big_key_opened and not layout.big_key_special else 0 avail_chest_keys = min(free_locations - bk_adj, layout.max_chests) return max(0, avail_chest_keys + len(counter.key_only_locations) - counter.used_keys) @@ -2059,14 +2060,14 @@ def val_rule(rule, skn, allow=False, loc=None, askn=None, setCheck=None): # Soft lock stuff def validate_key_placement(key_layout, world, player): - if world.retro[player] or world.accessibility[player] == 'none': + if world.keyshuffle[player] == 'universal' or world.accessibility[player] == 'none': return True # Can't keylock in retro. Expected if beatable only. max_counter = find_max_counter(key_layout) keys_outside = 0 big_key_outside = False smallkey_name = dungeon_keys[key_layout.sector.name] bigkey_name = dungeon_bigs[key_layout.sector.name] - if world.keyshuffle[player]: + if world.keyshuffle[player] != 'none': keys_outside = key_layout.max_chests - sum(1 for i in max_counter.free_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player) if world.bigkeyshuffle[player]: max_counter = find_max_counter(key_layout) diff --git a/Main.py b/Main.py index 1c6cc442..b1f94d6b 100644 --- a/Main.py +++ b/Main.py @@ -75,7 +75,7 @@ def main(args, seed=None, fish=None): customized.adjust_args(args) world = World(args.multi, args.shuffle, args.door_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.retro, args.custom, args.customitemarray, args.hints) + args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints) world.customizer = customized if customized else None logger = logging.getLogger('') if seed is None: @@ -96,6 +96,7 @@ def main(args, seed=None, fish=None): world.bigkeyshuffle = args.bigkeyshuffle.copy() world.bombbag = args.bombbag.copy() world.flute_mode = args.flute_mode.copy() + world.bow_mode = args.bow_mode.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.crystals_ganon_orig = args.crystals_ganon.copy() @@ -124,11 +125,13 @@ def main(args, seed=None, fish=None): world.shuffletavern = args.shuffletavern.copy() world.pseudoboots = args.pseudoboots.copy() world.overworld_map = args.overworld_map.copy() + world.take_any = args.take_any.copy() world.restrict_boss_items = args.restrict_boss_items.copy() world.collection_rate = args.collection_rate.copy() world.colorizepots = args.colorizepots.copy() world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} + world.finish_init() logger.info( world.fish.translate("cli","cli","app.title") + "\n", @@ -247,7 +250,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): if world.shopsanity[player]: sell_potions(world, player) - if world.retro[player]: + if world.keyshuffle[player] == 'universal': sell_keys(world, player) else: lock_shop_locations(world, player) @@ -425,7 +428,7 @@ def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.shuffle, world.doorShuffle, 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.retro, world.custom, world.customitemarray, world.hints) + world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints) ret.teams = world.teams ret.player_names = copy.deepcopy(world.player_names) ret.remote_items = world.remote_items.copy() diff --git a/PotShuffle.py b/PotShuffle.py index 2848dfe8..fd59f6cc 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -879,7 +879,8 @@ def shuffle_pots(world, player): elif old_pot.item == PotItem.Switch: available_pots = (pot for pot in new_pots if (pot.room == old_pot.room or pot.room in movable_switch_rooms[old_pot.room]) and not (pot.flags & PotFlags.NoSwitch)) elif old_pot.item == PotItem.Key: - if world.doorShuffle[player] == 'vanilla' and not world.retro[player] and world.pottery[player] == 'none' and world.logic[player] != 'nologic': + if (world.doorShuffle[player] == 'vanilla' and world.keyshuffle[player] != 'universal' + and world.pottery[player] == 'none' and world.logic[player] != 'nologic'): available_pots = (pot for pot in new_pots if pot.room not in invalid_key_rooms) else: available_pots = new_pots @@ -890,7 +891,7 @@ def shuffle_pots(world, player): new_pot = random.choice(available_pots) new_pot.item = old_pot.item - if world.retro[player] and new_pot.item == PotItem.FiveArrows: + if world.bow_mode[player].startswith('retro') and new_pot.item == PotItem.FiveArrows: new_pot.item = PotItem.FiveRupees if new_pot.item == PotItem.Key: @@ -938,7 +939,7 @@ def shuffle_pot_switches(world, player): new_pot = random.choice(available_pots) new_pot.item, old_pot.item = old_pot.item, new_pot.item - if world.retro[player] and new_pot.item == PotItem.FiveArrows: + if world.bow_mode[player].startswith('retro') and new_pot.item == PotItem.FiveArrows: new_pot.item = PotItem.FiveRupees if new_pot.item == PotItem.Switch and (new_pot.flags & PotFlags.SwitchLogicChange): diff --git a/Regions.py b/Regions.py index d0ff68ab..3e288727 100644 --- a/Regions.py +++ b/Regions.py @@ -1029,7 +1029,7 @@ def adjust_locations(world, player): loc.event = False item_dungeon = key_item.dungeon dungeon = world.get_dungeon(item_dungeon, player) - if key_item.smallkey and not world.retro[player]: + if key_item.smallkey and world.keyshuffle[player] != 'universal': dungeon.small_keys.append(key_item) elif key_item.bigkey: dungeon.big_key = key_item diff --git a/Rom.py b/Rom.py index 458e24df..544f2c5b 100644 --- a/Rom.py +++ b/Rom.py @@ -797,7 +797,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x138002, 2) for name, layout in world.key_layout[player].items(): offset = compass_data[name][4]//2 - if world.retro[player]: + if world.keyshuffle[player] == 'universal': rom.write_byte(0x13f030+offset, layout.max_chests + layout.max_drops) else: rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used @@ -898,7 +898,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_byte(0x142A50, 1) # StandingItemsOn multiClientFlags = ((0x1 if world.dropshuffle[player] else 0) | (0x2 if world.shopsanity[player] else 0) - | (0x4 if world.retro[player] else 0) + | (0x4 if world.take_any[player] != 'none' else 0) | (0x8 if world.pottery[player] != 'none' else 0) | (0x10 if is_mystery else 0)) rom.write_byte(0x142A51, multiClientFlags) @@ -1112,7 +1112,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): prizes = [prize_replacements.get(prize, prize) for prize in prizes] dig_prizes = [prize_replacements.get(prize, prize) for prize in dig_prizes] - if world.retro[player]: + if world.bow_mode[player].startswith('retro'): prize_replacements = {0xE1: 0xDA, #5 Arrows -> Blue Rupee 0xE2: 0xDB} #10 Arrows -> Red Rupee prizes = [prize_replacements.get(prize, prize) for prize in prizes] @@ -1155,7 +1155,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees 0x51, 0x00 if world.bombbag[player] else 0x06, 0x31 if world.bombbag[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bombbag -> turns into Bombs (10) 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade - 0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) + 0x58, 0x01, 0x36 if world.bow_mode[player].startswith('retro') else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel @@ -1204,7 +1204,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): ERtimeincrease = 10 else: ERtimeincrease = 20 - if world.keyshuffle[player] or world.bigkeyshuffle[player] or world.mapshuffle[player]: + if world.keyshuffle[player] != 'none' or world.bigkeyshuffle[player] or world.mapshuffle[player]: ERtimeincrease = ERtimeincrease + 15 if world.clock_mode == 'none': rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode @@ -1337,7 +1337,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # m - enabled for inside maps # c - enabled for inside compasses # s - enabled for inside small keys - rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle[player] else 0x00) + rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle[player] == 'wild' 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 @@ -1403,7 +1403,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): # a - Small Key # enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'none' - rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00) + rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] == 'wild' else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] or enable_menu_map_check else 0x00) | (0x08 if world.compassshuffle[player] else 0x00))) # free roaming items in menu @@ -1434,17 +1434,17 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): 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[player] else 0x00) # universal keys - rom.write_byte(0x180175, 0x01 if world.retro[player] else 0x00) # rupee bow - rom.write_byte(0x180176, 0x0A if world.retro[player] else 0x00) # wood arrow cost - rom.write_byte(0x180178, 0x32 if world.retro[player] else 0x00) # silver arrow cost - rom.write_byte(0x301FC, 0xDA if world.retro[player] else 0xE1) # rupees replace arrows under pots + rom.write_byte(0x180172, 0x01 if world.keyshuffle[player] == 'universal' else 0x00) # universal keys + rom.write_byte(0x180175, 0x01 if world.bow_mode[player].startswith('retro') else 0x00) # rupee bow + rom.write_byte(0x180176, 0x0A if world.bow_mode[player].startswith('retro') else 0x00) # wood arrow cost + rom.write_byte(0x180178, 0x32 if world.bow_mode[player].startswith('retro') else 0x00) # silver arrow cost + rom.write_byte(0x301FC, 0xDA if world.bow_mode[player].startswith('retro') else 0xE1) # rupees replace arrows under pots if enemized: - rom.write_byte(0x1B152e, 0xDA if world.retro[player] else 0xE1) - rom.write_byte(0x30052, 0xDB if world.retro[player] else 0xE2) # replace arrows in fish prize from bottle merchant - rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.retro[player] else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows - rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.retro[player] else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows - rom.write_bytes(0xEDA5, [0x35, 0x41] if world.retro[player] else [0x43, 0x44]) # Chest game gives rupees instead of arrows + rom.write_byte(0x1B152e, 0xDA if world.bow_mode[player].startswith('retro') else 0xE1) + rom.write_byte(0x30052, 0xDB if world.bow_mode[player].startswith('retro') else 0xE2) # replace arrows in fish prize from bottle merchant + rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.bow_mode[player].startswith('retro') else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows + rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.bow_mode[player].startswith('retro') else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows + rom.write_bytes(0xEDA5, [0x35, 0x41] if world.bow_mode[player].startswith('retro') else [0x43, 0x44]) # Chest game gives rupees instead of arrows digging_game_rng = random.randint(1, 30) # set rng for digging game rom.write_byte(0x180020, digging_game_rng) rom.write_byte(0xEFD95, digging_game_rng) @@ -2151,7 +2151,7 @@ def write_strings(rom, world, player, team): # 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[player]: + if world.keyshuffle[player] == 'wild': items_to_hint.extend(SmallKeys) if world.bigkeyshuffle[player]: items_to_hint.extend(BigKeys) diff --git a/Rules.py b/Rules.py index 9d3dad12..5bd27023 100644 --- a/Rules.py +++ b/Rules.py @@ -2072,7 +2072,7 @@ def add_key_logic_rules(world, player): add_rule(big_chest, create_rule(d_logic.bk_name, player)) if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1: set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player)) - if world.retro[player]: + if world.keyshuffle[player] == 'universal': for d_name, layout in world.key_layout[player].items(): for door in layout.flat_prop: if world.mode[player] != 'standard' or not retro_in_hc(door.entrance): diff --git a/mystery_example.yml b/mystery_example.yml index 78b47a95..26fb559f 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -48,6 +48,11 @@ flute_mode: normal: 3 active: 1 + bow_mode: + progressive: 5 + silvers: 5 + retro: 1 + retro_silvers: 1 entrance_shuffle: none: 15 dungeonssimple: 3 @@ -66,9 +71,10 @@ open: 1 inverted: 1 retro: 0 - retro: - on: 1 - off: 4 + take_any: + random: 1 + fixed: 3 + none: 16 goals: ganon: 2 fast_ganon: 2 @@ -86,6 +92,12 @@ mc: 3 mcs: 2 full: 5 + mcu: 1 # map, compass, universal smalls +# for use when you aren't using the dungeon_items above +# smallkey_shuffle: +# standard: 5 +# wild: 1 +# universal: 1 dungeon_counters: on: 5 off: 0 diff --git a/mystery_testsuite.yml b/mystery_testsuite.yml index 364806e6..aca535c3 100644 --- a/mystery_testsuite.yml +++ b/mystery_testsuite.yml @@ -50,7 +50,6 @@ world_state: standard: 1 open: 1 inverted: 1 - retro: 0 retro: on: 1 off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index d25f542b..0074143d 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -48,6 +48,14 @@ "active" ] }, + "bow_mode": { + "choices": [ + "progressive", + "silvers", + "retro", + "retro_silvers" + ] + }, "goal": { "choices": [ "ganon", @@ -266,8 +274,11 @@ "type": "bool" }, "keyshuffle": { - "action": "store_true", - "type": "bool" + "choices": [ + "none", + "wild", + "universal" + ] }, "bigkeyshuffle": { "action": "store_true", @@ -286,6 +297,13 @@ "action": "store_true", "type": "bool" }, + "take_any": { + "choices": [ + "none", + "random", + "fixed" + ] + }, "startinventory": {}, "usestartinventory": { "type": "bool" diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index eab0e34d..a6dcc3ee 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -122,10 +122,17 @@ "Expert: Greatly reduced functionality." ], "flute_mode": [ - "Determine if you need to wake up the bird or not on flute pickup (default: %(default)s)", + "Determines if you need to wake up the bird or not on flute pickup (default: %(default)s)", "Normal: Normal functionality.", "Active: Flute is activated on pickup." ], + "bow_mode": [ + "Determines how the bow acts in the pool (default: %(default)s)", + "Progressive: Two progressive bows placed. First picked up is the bow. Second is silvers.", + "Silvers Separate: Bow and silvers are completely separate items.", + "Retro: Z1 Bow where arrows cost money and the Single Arrow must be bought or found to shoot", + "Retro + Silvers: Bow and silvers are completely separate items." + ], "timer": [ "Select game timer setting. Affects available itempool. (default: %(default)s)", "None: No timer.", @@ -300,6 +307,12 @@ "Keys are universal, shooting arrows costs rupees,", "and a few other little things make this more like Zelda-1. (default: %(default)s)" ], + "take_any": [ + "Take Any caves from Zelda 1 (default: %(default)s)", + "None: No take any caves", + "Random: Take any caves can replace a random set of un-interesting caves. See documentation for full list", + "Fixed: Take any caves will replace certain location. See documentation for full list" + ], "pseudoboots": [ " Players starts with pseudo boots that allow dashing but no item checks (default: %(default)s"], "bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ], "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 5a6e0a9f..9c7f9dfa 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -53,6 +53,9 @@ "randomizer.dungeon.mapshuffle": "Maps", "randomizer.dungeon.compassshuffle": "Compasses", "randomizer.dungeon.smallkeyshuffle": "Small Keys", + "randomizer.dungeon.smallkeyshuffle.standard": "In Dungeon", + "randomizer.dungeon.smallkeyshuffle.wild": "Randomized", + "randomizer.dungeon.smallkeyshuffle.universal": "Universal", "randomizer.dungeon.bigkeyshuffle": "Big Keys", "randomizer.dungeon.keydropshuffle": "Key Drop Shuffle (Legacy)", "randomizer.dungeon.decoupledoors": "Decouple Doors", @@ -155,7 +158,10 @@ "randomizer.entrance.entranceshuffle.dungeonsfull": "Dungeons + Full", "randomizer.entrance.entranceshuffle.dungeonssimple": "Dungeons + Simple", - + "randomizer.entrance.take_any": "Take Any Caves", + "randomizer.entrance.take_any.none": "None", + "randomizer.entrance.take_any.random": "Random", + "randomizer.entrance.take_any.fixed": "Fixed", "randomizer.gameoptions.nobgm": "Disable Music & MSU-1", "randomizer.gameoptions.quickswap": "L/R Quickswapping", @@ -219,7 +225,7 @@ "randomizer.generation.rom.dialog.allfiles": "All Files", "randomizer.item.hints": "Include Helpful Hints", - "randomizer.item.retro": "Retro mode (universal keys)", + "randomizer.item.retro": "Retro mode", "randomizer.item.pseudoboots": "Start with Pseudo Boots", "randomizer.item.bombbag": "Bombbag", @@ -289,6 +295,12 @@ "randomizer.item.flute_mode.normal": "Normal", "randomizer.item.flute_mode.active": "Pre-Activated", + "randomizer.item.bow_mode": "Bow Mode", + "randomizer.item.bow_mode.progressive": "Progressive", + "randomizer.item.bow_mode.silvers": "Silvers Separate", + "randomizer.item.bow_mode.retro": "Retro (Progressive)", + "randomizer.item.bow_mode.retro_silvers": "Retro + Silvers", + "randomizer.item.timer": "Timer Setting", "randomizer.item.timer.none": "No Timer", "randomizer.item.timer.display": "Stopwatch", diff --git a/resources/app/gui/randomize/dungeon/keysanity.json b/resources/app/gui/randomize/dungeon/keysanity.json index 49a17237..ffd9bc92 100644 --- a/resources/app/gui/randomize/dungeon/keysanity.json +++ b/resources/app/gui/randomize/dungeon/keysanity.json @@ -2,7 +2,6 @@ "keysanity": { "mapshuffle": { "type": "checkbox" }, "compassshuffle": { "type": "checkbox" }, - "smallkeyshuffle": { "type": "checkbox" }, "bigkeyshuffle": { "type": "checkbox" } } } diff --git a/resources/app/gui/randomize/dungeon/widgets.json b/resources/app/gui/randomize/dungeon/widgets.json index df7d3474..02a7f262 100644 --- a/resources/app/gui/randomize/dungeon/widgets.json +++ b/resources/app/gui/randomize/dungeon/widgets.json @@ -1,5 +1,13 @@ { "widgets": { + "keyshuffle": { + "type": "selectbox", + "options": [ + "standard", + "wild", + "universal" + ] + }, "dungeondoorshuffle": { "type": "selectbox", "default": "basic", diff --git a/resources/app/gui/randomize/entrando/widgets.json b/resources/app/gui/randomize/entrando/widgets.json index b4c6698c..a136de91 100644 --- a/resources/app/gui/randomize/entrando/widgets.json +++ b/resources/app/gui/randomize/entrando/widgets.json @@ -29,6 +29,14 @@ "config": { "width": 45 } + }, + "take_any": { + "type": "selectbox", + "options": [ + "none", + "random", + "fixed" + ] } } } diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index b55d6117..88f85858 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -85,6 +85,15 @@ "active" ] }, + "bow_mode": { + "type": "selectbox", + "options": [ + "progressive", + "silvers", + "retro", + "retro_silvers" + ] + }, "timer": { "type": "selectbox", "options": [ diff --git a/source/classes/CustomSettings.py b/source/classes/CustomSettings.py index 82cf0ac9..08150a77 100644 --- a/source/classes/CustomSettings.py +++ b/source/classes/CustomSettings.py @@ -69,11 +69,13 @@ class CustomSettings(object): args.mode[p] = get_setting(settings['mode'], args.mode[p]) args.swords[p] = get_setting(settings['swords'], args.swords[p]) args.flute_mode[p] = get_setting(settings['flute_mode'], args.flute_mode[p]) + args.bow_mode[p] = get_setting(settings['bow_mode'], args.bow_mode[p]) args.item_functionality[p] = get_setting(settings['item_functionality'], args.item_functionality[p]) args.goal[p] = get_setting(settings['goal'], args.goal[p]) args.difficulty[p] = get_setting(settings['difficulty'], args.difficulty[p]) args.accessibility[p] = get_setting(settings['accessibility'], args.accessibility[p]) args.retro[p] = get_setting(settings['retro'], args.retro[p]) + args.take_any[p] = get_setting(settings['take_any'], args.take_any[p]) args.hints[p] = get_setting(settings['hints'], args.hints[p]) args.shopsanity[p] = get_setting(settings['shopsanity'], args.shopsanity[p]) args.dropshuffle[p] = get_setting(settings['dropshuffle'], args.dropshuffle[p]) @@ -191,11 +193,12 @@ class CustomSettings(object): settings_dict[p]['mode'] = world.mode[p] settings_dict[p]['swords'] = world.swords[p] settings_dict[p]['flute_mode'] = world.flute_mode[p] + settings_dict[p]['bow_mode'] = world.bow_mode[p] settings_dict[p]['difficulty'] = world.difficulty[p] settings_dict[p]['goal'] = world.goal[p] settings_dict[p]['accessibility'] = world.accessibility[p] settings_dict[p]['item_functionality'] = world.difficulty_adjustments[p] - settings_dict[p]['retro'] = world.retro[p] + settings_dict[p]['take_any'] = world.take_any[p] settings_dict[p]['hints'] = world.hints[p] settings_dict[p]['shopsanity'] = world.shopsanity[p] settings_dict[p]['dropshuffle'] = world.dropshuffle[p] diff --git a/source/classes/constants.py b/source/classes/constants.py index 3fb8da88..1f801e33 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -68,6 +68,7 @@ SETTINGSTOPROCESS = { "weapons": "swords", "itempool": "difficulty", "flute_mode": "flute_mode", + "bow_mode": "bow_mode", "timer": "timer", "accessibility": "accessibility", "sortingalgo": "algorithm", @@ -81,6 +82,7 @@ SETTINGSTOPROCESS = { "shuffletavern": "shuffletavern", "entranceshuffle": "shuffle", "overworld_map": "overworld_map", + "take_any": "take_any", }, "enemizer": { "enemyshuffle": "shuffleenemies", diff --git a/source/gui/bottom.py b/source/gui/bottom.py index b887e1ac..27a22217 100644 --- a/source/gui/bottom.py +++ b/source/gui/bottom.py @@ -285,4 +285,12 @@ def create_guiargs(parent): guiargs.dropshuffle = 1 guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery + if guiargs.retro or guiargs.mode == 'retro': + if guiargs.bow_mode == 'progressive': + guiargs.bow_mode = 'retro' + elif guiargs.bow_mode == 'silvers': + guiargs.bow_mode = 'retro_silvers' + guiargs.take_any = 'random' if guiargs.take_any == 'none' else guiargs.take_any + guiargs.keyshuffle = 'universal' + return guiargs diff --git a/source/item/FillUtil.py b/source/item/FillUtil.py index 6f60b72e..8cb74805 100644 --- a/source/item/FillUtil.py +++ b/source/item/FillUtil.py @@ -82,10 +82,11 @@ def create_item_pool_config(world): if world.shopsanity[player]: for item, locs in shop_vanilla_mapping.items(): config.static_placement[player][item].extend(locs) - if world.retro[player]: + if world.take_any[player] != 'none': for item, locs in retro_vanilla_mapping.items(): config.static_placement[player][item].extend(locs) # universal keys + if world.keyshuffle[player] == 'universal': universal_key_locations = [] for item, locs in vanilla_mapping.items(): if 'Small Key' in item: @@ -98,12 +99,13 @@ def create_item_pool_config(world): for item, locs in potkeys_vanilla_mapping.items(): universal_key_locations.extend(locs) if world.shopsanity[player]: - single_arrow_placement = list(shop_vanilla_mapping['Red Potion']) - single_arrow_placement.append('Red Shield Shop - Right') - config.static_placement[player]['Single Arrow'] = single_arrow_placement universal_key_locations.extend(shop_vanilla_mapping['Small Heart']) universal_key_locations.extend(shop_vanilla_mapping['Blue Shield']) config.static_placement[player]['Small Key (Universal)'] = universal_key_locations + if world.bow_mode[player].startswith('retro') and world.shopsanity[player]: + single_arrow_placement = list(shop_vanilla_mapping['Red Potion']) + single_arrow_placement.append('Red Shield Shop - Right') + config.static_placement[player]['Single Arrow'] = single_arrow_placement config.location_groups[player] = [ LocationGroup('Major').locs(mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']), LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']), @@ -124,7 +126,7 @@ def create_item_pool_config(world): groups.locations.extend(mode_grouping['Big Keys']) if world.dropshuffle[player] != 'none': groups.locations.extend(mode_grouping['Big Key Drops']) - if world.keyshuffle[player]: + if world.keyshuffle[player] != 'none': groups.locations.extend(mode_grouping['Small Keys']) if world.dropshuffle[player] != 'none': groups.locations.extend(mode_grouping['Key Drops']) @@ -137,7 +139,7 @@ def create_item_pool_config(world): if world.shopsanity[player]: groups.locations.append('Capacity Upgrade - Left') groups.locations.append('Capacity Upgrade - Right') - if world.retro[player]: + if world.take_any[player] != 'none': if world.shopsanity[player]: groups.locations.extend(retro_vanilla_mapping['Heart Container']) groups.locations.append('Old Man Sword Cave Item 1') @@ -249,7 +251,7 @@ def previously_reserved(location, world, player): if world.restrict_boss_items[player] == 'dungeon' and (not world.compassshuffle[player] or not world.mapshuffle[player] or not world.bigkeyshuffle[player] - or not (world.keyshuffle[player] or world.retro[player])): + or world.keyshuffle[player] == 'standard'): return True return False @@ -335,7 +337,7 @@ def determine_major_items(world, player): pass # now what? if world.bigkeyshuffle[player]: major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'}) - if world.keyshuffle[player]: + if world.keyshuffle[player] != 'none': major_item_set.update({x for x, y in item_table.items() if y[2] == 'SmallKey'}) if world.compassshuffle[player]: major_item_set.update({x for x, y in item_table.items() if y[2] == 'Compass'}) @@ -344,8 +346,9 @@ def determine_major_items(world, player): if world.shopsanity[player]: major_item_set.add('Bomb Upgrade (+5)') major_item_set.add('Arrow Upgrade (+5)') - if world.retro[player]: + if world.bow_mode[player].startswith('retro'): major_item_set.add('Single Arrow') + if world.keyshuffle[player] == 'universal': major_item_set.add('Small Key (Universal)') if world.goal == 'triforcehunt': major_item_set.add('Triforce Piece') diff --git a/source/tools/MysteryUtils.py b/source/tools/MysteryUtils.py index ae628b7c..38ee8c1a 100644 --- a/source/tools/MysteryUtils.py +++ b/source/tools/MysteryUtils.py @@ -64,7 +64,13 @@ def roll_settings(weights): dungeon_items = 'mcsb' if dungeon_items == 'full' else dungeon_items ret.mapshuffle = get_choice('map_shuffle') == 'on' if 'map_shuffle' in weights else 'm' in dungeon_items ret.compassshuffle = get_choice('compass_shuffle') == 'on' if 'compass_shuffle' in weights else 'c' in dungeon_items - ret.keyshuffle = get_choice('smallkey_shuffle') == 'on' if 'smallkey_shuffle' in weights else 's' in dungeon_items + if 'smallkey_shuffle' in weights: + ret.keyshuffle = get_choice('smallkey_shuffle') + else: + if 's' in dungeon_items: + ret.keyshuffle = 'wild' + if 'u' in dungeon_items: + ret.keyshuffle = 'universal' ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else 'b' in dungeon_items ret.accessibility = get_choice('accessibility') @@ -130,6 +136,7 @@ def roll_settings(weights): ret.mode = 'open' ret.retro = True ret.retro = get_choice('retro') == 'on' # this overrides world_state if used + ret.take_any = get_choice_default('take_any', default='none') ret.bombbag = get_choice('bombbag') == 'on' @@ -145,6 +152,7 @@ def roll_settings(weights): ret.difficulty = get_choice('item_pool') ret.flute_mode = get_choice_default('flute_mode', default='normal') + ret.bow_mode = get_choice_default('bow_mode', default='progressive') ret.item_functionality = get_choice('item_functionality')