Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2021-12-19 20:29:48 -06:00
18 changed files with 115 additions and 66 deletions

View File

@@ -1658,7 +1658,8 @@ class Entrance(object):
from OWEdges import OWTileRegions from OWEdges import OWTileRegions
from OverworldShuffle import ow_connections from OverworldShuffle import ow_connections
owid = OWTileRegions[follower_region.name] 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_map.extend(other_world)
mirror_exit = None mirror_exit = None
while len(mirror_map): while len(mirror_map):
@@ -1710,7 +1711,8 @@ class Entrance(object):
from OWEdges import OWTileRegions from OWEdges import OWTileRegions
from OverworldShuffle import ow_connections from OverworldShuffle import ow_connections
owid = OWTileRegions[dest_region.name] 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.extend(other_world)
mirror_map = [(x, d) for (x, d) in mirror_map if x in [e.name for e in dest_region.exits]] 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 # 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)]) self.overworlds[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
def set_map(self, type, text, data, player): def set_map(self, type, text, data, player):
if type not in self.maps:
self.maps[type] = {}
if self.world.players == 1: 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: 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): def set_entrance(self, entrance, exit, direction, player):
if self.world.players == 1: if self.world.players == 1:
@@ -3032,12 +3032,15 @@ class Spoiler(object):
if self.overworlds: if self.overworlds:
outfile.write('\n\nOverworld:\n\n') outfile.write('\n\nOverworld:\n\n')
# overworld tile swaps # overworld tile swaps
if 'swaps' in self.maps: for player in range(1, self.world.players + 1):
outfile.write('OW Tile Swaps:\n') if ('swaps', player) in self.maps:
for player in self.maps['swaps']: 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: if self.world.players > 1:
outfile.write(str('(Player ' + str(player) + ')\n')) # player name 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 # 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()])) 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()]))

View File

@@ -32,7 +32,7 @@ def LanmolasDefeatRule(state, player):
state.has('Fire Rod', player) or state.has('Fire Rod', player) or
state.has('Ice Rod', player) or state.has('Ice Rod', player) or
state.has('Cane of Somaria', 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)) state.can_shoot_arrows(player))
def MoldormDefeatRule(state, player): def MoldormDefeatRule(state, player):
@@ -51,7 +51,7 @@ def ArrghusDefeatRule(state, player):
return True 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 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): def MothulaDefeatRule(state, player):
@@ -92,7 +92,7 @@ def KholdstareDefeatRule(state, player):
) )
def VitreousDefeatRule(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): def TrinexxDefeatRule(state, player):
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)):

View File

@@ -1,5 +1,15 @@
# Changelog # 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 ### 0.2.3.3
- Added OW Layout validation that reduces the cases where some screens are unreachable - 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 - Fixed issue with mirror portals showing up in DW in Crossed OW

View File

@@ -1820,7 +1820,7 @@ def find_inaccessible_regions(world, player):
else: else:
start_regions = ['Links House', 'Dark Sanctuary Hint'] start_regions = ['Links House', 'Dark Sanctuary Hint']
regs = convert_regions(start_regions, world, player) 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() visited_regions = set()
queue = deque(regs) queue = deque(regs)
while len(queue) > 0: 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 and connect not in queue and connect not in visited_regions:
if connect.type is not RegionType.Dungeon or connect.name.endswith(' Portal'): if connect.type is not RegionType.Dungeon or connect.name.endswith(' Portal'):
queue.append(connect) 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]): if (world.mode[player] == 'inverted') != (0x1b in world.owswaps[player][0] and world.owMixed[player]):
ledge = world.get_region('Hyrule Castle Ledge', 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'): if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'):

View File

@@ -1,5 +1,5 @@
import logging import logging
from collections import defaultdict from collections import defaultdict, OrderedDict
import RaceRandom as random import RaceRandom as random
from BaseClasses import CollectionState, RegionType from BaseClasses import CollectionState, RegionType
from OverworldShuffle import build_accessible_region_list 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)] 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) connect_inaccessible_regions(world, [], connector_entrances, caves, player)
if invFlag: if invFlag:
lw_dungeons = list(set(lw_dungeons) & set(caves)) lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves))
else: 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)] 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) connect_inaccessible_regions(world, connector_entrances, [], caves, player)
if not invFlag: if not invFlag:
lw_dungeons = list(set(lw_dungeons) & set(caves)) lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves))
else: 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 []) lw_dungeons = lw_dungeons + (Old_Man_House if not invFlag else [])
dw_dungeons = dw_dungeons + ([] if not invFlag else Old_Man_House) 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 # 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] 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: if invFlag:
dark_sanc = world.get_entrance('Dark Sanctuary Hint Exit', player).connected_region.name 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: elif world.doorShuffle[player] == 'vanilla' or world.intensity[player] < 3:
sanc_region = world.get_entrance('Sanctuary Exit', player).connected_region.name 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']: 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]] 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) accessible_regions.append(region_name)
break break
for region_name in accessible_regions.copy(): 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] world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions]
# get inaccessible entrances # get inaccessible entrances
@@ -1527,7 +1527,7 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe
accessible_regions.append(region_name) accessible_regions.append(region_name)
break break
for region_name in accessible_regions.copy(): 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] 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 # 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) new_regions.append(ledge)
explored_regions.extend(new_regions) explored_regions.extend(new_regions)
entrances = set() entrances = list()
for region_name in explored_regions: for region_name in explored_regions:
region = base_world.get_region(region_name, player) region = base_world.get_region(region_name, player)
for exit in region.exits: for exit in region.exits:
if exit.name in entrance_pool and (not exit_rules or exit.access_rule(blank_state)): 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 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)')] 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 (South)',
'Desert Palace Entrance (West)', 'Desert Palace Entrance (West)',
'Desert Palace Entrance (East)', 'Desert Palace Entrance (East)',
@@ -1936,9 +1936,9 @@ Entrance_Pool_Base = {'Links House',
'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (West)',
'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (East)',
'Skull Woods First Section Hole (North)', '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 (South)',
'Desert Palace Exit (West)', 'Desert Palace Exit (West)',
'Desert Palace Exit (East)', 'Desert Palace Exit (East)',
@@ -2076,7 +2076,7 @@ Exit_Pool_Base = {'Links House Exit',
'Skull Left Drop', 'Skull Left Drop',
'Skull Pinball', 'Skull Pinball',
'Skull Pot Circle', '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 # 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'), mandatory_connections = [('Links House S&Q', 'Links House'),

View File

@@ -762,9 +762,12 @@ def balance_money_progression(world):
if sphere_costs[player] > 0 and sphere_costs[player] > wallet[player]: if sphere_costs[player] > 0 and sphere_costs[player] > wallet[player]:
insolvent.add(player) insolvent.add(player)
if len([p for p in solvent if len(locked_by_money[p]) > 0]) == 0: 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]) if len(insolvent) > 0:
difference = sphere_costs[target_player]-wallet[target_player] target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p])
logger.debug(f'Money balancing needed: Player {target_player} short {difference}') 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: 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] 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: if len(swap_targets) == 0:

View File

@@ -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 ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
from Utils import output_path, parse_player_names 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 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): for player in range(1, world.players + 1):
link_overworld(world, player) link_overworld(world, player)
create_shops(world, player)
update_world_regions(world, player) update_world_regions(world, player)
create_flute_exits(world, player) create_flute_exits(world, player)

View File

@@ -1,9 +1,10 @@
import RaceRandom as random, logging, copy import RaceRandom as random, logging, copy
from collections import OrderedDict
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
from Regions import mark_dark_world_regions, mark_light_world_regions from Regions import mark_dark_world_regions, mark_light_world_regions
from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel 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): def link_overworld(world, player):
# setup mandatory connections # setup mandatory connections
@@ -265,6 +266,8 @@ def link_overworld(world, player):
assert len(forward_set) == len(back_set) assert len(forward_set) == len(back_set)
for (forward_edge, back_edge) in zip(forward_set, back_set): for (forward_edge, back_edge) in zip(forward_set, back_set):
connect_two_way(world, forward_edge, back_edge, player, connected_edges) connect_two_way(world, forward_edge, back_edge, player, connected_edges)
world.owsectors[player] = build_sectors(world, player)
else: else:
if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']: if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']:
for exitname, destname in parallelsimilar_connections: 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' assert valid_layout, 'Could not find a valid OW layout'
# flute shuffle # flute shuffle
logging.getLogger('').debug('Shuffling flute spots')
def connect_flutes(flute_destinations): def connect_flutes(flute_destinations):
for o in range(0, len(flute_destinations)): for o in range(0, len(flute_destinations)):
owslot = flute_destinations[o] owslot = flute_destinations[o]
@@ -377,22 +381,36 @@ def link_overworld(world, player):
new_spots.append(owid) new_spots.append(owid)
return True return True
# guarantee desert/mire access # determine sectors (isolated groups of regions) to place flute spots
addSpot(0x38) 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 mountain access # reserve a number of flute spots for each sector
if world.owShuffle[player] == 'vanilla': flute_spots = 8
mountainIds = [0x0b, 0x0e, 0x07] for sector in flute_sectors:
addSpot(mountainIds[random.randint(0, 2)]) 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
random.shuffle(flute_pool) if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]:
f = 0 addSpot(0x38) # guarantee desert/mire access
while len(new_spots) < 8:
if f >= len(flute_pool): random.shuffle(sector[1])
f = 0 f = 0
if flute_pool[f] not in new_spots: while len(new_spots) < target_spots:
addSpot(flute_pool[f]) if f >= len(sector[1]):
f += 1 f = 0
if sector[1][f] not in new_spots:
addSpot(flute_regions[sector[1][f]])
f += 1
region_total -= sector[0]
flute_spots -= spots_to_place
# connect new flute spots
new_spots.sort() new_spots.sort()
world.owflutespots[player] = new_spots world.owflutespots[player] = new_spots
connect_flutes(new_spots) connect_flutes(new_spots)
@@ -483,7 +501,7 @@ def shuffle_tiles(world, groups, result_list, player):
exist_dw_regions.extend(dw_regions) exist_dw_regions.extend(dw_regions)
# check whirlpool parity # 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, exist_lw_regions, exist_dw_regions) = result_list
exist_owids.extend(new_results[0]) exist_owids.extend(new_results[0])
@@ -792,7 +810,7 @@ def can_reach_smith(world, player):
def explore_region(region_name, region=None): def explore_region(region_name, region=None):
nonlocal found nonlocal found
explored_regions.add(region_name) explored_regions.append(region_name)
if not found: if not found:
if not region: if not region:
region = world.get_region(region_name, player) 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) blank_state.collect(ItemFactory('Titans Mitts', player), True)
found = False found = False
explored_regions = set() explored_regions = list()
explore_region('Links House') explore_region('Links House')
if not found: if not found:
if not invFlag: if not invFlag:
@@ -850,7 +868,7 @@ def build_sectors(world, player):
if (any(r in unique_regions for r in explored_regions)): if (any(r in unique_regions for r in explored_regions)):
for s in range(len(sectors)): for s in range(len(sectors)):
if (any(r in sectors[s] for r in explored_regions)): 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 break
else: else:
sectors.append(explored_regions) sectors.append(explored_regions)
@@ -876,7 +894,7 @@ def build_sectors(world, player):
if (any(r in unique_regions for r in explored_regions)): if (any(r in unique_regions for r in explored_regions)):
for s2 in range(len(sectors2)): for s2 in range(len(sectors2)):
if (any(r in sectors2[s2] for r in explored_regions)): 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 break
else: else:
sectors2.append(explored_regions) 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 from Items import ItemFactory
def explore_region(region_name, region=None): def explore_region(region_name, region=None):
explored_regions.add(region_name) explored_regions.append(region_name)
if not region: if not region:
region = base_world.get_region(region_name, player) region = base_world.get_region(region_name, player)
for exit in region.exits: 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) blank_state = CollectionState(base_world)
if base_world.mode[player] == 'standard': if base_world.mode[player] == 'standard':
blank_state.collect(ItemFactory('Zelda Delivered', player), True) blank_state.collect(ItemFactory('Zelda Delivered', player), True)
explored_regions = set() explored_regions = list()
explore_region(start_region) explore_region(start_region)
return explored_regions return explored_regions

View File

@@ -68,6 +68,8 @@ OW Transitions are shuffled within each world separately.
This allows OW connections to be shuffled cross-world. 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 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. 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.

View File

@@ -15,6 +15,10 @@ CLI: ```--bombbag```
# Bug Fixes and Notes. # 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 * 0.5.1.5
* Fix for hard pool capacity upgrades missing * 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 * Bonk Fairy (Light) is no longer in logic for ER Standard and is forbidden to be a connector, so rain state isn't exitable

View File

@@ -1121,9 +1121,12 @@ def mark_dark_world_regions(world, player):
def create_shops(world, player): def create_shops(world, player):
world.shops[player] = [] world.shops[player] = []
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram) in shop_table.items(): 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': if world.mode[player] == 'inverted':
locked = True if (0x35 not in world.owswaps[player][0] and region_name == 'Dark Lake Hylia Shop') \
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] 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) region = world.get_region(region_name, player)
shop = Shop(region, room_id, type, shopkeeper, custom, locked, sram) shop = Shop(region, room_id, type, shopkeeper, custom, locked, sram)
region.shop = shop region.shop = shop

2
Rom.py
View File

@@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '607ec23701b1099fdeb64a0caff190b6' RANDOMIZERBASEHASH = 'c5903112ffe302d1e9a3878ee720d1b3'
class JsonRom(object): class JsonRom(object):

View File

@@ -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)) 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']: 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)) 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) # I think these rules are unnecessary now - testing needed
forbid_item(world.get_location('Thieves\' Town - Blind\'s Cell', player), 'Big Key (Thieves Town)', player) # 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']: 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(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)) 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 # 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('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 (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('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 set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)', player), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling

Binary file not shown.

View File

@@ -119,6 +119,7 @@
"ow_crossed": { "ow_crossed": {
"choices": [ "choices": [
"none", "none",
"allowed",
"polar", "polar",
"grouped", "grouped",
"limited", "limited",

View File

@@ -208,6 +208,7 @@
"ow_crossed": [ "ow_crossed": [
"This allows cross-world connections to occur on the overworld.", "This allows cross-world connections to occur on the overworld.",
"None: No transitions are cross-world connections.", "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", "Polar: Only used when Mixed is enabled. This retains original",
" connections even when overworld tiles are swapped.", " connections even when overworld tiles are swapped.",
"Limited: Exactly nine transitions are randomly chosen as", "Limited: Exactly nine transitions are randomly chosen as",

View File

@@ -119,6 +119,7 @@
"randomizer.overworld.crossed": "Crossed", "randomizer.overworld.crossed": "Crossed",
"randomizer.overworld.crossed.none": "None", "randomizer.overworld.crossed.none": "None",
"randomizer.overworld.crossed.allowed": "None (Allowed)",
"randomizer.overworld.crossed.polar": "Polar", "randomizer.overworld.crossed.polar": "Polar",
"randomizer.overworld.crossed.grouped": "Grouped", "randomizer.overworld.crossed.grouped": "Grouped",
"randomizer.overworld.crossed.limited": "Limited", "randomizer.overworld.crossed.limited": "Limited",

View File

@@ -14,6 +14,7 @@
"default": "vanilla", "default": "vanilla",
"options": [ "options": [
"none", "none",
"allowed",
"polar", "polar",
"grouped", "grouped",
"limited", "limited",