diff --git a/BaseClasses.py b/BaseClasses.py index 9acbaeaf..0c2925e5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1658,7 +1658,8 @@ class Entrance(object): from OWEdges import OWTileRegions from OverworldShuffle import ow_connections owid = OWTileRegions[follower_region.name] - (mirror_map, other_world) = ow_connections[owid % 0x40] + (mirror_map_orig, other_world) = ow_connections[owid % 0x40] + mirror_map = list(mirror_map_orig).copy() mirror_map.extend(other_world) mirror_exit = None while len(mirror_map): @@ -1710,7 +1711,8 @@ class Entrance(object): from OWEdges import OWTileRegions from OverworldShuffle import ow_connections owid = OWTileRegions[dest_region.name] - (mirror_map, other_world) = ow_connections[owid % 0x40] + (mirror_map_orig, other_world) = ow_connections.copy()[owid % 0x40] + mirror_map = list(mirror_map_orig).copy() mirror_map.extend(other_world) mirror_map = [(x, d) for (x, d) in mirror_map if x in [e.name for e in dest_region.exits]] # loop thru potential places to leave a mirror portal @@ -2746,12 +2748,10 @@ class Spoiler(object): self.overworlds[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) def set_map(self, type, text, data, player): - if type not in self.maps: - self.maps[type] = {} if self.world.players == 1: - self.maps[type][player] = OrderedDict([('text', text), ('data', data)]) + self.maps[(type, player)] = OrderedDict([('type', type), ('text', text), ('data', data)]) else: - self.maps[type][player] = OrderedDict([('player', player), ('text', text), ('data', data)]) + self.maps[(type, player)] = OrderedDict([('player', player), ('type', type), ('text', text), ('data', data)]) def set_entrance(self, entrance, exit, direction, player): if self.world.players == 1: @@ -3032,12 +3032,15 @@ class Spoiler(object): if self.overworlds: outfile.write('\n\nOverworld:\n\n') # overworld tile swaps - if 'swaps' in self.maps: - outfile.write('OW Tile Swaps:\n') - for player in self.maps['swaps']: + for player in range(1, self.world.players + 1): + if ('swaps', player) in self.maps: + outfile.write('OW Tile Swaps:\n') + break + for player in range(1, self.world.players + 1): + if ('swaps', player) in self.maps: if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name - outfile.write(self.maps['swaps'][player]['text'] + '\n\n') + outfile.write(self.maps[('swaps', player)]['text'] + '\n\n') # overworld transitions outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()])) diff --git a/Bosses.py b/Bosses.py index 90acafcf..d577c66d 100644 --- a/Bosses.py +++ b/Bosses.py @@ -32,7 +32,7 @@ def LanmolasDefeatRule(state, player): state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or - state.has('Cane of Byrna', player) or + (state.has('Cane of Byrna', player) and state.can_use_bombs(player)) or state.can_shoot_arrows(player)) def MoldormDefeatRule(state, player): @@ -51,7 +51,7 @@ def ArrghusDefeatRule(state, player): return True return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot - (state.has('Ice Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16)))) + (state.has('Ice Rod', player) and state.can_use_bombs(player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16)))) def MothulaDefeatRule(state, player): @@ -92,7 +92,7 @@ def KholdstareDefeatRule(state, player): ) def VitreousDefeatRule(state, player): - return state.can_shoot_arrows(player) or state.has_blunt_weapon(player) + return (state.can_shoot_arrows(player) and state.can_use_bombs(player)) or state.has_blunt_weapon(player) def TrinexxDefeatRule(state, player): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): diff --git a/CHANGELOG.md b/CHANGELOG.md index e5860f08..dd9cae91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +### 0.2.3.4 +- Fixed major issue with subsequent seeds using same seed/settings resulting different +- Flute Shuffle now awards separated regions a prorated number of flute spots based on size +- Fixed spoiler log, was missing OW Tile Swap map +- Fixed spoiler log JSON output +- Fake flipper damage fix improved to skip the long delay after the scroll +- Fixed missing Blue Potion in Lake Shop in Inverted +- Added legacy OW Crossed option 'None (Allowed)' to support old behavior when invalid option was used in Mystery +- ~~Merged DR v0.5.1.6 - Money balancing fix/Boss logic fixes with Bombbag~~ + ### 0.2.3.3 - Added OW Layout validation that reduces the cases where some screens are unreachable - Fixed issue with mirror portals showing up in DW in Crossed OW diff --git a/DoorShuffle.py b/DoorShuffle.py index 6e80d19d..7964755e 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1820,7 +1820,7 @@ def find_inaccessible_regions(world, player): else: start_regions = ['Links House', 'Dark Sanctuary Hint'] regs = convert_regions(start_regions, world, player) - all_regions = set([r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon]) + all_regions = [r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon] visited_regions = set() queue = deque(regs) while len(queue) > 0: @@ -1836,7 +1836,7 @@ def find_inaccessible_regions(world, player): if connect and connect not in queue and connect not in visited_regions: if connect.type is not RegionType.Dungeon or connect.name.endswith(' Portal'): queue.append(connect) - world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)]) + world.inaccessible_regions[player].extend([r.name for r in all_regions if r not in visited_regions and valid_inaccessible_region(r)]) if (world.mode[player] == 'inverted') != (0x1b in world.owswaps[player][0] and world.owMixed[player]): ledge = world.get_region('Hyrule Castle Ledge', player) if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'): diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 1017e6d7..2b51eaaf 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1,5 +1,5 @@ import logging -from collections import defaultdict +from collections import defaultdict, OrderedDict import RaceRandom as random from BaseClasses import CollectionState, RegionType from OverworldShuffle import build_accessible_region_list @@ -432,21 +432,21 @@ def link_entrances(world, player): connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (dw_entrances if not invFlag else lw_entrances)] connect_inaccessible_regions(world, [], connector_entrances, caves, player) if invFlag: - lw_dungeons = list(set(lw_dungeons) & set(caves)) + lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) else: - dw_dungeons = list(set(dw_dungeons) & set(caves)) + dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) - caves = list(set(Cave_Base) & set(caves)) + (lw_dungeons if not invFlag else dw_dungeons) + caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + (lw_dungeons if not invFlag else dw_dungeons) connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (lw_entrances if not invFlag else dw_entrances)] connect_inaccessible_regions(world, connector_entrances, [], caves, player) if not invFlag: - lw_dungeons = list(set(lw_dungeons) & set(caves)) + lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) else: - dw_dungeons = list(set(dw_dungeons) & set(caves)) + dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) lw_dungeons = lw_dungeons + (Old_Man_House if not invFlag else []) dw_dungeons = dw_dungeons + ([] if not invFlag else Old_Man_House) - caves = list(set(Cave_Base) & set(caves)) + DW_Mid_Dungeon_Exits + caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + DW_Mid_Dungeon_Exits # place old man, has limited options lw_entrances = [e for e in lw_entrances if e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] and e in entrance_pool] @@ -1429,10 +1429,10 @@ def place_blacksmith(world, links_house, player): if invFlag: dark_sanc = world.get_entrance('Dark Sanctuary Hint Exit', player).connected_region.name - blacksmith_doors = list(set(blacksmith_doors + list(build_accessible_entrance_list(world, dark_sanc, player, assumed_inventory, False, True, True)))) + blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, dark_sanc, player, assumed_inventory, False, True, True)))) elif world.doorShuffle[player] == 'vanilla' or world.intensity[player] < 3: sanc_region = world.get_entrance('Sanctuary Exit', player).connected_region.name - blacksmith_doors = list(set(blacksmith_doors + list(build_accessible_entrance_list(world, sanc_region, player, assumed_inventory, False, True, True)))) + blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, sanc_region, player, assumed_inventory, False, True, True)))) if world.shuffle[player] in ['lite', 'lean']: blacksmith_doors = [e for e in blacksmith_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []))))[0]] @@ -1491,7 +1491,7 @@ def junk_fill_inaccessible(world, player): accessible_regions.append(region_name) break for region_name in accessible_regions.copy(): - accessible_regions = list(set(accessible_regions + list(build_accessible_region_list(base_world, region_name, player, False, True, False, False)))) + accessible_regions = list(OrderedDict.fromkeys(accessible_regions + list(build_accessible_region_list(base_world, region_name, player, False, True, False, False)))) world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions] # get inaccessible entrances @@ -1527,7 +1527,7 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe accessible_regions.append(region_name) break for region_name in accessible_regions.copy(): - accessible_regions = list(set(accessible_regions + list(build_accessible_region_list(world, region_name, player, True, True, False, False)))) + accessible_regions = list(OrderedDict.fromkeys(accessible_regions + list(build_accessible_region_list(world, region_name, player, True, True, False, False)))) world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions] # split inaccessible into 2 lists for each world @@ -1665,12 +1665,12 @@ def build_accessible_entrance_list(world, start_region, player, assumed_inventor new_regions.append(ledge) explored_regions.extend(new_regions) - entrances = set() + entrances = list() for region_name in explored_regions: region = base_world.get_region(region_name, player) for exit in region.exits: if exit.name in entrance_pool and (not exit_rules or exit.access_rule(blank_state)): - entrances.add(exit.name) + entrances.append(exit.name) return entrances @@ -1798,7 +1798,7 @@ Cave_Three_Exits_Base = [('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cav Old_Man_House_Base = [('Old Man House Exit (Bottom)', 'Old Man House Exit (Top)')] -Entrance_Pool_Base = {'Links House', +Entrance_Pool_Base = ['Links House', 'Desert Palace Entrance (South)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (East)', @@ -1936,9 +1936,9 @@ Entrance_Pool_Base = {'Links House', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (North)', - 'Pyramid Hole'} + 'Pyramid Hole'] -Exit_Pool_Base = {'Links House Exit', +Exit_Pool_Base = ['Links House Exit', 'Desert Palace Exit (South)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', @@ -2076,7 +2076,7 @@ Exit_Pool_Base = {'Links House Exit', 'Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle', - 'Pyramid'} + 'Pyramid'] # these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions mandatory_connections = [('Links House S&Q', 'Links House'), diff --git a/Fill.py b/Fill.py index 7deb6fcc..8cfefdb3 100644 --- a/Fill.py +++ b/Fill.py @@ -762,9 +762,12 @@ def balance_money_progression(world): if sphere_costs[player] > 0 and sphere_costs[player] > wallet[player]: insolvent.add(player) if len([p for p in solvent if len(locked_by_money[p]) > 0]) == 0: - target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p]) - difference = sphere_costs[target_player]-wallet[target_player] - logger.debug(f'Money balancing needed: Player {target_player} short {difference}') + if len(insolvent) > 0: + target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p]) + difference = sphere_costs[target_player]-wallet[target_player] + logger.debug(f'Money balancing needed: Player {target_player} short {difference}') + else: + difference = 0 while difference > 0: swap_targets = [x for x in unchecked_locations if x not in sphere_locations and x.item.name.startswith('Rupees') and x.item.player == target_player] if len(swap_targets) == 0: diff --git a/Main.py b/Main.py index 40e73b57..a4dc5d68 100644 --- a/Main.py +++ b/Main.py @@ -30,7 +30,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.5.1.5-u' +__version__ = '0.5.1.6-u' from source.classes.BabelFish import BabelFish @@ -175,6 +175,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_overworld(world, player) + create_shops(world, player) update_world_regions(world, player) create_flute_exits(world, player) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d6c1e331..04f2d575 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1,9 +1,10 @@ import RaceRandom as random, logging, copy +from collections import OrderedDict from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from Regions import mark_dark_world_regions, mark_light_world_regions from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel -__version__ = '0.2.3.3-u' +__version__ = '0.2.3.4-u' def link_overworld(world, player): # setup mandatory connections @@ -265,6 +266,8 @@ def link_overworld(world, player): assert len(forward_set) == len(back_set) for (forward_edge, back_edge) in zip(forward_set, back_set): connect_two_way(world, forward_edge, back_edge, player, connected_edges) + + world.owsectors[player] = build_sectors(world, player) else: if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']: for exitname, destname in parallelsimilar_connections: @@ -334,6 +337,7 @@ def link_overworld(world, player): assert valid_layout, 'Could not find a valid OW layout' # flute shuffle + logging.getLogger('').debug('Shuffling flute spots') def connect_flutes(flute_destinations): for o in range(0, len(flute_destinations)): owslot = flute_destinations[o] @@ -376,23 +380,37 @@ def link_overworld(world, player): flute_pool.remove(owid) new_spots.append(owid) return True + + # determine sectors (isolated groups of regions) to place flute spots + flute_regions = {(f[0][0] if f[1] not in world.owswaps[player][0] else f[0][1]) : o for o, f in flute_data.items()} + flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]] + flute_sectors = [s for s in flute_sectors if len(s[1]) > 0] + region_total = sum([c for c,_ in flute_sectors]) + sector_total = len(flute_sectors) - # guarantee desert/mire access - addSpot(0x38) + # reserve a number of flute spots for each sector + flute_spots = 8 + for sector in flute_sectors: + sector_total -= 1 + spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) + target_spots = len(new_spots) + spots_to_place + + if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]: + addSpot(0x38) # guarantee desert/mire access - # guarantee mountain access - if world.owShuffle[player] == 'vanilla': - mountainIds = [0x0b, 0x0e, 0x07] - addSpot(mountainIds[random.randint(0, 2)]) + random.shuffle(sector[1]) + f = 0 + while len(new_spots) < target_spots: + if f >= len(sector[1]): + f = 0 + if sector[1][f] not in new_spots: + addSpot(flute_regions[sector[1][f]]) + f += 1 - random.shuffle(flute_pool) - f = 0 - while len(new_spots) < 8: - if f >= len(flute_pool): - f = 0 - if flute_pool[f] not in new_spots: - addSpot(flute_pool[f]) - f += 1 + region_total -= sector[0] + flute_spots -= spots_to_place + + # connect new flute spots new_spots.sort() world.owflutespots[player] = new_spots connect_flutes(new_spots) @@ -483,7 +501,7 @@ def shuffle_tiles(world, groups, result_list, player): exist_dw_regions.extend(dw_regions) # check whirlpool parity - valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len(set(new_results[0]) & set({0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f})) % 2 == 0 + valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len(OrderedDict.fromkeys(new_results[0] + [0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f])) % 2 == 0 (exist_owids, exist_lw_regions, exist_dw_regions) = result_list exist_owids.extend(new_results[0]) @@ -792,7 +810,7 @@ def can_reach_smith(world, player): def explore_region(region_name, region=None): nonlocal found - explored_regions.add(region_name) + explored_regions.append(region_name) if not found: if not region: region = world.get_region(region_name, player) @@ -821,7 +839,7 @@ def can_reach_smith(world, player): blank_state.collect(ItemFactory('Titans Mitts', player), True) found = False - explored_regions = set() + explored_regions = list() explore_region('Links House') if not found: if not invFlag: @@ -850,7 +868,7 @@ def build_sectors(world, player): if (any(r in unique_regions for r in explored_regions)): for s in range(len(sectors)): if (any(r in sectors[s] for r in explored_regions)): - sectors[s] = set(list(sectors[s]) + list(explored_regions)) + sectors[s] = list(list(sectors[s]) + list(explored_regions)) break else: sectors.append(explored_regions) @@ -876,7 +894,7 @@ def build_sectors(world, player): if (any(r in unique_regions for r in explored_regions)): for s2 in range(len(sectors2)): if (any(r in sectors2[s2] for r in explored_regions)): - sectors2[s2] = set(list(sectors2[s2]) + list(explored_regions)) + sectors2[s2] = list(sectors2[s2] + explored_regions) break else: sectors2.append(explored_regions) @@ -890,7 +908,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F from Items import ItemFactory def explore_region(region_name, region=None): - explored_regions.add(region_name) + explored_regions.append(region_name) if not region: region = base_world.get_region(region_name, player) for exit in region.exits: @@ -919,7 +937,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F blank_state = CollectionState(base_world) if base_world.mode[player] == 'standard': blank_state.collect(ItemFactory('Zelda Delivered', player), True) - explored_regions = set() + explored_regions = list() explore_region(start_region) return explored_regions diff --git a/README.md b/README.md index 5eb15aea..bfd37cb6 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ OW Transitions are shuffled within each world separately. This allows OW connections to be shuffled cross-world. +'None (Allowed)' allows entrance connectors and whirlpools to result in cross-world behavior, but edge transitions will not. This isn't a recommended option. + Polar and Grouped both are guaranteed to result in two separated planes of tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a LW tile that was previously unreachable Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c1a7a62c..eaa45e2b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,10 @@ CLI: ```--bombbag``` # Bug Fixes and Notes. +* 0.5.1.6 + * Rules fixes for TT (Boss and Cell) can now have TT Big Key if not otherwise required (boss shuffle + crossed dungeon) + * BUg fix for money balancing + * Add some bomb assumptions for bosses in bombbag mode * 0.5.1.5 * Fix for hard pool capacity upgrades missing * Bonk Fairy (Light) is no longer in logic for ER Standard and is forbidden to be a connector, so rain state isn't exitable diff --git a/Regions.py b/Regions.py index b4a48d73..f1d372cf 100644 --- a/Regions.py +++ b/Regions.py @@ -1121,9 +1121,12 @@ def mark_dark_world_regions(world, player): def create_shops(world, player): world.shops[player] = [] for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram) in shop_table.items(): - if (world.mode[player] == 'inverted') != (0x35 in world.owswaps[player][0] and world.owMixed[player]) and region_name == 'Dark Lake Hylia Shop': - locked = True - inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] + if world.mode[player] == 'inverted': + if (0x35 not in world.owswaps[player][0] and region_name == 'Dark Lake Hylia Shop') \ + or (0x35 in world.owswaps[player][0] and region_name == 'Cave Shop (Lake Hylia)'): + locked = True + custom = True + inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] region = world.get_region(region_name, player) shop = Shop(region, room_id, type, shopkeeper, custom, locked, sram) region.shop = shop diff --git a/Rom.py b/Rom.py index 2fe1bdfa..6c57d091 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '607ec23701b1099fdeb64a0caff190b6' +RANDOMIZERBASEHASH = 'c5903112ffe302d1e9a3878ee720d1b3' class JsonRom(object): diff --git a/Rules.py b/Rules.py index efadaf6e..149c7d36 100644 --- a/Rules.py +++ b/Rules.py @@ -307,9 +307,10 @@ def global_rules(world, player): set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: state.has('Hammer', player)) for entrance in ['Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Block Path', 'Thieves Conveyor Bridge Block Path']: set_rule(world.get_entrance(entrance, player), lambda state: state.can_lift_rocks(player)) - for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: - forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player) - forbid_item(world.get_location('Thieves\' Town - Blind\'s Cell', player), 'Big Key (Thieves Town)', player) + + # I think these rules are unnecessary now - testing needed + # for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: + # forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player) for location in ['Suspicious Maiden', 'Thieves\' Town - Blind\'s Cell']: set_rule(world.get_location(location, player), lambda state: state.has('Big Key (Thieves Town)', player)) set_rule(world.get_location('Revealing Light', player), lambda state: state.has('Shining Light', player) and state.has('Maiden Rescued', player)) @@ -717,7 +718,7 @@ def default_rules(world, player): # Underworld Logic set_rule(world.get_entrance('Old Man Cave Exit (West)', player), lambda state: False) # drop cannot be climbed up - set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has('Magic Mirror', player)) # can erase block + set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) # can erase block, overwritten in noglitches set_rule(world.get_entrance('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player)) set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)', player), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling diff --git a/data/base2current.bps b/data/base2current.bps index 022ded6a..e229d170 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 365e40e6..8e10afde 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -119,6 +119,7 @@ "ow_crossed": { "choices": [ "none", + "allowed", "polar", "grouped", "limited", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 59d9a9f3..2f5d962d 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -208,6 +208,7 @@ "ow_crossed": [ "This allows cross-world connections to occur on the overworld.", "None: No transitions are cross-world connections.", + "Allowed: Only entrances/whirlpools can end up cross-world.", "Polar: Only used when Mixed is enabled. This retains original", " connections even when overworld tiles are swapped.", "Limited: Exactly nine transitions are randomly chosen as", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 2e69ba8e..a057e5ab 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -119,6 +119,7 @@ "randomizer.overworld.crossed": "Crossed", "randomizer.overworld.crossed.none": "None", + "randomizer.overworld.crossed.allowed": "None (Allowed)", "randomizer.overworld.crossed.polar": "Polar", "randomizer.overworld.crossed.grouped": "Grouped", "randomizer.overworld.crossed.limited": "Limited", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index f6751bc2..43dc9394 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -14,6 +14,7 @@ "default": "vanilla", "options": [ "none", + "allowed", "polar", "grouped", "limited",