Merge branch 'OverworldShuffleDev' into OverworldShuffle
This commit is contained in:
@@ -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()]))
|
||||||
|
|||||||
@@ -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)):
|
||||||
|
|||||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
@@ -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'):
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
9
Fill.py
9
Fill.py
@@ -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:
|
||||||
|
|||||||
3
Main.py
3
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 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)
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -376,23 +380,37 @@ def link_overworld(world, player):
|
|||||||
flute_pool.remove(owid)
|
flute_pool.remove(owid)
|
||||||
new_spots.append(owid)
|
new_spots.append(owid)
|
||||||
return True
|
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
|
# reserve a number of flute spots for each sector
|
||||||
addSpot(0x38)
|
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
|
random.shuffle(sector[1])
|
||||||
if world.owShuffle[player] == 'vanilla':
|
f = 0
|
||||||
mountainIds = [0x0b, 0x0e, 0x07]
|
while len(new_spots) < target_spots:
|
||||||
addSpot(mountainIds[random.randint(0, 2)])
|
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)
|
region_total -= sector[0]
|
||||||
f = 0
|
flute_spots -= spots_to_place
|
||||||
while len(new_spots) < 8:
|
|
||||||
if f >= len(flute_pool):
|
# connect new flute spots
|
||||||
f = 0
|
|
||||||
if flute_pool[f] not in new_spots:
|
|
||||||
addSpot(flute_pool[f])
|
|
||||||
f += 1
|
|
||||||
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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
2
Rom.py
@@ -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):
|
||||||
|
|||||||
9
Rules.py
9
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))
|
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.
@@ -119,6 +119,7 @@
|
|||||||
"ow_crossed": {
|
"ow_crossed": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"none",
|
"none",
|
||||||
|
"allowed",
|
||||||
"polar",
|
"polar",
|
||||||
"grouped",
|
"grouped",
|
||||||
"limited",
|
"limited",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"default": "vanilla",
|
"default": "vanilla",
|
||||||
"options": [
|
"options": [
|
||||||
"none",
|
"none",
|
||||||
|
"allowed",
|
||||||
"polar",
|
"polar",
|
||||||
"grouped",
|
"grouped",
|
||||||
"limited",
|
"limited",
|
||||||
|
|||||||
Reference in New Issue
Block a user