Whole ton of things:
-Experimental Flag --Mirror Scroll --Mortal GT Minibosses --Random door kinds -Crossed Mode --Standard logic --Nothing Items --GT Trash fill skip --Too many keys in retro --Hint work --Spoiler clarification --Aga 1 logic -Misc --Retro nothing item --Bombable/Dashable matching --ER+Inverted Logic fix --Logic for GT Gauntlet/Wizzrobes --Logic for PoD Sexy Statue switch
This commit is contained in:
+14
-5
@@ -69,6 +69,7 @@ class World(object):
|
|||||||
self.dungeon_layouts = {}
|
self.dungeon_layouts = {}
|
||||||
self.inaccessible_regions = {}
|
self.inaccessible_regions = {}
|
||||||
self.key_logic = {}
|
self.key_logic = {}
|
||||||
|
self.pool_adjustment = {}
|
||||||
|
|
||||||
for player in range(1, players + 1):
|
for player in range(1, players + 1):
|
||||||
def set_player_attr(attr, val):
|
def set_player_attr(attr, val):
|
||||||
@@ -1515,11 +1516,11 @@ class Spoiler(object):
|
|||||||
else:
|
else:
|
||||||
self.entrances[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
|
self.entrances[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
|
||||||
|
|
||||||
def set_door(self, entrance, exit, direction, player):
|
def set_door(self, entrance, exit, direction, player, d_name):
|
||||||
if self.world.players == 1:
|
if self.world.players == 1:
|
||||||
self.doors[(entrance, direction, player)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)])
|
self.doors[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction), ('dname', d_name)])
|
||||||
else:
|
else:
|
||||||
self.doors[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)])
|
self.doors[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction), ('dname', d_name)])
|
||||||
|
|
||||||
def set_door_type(self, doorNames, type, player):
|
def set_door_type(self, doorNames, type, player):
|
||||||
if self.world.players == 1:
|
if self.world.players == 1:
|
||||||
@@ -1625,7 +1626,8 @@ class Spoiler(object):
|
|||||||
'enemy_health': self.world.enemy_health,
|
'enemy_health': self.world.enemy_health,
|
||||||
'enemy_damage': self.world.enemy_damage,
|
'enemy_damage': self.world.enemy_damage,
|
||||||
'players': self.world.players,
|
'players': self.world.players,
|
||||||
'teams': self.world.teams
|
'teams': self.world.teams,
|
||||||
|
'experimental' : self.world.experimental
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
@@ -1683,9 +1685,16 @@ class Spoiler(object):
|
|||||||
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
|
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
|
||||||
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
||||||
outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
||||||
|
outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
|
||||||
if self.doors:
|
if self.doors:
|
||||||
outfile.write('\n\nDoors:\n\n')
|
outfile.write('\n\nDoors:\n\n')
|
||||||
outfile.write('\n'.join(['%s%s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.doors.values()]))
|
outfile.write('\n'.join(
|
||||||
|
['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '',
|
||||||
|
entry['entrance'],
|
||||||
|
'<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>',
|
||||||
|
entry['exit'],
|
||||||
|
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] == 'crossed' else '') for
|
||||||
|
entry in self.doors.values()]))
|
||||||
if self.doorTypes:
|
if self.doorTypes:
|
||||||
outfile.write('\n\nDoor Types:\n\n')
|
outfile.write('\n\nDoor Types:\n\n')
|
||||||
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['doorNames'], entry['type']) for entry in self.doorTypes.values()]))
|
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', entry['doorNames'], entry['type']) for entry in self.doorTypes.values()]))
|
||||||
|
|||||||
+182
-44
@@ -1,6 +1,5 @@
|
|||||||
import random
|
import random
|
||||||
import collections
|
from collections import defaultdict, deque
|
||||||
from collections import defaultdict
|
|
||||||
import logging
|
import logging
|
||||||
import operator as op
|
import operator as op
|
||||||
import time
|
import time
|
||||||
@@ -11,7 +10,7 @@ from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBa
|
|||||||
from Regions import key_only_locations
|
from Regions import key_only_locations
|
||||||
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions
|
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions
|
||||||
from Dungeons import dungeon_regions, region_starts, split_region_starts, flexible_starts
|
from Dungeons import dungeon_regions, region_starts, split_region_starts, flexible_starts
|
||||||
from Dungeons import drop_entrances, dungeon_bigs, dungeon_keys
|
from Dungeons import drop_entrances, dungeon_bigs, dungeon_keys, dungeon_hints
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from RoomData import DoorKind, PairedDoor
|
from RoomData import DoorKind, PairedDoor
|
||||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, validate_tr
|
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, validate_tr
|
||||||
@@ -54,8 +53,6 @@ def link_doors(world, player):
|
|||||||
within_dungeon(world, player)
|
within_dungeon(world, player)
|
||||||
elif world.doorShuffle[player] == 'crossed':
|
elif world.doorShuffle[player] == 'crossed':
|
||||||
cross_dungeon(world, player)
|
cross_dungeon(world, player)
|
||||||
elif world.doorShuffle[player] == 'experimental':
|
|
||||||
experiment(world, player)
|
|
||||||
else:
|
else:
|
||||||
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||||
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
|
||||||
@@ -69,7 +66,7 @@ def mark_regions(world, player):
|
|||||||
# traverse dungeons and make sure dungeon property is assigned
|
# traverse dungeons and make sure dungeon property is assigned
|
||||||
player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
|
player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
|
||||||
for dungeon in player_dungeons:
|
for dungeon in player_dungeons:
|
||||||
queue = collections.deque(dungeon.regions)
|
queue = deque(dungeon.regions)
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
region = world.get_region(queue.popleft(), player)
|
region = world.get_region(queue.popleft(), player)
|
||||||
if region.name not in dungeon.regions:
|
if region.name not in dungeon.regions:
|
||||||
@@ -87,31 +84,42 @@ def mark_regions(world, player):
|
|||||||
|
|
||||||
def create_door_spoiler(world, player):
|
def create_door_spoiler(world, player):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
queue = collections.deque((door for door in world.doors if door.player == player))
|
|
||||||
|
queue = deque(world.dungeon_layouts[player].values())
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
door_a = queue.popleft()
|
builder = queue.popleft()
|
||||||
if door_a.type in [DoorType.Normal, DoorType.SpiralStairs]:
|
done = set()
|
||||||
|
start_regions = set(convert_regions(builder.layout_starts, world, player)) # todo: set all_entrances for basic
|
||||||
|
reg_queue = deque(start_regions)
|
||||||
|
visited = set(start_regions)
|
||||||
|
while len(reg_queue) > 0:
|
||||||
|
next = reg_queue.pop()
|
||||||
|
for ext in next.exits:
|
||||||
|
door_a = ext.door
|
||||||
|
connect = ext.connected_region
|
||||||
|
if door_a and door_a.type in [DoorType.Normal, DoorType.SpiralStairs] and door_a not in done:
|
||||||
|
done.add(door_a)
|
||||||
door_b = door_a.dest
|
door_b = door_a.dest
|
||||||
if door_b is not None:
|
if door_b:
|
||||||
logger.debug('spoiler: %s connected to %s', door_a.name, door_b.name)
|
done.add(door_b)
|
||||||
if not door_a.blocked and not door_b.blocked:
|
if not door_a.blocked and not door_b.blocked:
|
||||||
world.spoiler.set_door(door_a.name, door_b.name, 'both', player)
|
world.spoiler.set_door(door_a.name, door_b.name, 'both', player, builder.name)
|
||||||
elif door_a.blocked:
|
elif door_a.blocked:
|
||||||
world.spoiler.set_door(door_b.name, door_a.name, 'entrance', player)
|
world.spoiler.set_door(door_b.name, door_a.name, 'entrance', player, builder.name)
|
||||||
elif door_b.blocked:
|
elif door_b.blocked:
|
||||||
world.spoiler.set_door(door_a.name, door_b.name, 'entrance', player)
|
world.spoiler.set_door(door_a.name, door_b.name, 'entrance', player, builder.name)
|
||||||
else:
|
else:
|
||||||
logger.warning('This is a bug')
|
logger.warning('This is a bug during door spoiler')
|
||||||
if door_b in queue:
|
|
||||||
queue.remove(door_b)
|
|
||||||
else:
|
|
||||||
logger.debug('Door not found in queue: %s connected to %s', door_b.name, door_a.name)
|
|
||||||
else:
|
else:
|
||||||
logger.warning('Door not connected: %s', door_a.name)
|
logger.warning('Door not connected: %s', door_a.name)
|
||||||
|
if connect and connect.type == RegionType.Dungeon and connect not in visited:
|
||||||
|
visited.add(connect)
|
||||||
|
reg_queue.append(connect)
|
||||||
|
|
||||||
|
|
||||||
def vanilla_key_logic(world, player):
|
def vanilla_key_logic(world, player):
|
||||||
builders = []
|
builders = []
|
||||||
|
world.dungeon_layouts[player] = {}
|
||||||
for dungeon in [dungeon for dungeon in world.dungeons if dungeon.player == player]:
|
for dungeon in [dungeon for dungeon in world.dungeons if dungeon.player == player]:
|
||||||
sector = Sector()
|
sector = Sector()
|
||||||
sector.name = dungeon.name
|
sector.name = dungeon.name
|
||||||
@@ -119,12 +127,13 @@ def vanilla_key_logic(world, player):
|
|||||||
builder = simple_dungeon_builder(sector.name, [sector])
|
builder = simple_dungeon_builder(sector.name, [sector])
|
||||||
builder.master_sector = sector
|
builder.master_sector = sector
|
||||||
builders.append(builder)
|
builders.append(builder)
|
||||||
|
world.dungeon_layouts[player][builder.name] = builder
|
||||||
|
|
||||||
overworld_prep(world, player)
|
overworld_prep(world, player)
|
||||||
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
||||||
|
|
||||||
enabled_entrances = {}
|
enabled_entrances = {}
|
||||||
sector_queue = collections.deque(builders)
|
sector_queue = deque(builders)
|
||||||
last_key = None
|
last_key = None
|
||||||
while len(sector_queue) > 0:
|
while len(sector_queue) > 0:
|
||||||
builder = sector_queue.popleft()
|
builder = sector_queue.popleft()
|
||||||
@@ -313,6 +322,7 @@ def within_dungeon(world, player):
|
|||||||
for builder in world.dungeon_layouts[player].values():
|
for builder in world.dungeon_layouts[player].values():
|
||||||
shuffle_key_doors(builder, world, player)
|
shuffle_key_doors(builder, world, player)
|
||||||
logging.getLogger('').info('Key door shuffle time: %s', time.process_time()-start)
|
logging.getLogger('').info('Key door shuffle time: %s', time.process_time()-start)
|
||||||
|
smooth_door_pairs(world, player)
|
||||||
|
|
||||||
|
|
||||||
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map):
|
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map):
|
||||||
@@ -334,7 +344,7 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
|
|||||||
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
|
def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player):
|
||||||
entrances_map, potentials, connections = connections_tuple
|
entrances_map, potentials, connections = connections_tuple
|
||||||
enabled_entrances = {}
|
enabled_entrances = {}
|
||||||
sector_queue = collections.deque(dungeon_builders.values())
|
sector_queue = deque(dungeon_builders.values())
|
||||||
last_key = None
|
last_key = None
|
||||||
while len(sector_queue) > 0:
|
while len(sector_queue) > 0:
|
||||||
builder = sector_queue.popleft()
|
builder = sector_queue.popleft()
|
||||||
@@ -425,7 +435,7 @@ def find_new_entrances(sector, connections, potentials, enabled, world, player):
|
|||||||
for potential in potentials.pop(new_region):
|
for potential in potentials.pop(new_region):
|
||||||
enabled[potential] = (region.name, region.dungeon)
|
enabled[potential] = (region.name, region.dungeon)
|
||||||
# see if this unexplored region connects elsewhere
|
# see if this unexplored region connects elsewhere
|
||||||
queue = collections.deque(new_region.exits)
|
queue = deque(new_region.exits)
|
||||||
visited = set()
|
visited = set()
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
ext = queue.popleft()
|
ext = queue.popleft()
|
||||||
@@ -671,6 +681,33 @@ def cross_dungeon(world, player):
|
|||||||
gt = world.get_dungeon('Ganons Tower', player)
|
gt = world.get_dungeon('Ganons Tower', player)
|
||||||
del gt.dungeon_items[0] # removes map
|
del gt.dungeon_items[0] # removes map
|
||||||
|
|
||||||
|
assign_cross_keys(dungeon_builders, world, player)
|
||||||
|
all_dungeon_items = [y for x in world.dungeons if x.player == player for y in x.all_items]
|
||||||
|
target_items = 34 if world.retro[player] else 63
|
||||||
|
d_items = target_items - len(all_dungeon_items)
|
||||||
|
if d_items > 0:
|
||||||
|
if d_items >= 1: # restore HC map
|
||||||
|
world.get_dungeon('Hyrule Castle', player).dungeon_items.append(ItemFactory('Map (Escape)', player))
|
||||||
|
if d_items >= 2: # restore GT map
|
||||||
|
world.get_dungeon('Ganons Tower', player).dungeon_items.append(ItemFactory('Map (Ganons Tower)', player))
|
||||||
|
if d_items > 2:
|
||||||
|
world.pool_adjustment[player] = d_items - 2
|
||||||
|
elif d_items < 0:
|
||||||
|
world.pool_adjustment[player] = d_items
|
||||||
|
smooth_door_pairs(world, player)
|
||||||
|
|
||||||
|
# Re-assign dungeon bosses
|
||||||
|
gt = world.get_dungeon('Ganons Tower', player)
|
||||||
|
for name, builder in dungeon_builders.items():
|
||||||
|
reassign_boss('GT Ice Armos', 'bottom', builder, gt, world, player)
|
||||||
|
reassign_boss('GT Lanmolas 2', 'middle', builder, gt, world, player)
|
||||||
|
reassign_boss('GT Moldorm', 'top', builder, gt, world, player)
|
||||||
|
|
||||||
|
if world.hints[player]:
|
||||||
|
refine_hints(dungeon_builders)
|
||||||
|
|
||||||
|
|
||||||
|
def assign_cross_keys(dungeon_builders, world, player):
|
||||||
start = time.process_time()
|
start = time.process_time()
|
||||||
total_keys = remaining = 29
|
total_keys = remaining = 29
|
||||||
total_candidates = 0
|
total_candidates = 0
|
||||||
@@ -688,6 +725,7 @@ def cross_dungeon(world, player):
|
|||||||
total_candidates += builder.key_doors_num
|
total_candidates += builder.key_doors_num
|
||||||
start_regions_map[name] = start_regions
|
start_regions_map[name] = start_regions
|
||||||
|
|
||||||
|
|
||||||
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
||||||
for name, builder in dungeon_builders.items():
|
for name, builder in dungeon_builders.items():
|
||||||
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
||||||
@@ -717,7 +755,7 @@ def cross_dungeon(world, player):
|
|||||||
# Step 4: Try to assign remaining keys
|
# Step 4: Try to assign remaining keys
|
||||||
builder_order = [x for x in dungeon_builders.values() if x.flex > 0]
|
builder_order = [x for x in dungeon_builders.values() if x.flex > 0]
|
||||||
builder_order.sort(key=lambda b: b.combo_size)
|
builder_order.sort(key=lambda b: b.combo_size)
|
||||||
queue = collections.deque(builder_order)
|
queue = deque(builder_order)
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
while len(queue) > 0 and remaining > 0:
|
while len(queue) > 0 and remaining > 0:
|
||||||
builder = queue.popleft()
|
builder = queue.popleft()
|
||||||
@@ -731,7 +769,7 @@ def cross_dungeon(world, player):
|
|||||||
if builder.flex > 0:
|
if builder.flex > 0:
|
||||||
builder.combo_size = ncr(len(builder.candidates), builder.key_doors_num)
|
builder.combo_size = ncr(len(builder.candidates), builder.key_doors_num)
|
||||||
queue.append(builder)
|
queue.append(builder)
|
||||||
queue = collections.deque(sorted(queue, key=lambda b: b.combo_size))
|
queue = deque(sorted(queue, key=lambda b: b.combo_size))
|
||||||
else:
|
else:
|
||||||
logger.info('Cross Dungeon: Increase failed for %s', name)
|
logger.info('Cross Dungeon: Increase failed for %s', name)
|
||||||
builder.key_doors_num -= 1
|
builder.key_doors_num -= 1
|
||||||
@@ -739,6 +777,7 @@ def cross_dungeon(world, player):
|
|||||||
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
||||||
|
|
||||||
# Last Step: Adjust Small Key Dungeon Pool
|
# Last Step: Adjust Small Key Dungeon Pool
|
||||||
|
if not world.retro[player]:
|
||||||
for name, builder in dungeon_builders.items():
|
for name, builder in dungeon_builders.items():
|
||||||
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
||||||
dungeon = world.get_dungeon(name, player)
|
dungeon = world.get_dungeon(name, player)
|
||||||
@@ -747,14 +786,6 @@ def cross_dungeon(world, player):
|
|||||||
else:
|
else:
|
||||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||||
logging.getLogger('').info('Cross Dungeon: Key door shuffle time: %s', time.process_time()-start)
|
logging.getLogger('').info('Cross Dungeon: Key door shuffle time: %s', time.process_time()-start)
|
||||||
# todo: pair down paired doors - excessive rom writes ATM
|
|
||||||
|
|
||||||
# Re-assign dungeon bosses
|
|
||||||
gt = world.get_dungeon('Ganons Tower', player)
|
|
||||||
for name, builder in dungeon_builders.items():
|
|
||||||
reassign_boss('GT Ice Armos', 'bottom', builder, gt, world, player)
|
|
||||||
reassign_boss('GT Lanmolas 2', 'middle', builder, gt, world, player)
|
|
||||||
reassign_boss('GT Moldorm', 'top', builder, gt, world, player)
|
|
||||||
|
|
||||||
|
|
||||||
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||||
@@ -765,9 +796,12 @@ def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
|||||||
new_dungeon.bosses[boss_key] = gt_boss
|
new_dungeon.bosses[boss_key] = gt_boss
|
||||||
|
|
||||||
|
|
||||||
def experiment(world, player):
|
def refine_hints(dungeon_builders):
|
||||||
# print_wiki_doors(dungeon_regions, world, player)
|
for name, builder in dungeon_builders.items():
|
||||||
cross_dungeon(world, player)
|
for region in builder.master_sector.regions:
|
||||||
|
for location in region.locations:
|
||||||
|
if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
|
||||||
|
location.hint_text = dungeon_hints[name]
|
||||||
|
|
||||||
|
|
||||||
def convert_to_sectors(region_names, world, player):
|
def convert_to_sectors(region_names, world, player):
|
||||||
@@ -817,7 +851,7 @@ def convert_to_sectors(region_names, world, player):
|
|||||||
# those with split region starts like Desert/Skull combine for key layouts
|
# those with split region starts like Desert/Skull combine for key layouts
|
||||||
def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
||||||
for recombine in recombinant_builders.values():
|
for recombine in recombinant_builders.values():
|
||||||
queue = collections.deque(dungeon_builders.values())
|
queue = deque(dungeon_builders.values())
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
builder = queue.pop()
|
builder = queue.pop()
|
||||||
if builder.name.startswith(recombine.name):
|
if builder.name.startswith(recombine.name):
|
||||||
@@ -865,8 +899,8 @@ def find_current_key_doors(builder, world, player):
|
|||||||
current_doors = []
|
current_doors = []
|
||||||
for region in builder.master_sector.regions:
|
for region in builder.master_sector.regions:
|
||||||
for ext in region.exits:
|
for ext in region.exits:
|
||||||
d = world.check_for_door(ext.name, player)
|
d = ext.door
|
||||||
if d is not None and d.smallKey:
|
if d and d.smallKey:
|
||||||
current_doors.append(d)
|
current_doors.append(d)
|
||||||
return current_doors
|
return current_doors
|
||||||
|
|
||||||
@@ -976,7 +1010,7 @@ def log_key_logic(d_name, key_logic):
|
|||||||
|
|
||||||
def build_pair_list(flat_list):
|
def build_pair_list(flat_list):
|
||||||
paired_list = []
|
paired_list = []
|
||||||
queue = collections.deque(flat_list)
|
queue = deque(flat_list)
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
d = queue.pop()
|
d = queue.pop()
|
||||||
if d.dest in queue and d.type != DoorType.SpiralStairs:
|
if d.dest in queue and d.type != DoorType.SpiralStairs:
|
||||||
@@ -1002,7 +1036,7 @@ def find_key_door_candidates(region, checked, world, player):
|
|||||||
dungeon = region.dungeon
|
dungeon = region.dungeon
|
||||||
candidates = []
|
candidates = []
|
||||||
checked_doors = list(checked)
|
checked_doors = list(checked)
|
||||||
queue = collections.deque([(region, None, None)])
|
queue = deque([(region, None, None)])
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
current, last_door, last_region = queue.pop()
|
current, last_door, last_region = queue.pop()
|
||||||
for ext in current.exits:
|
for ext in current.exits:
|
||||||
@@ -1061,7 +1095,7 @@ def ncr(n, r):
|
|||||||
def reassign_key_doors(builder, proposal, world, player):
|
def reassign_key_doors(builder, proposal, world, player):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
flat_proposal = flatten_pair_list(proposal)
|
flat_proposal = flatten_pair_list(proposal)
|
||||||
queue = collections.deque(find_current_key_doors(builder, world, player))
|
queue = deque(find_current_key_doors(builder, world, player))
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
d = queue.pop()
|
d = queue.pop()
|
||||||
if d.type is DoorType.SpiralStairs and d not in proposal:
|
if d.type is DoorType.SpiralStairs and d not in proposal:
|
||||||
@@ -1070,7 +1104,7 @@ def reassign_key_doors(builder, proposal, world, player):
|
|||||||
room.delete(d.doorListPos)
|
room.delete(d.doorListPos)
|
||||||
else:
|
else:
|
||||||
if len(room.doorList) > 1:
|
if len(room.doorList) > 1:
|
||||||
room.mirror(d.doorListPos) # todo: I don't think this works for crossed - maybe it will
|
room.mirror(d.doorListPos) # I think this works for crossed now
|
||||||
else:
|
else:
|
||||||
room.delete(d.doorListPos)
|
room.delete(d.doorListPos)
|
||||||
d.smallKey = False
|
d.smallKey = False
|
||||||
@@ -1129,6 +1163,104 @@ def change_door_to_small_key(d, world, player):
|
|||||||
room.change(d.doorListPos, DoorKind.SmallKey)
|
room.change(d.doorListPos, DoorKind.SmallKey)
|
||||||
|
|
||||||
|
|
||||||
|
def smooth_door_pairs(world, player):
|
||||||
|
all_doors = [x for x in world.doors if x.player == player]
|
||||||
|
skip = set()
|
||||||
|
for door in all_doors:
|
||||||
|
if door.type in [DoorType.Normal, DoorType.Interior] and door not in skip:
|
||||||
|
partner = door.dest
|
||||||
|
skip.add(partner)
|
||||||
|
room_a = world.get_room(door.roomIndex, player)
|
||||||
|
room_b = world.get_room(partner.roomIndex, player)
|
||||||
|
type_a = room_a.kind(door)
|
||||||
|
type_b = room_b.kind(partner)
|
||||||
|
valid_pair = stateful_door(door, type_a) and stateful_door(partner, type_b)
|
||||||
|
if door.type == DoorType.Normal:
|
||||||
|
if type_a == DoorKind.SmallKey or type_b == DoorKind.SmallKey:
|
||||||
|
if valid_pair:
|
||||||
|
if type_a != DoorKind.SmallKey:
|
||||||
|
room_a.change(door.doorListPos, DoorKind.SmallKey)
|
||||||
|
if type_b != DoorKind.SmallKey:
|
||||||
|
room_b.change(partner.doorListPos, DoorKind.SmallKey)
|
||||||
|
add_pair(door, partner, world, player)
|
||||||
|
else:
|
||||||
|
if type_a == DoorKind.SmallKey:
|
||||||
|
remove_pair(door, world, player)
|
||||||
|
if type_b == DoorKind.SmallKey:
|
||||||
|
remove_pair(door, world, player)
|
||||||
|
elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||||
|
if valid_pair:
|
||||||
|
if type_a == type_b:
|
||||||
|
add_pair(door, partner, world, player)
|
||||||
|
spoiler_type = 'Bomb Door' if type_a == DoorKind.Bombable else 'Dash Door'
|
||||||
|
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
|
||||||
|
else:
|
||||||
|
new_type = DoorKind.Dashable if type_a == DoorKind.Dashable or type_b == DoorKind.Dashable else DoorKind.Bombable
|
||||||
|
if type_a != new_type:
|
||||||
|
room_a.change(door.doorListPos, new_type)
|
||||||
|
if type_b != new_type:
|
||||||
|
room_b.change(partner.doorListPos, new_type)
|
||||||
|
add_pair(door, partner, world, player)
|
||||||
|
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
|
||||||
|
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
|
||||||
|
else:
|
||||||
|
if type_a in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||||
|
room_a.change(door.doorListPos, DoorKind.Normal)
|
||||||
|
remove_pair(door, world, player)
|
||||||
|
elif type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||||
|
room_b.change(partner.doorListPos, DoorKind.Normal)
|
||||||
|
remove_pair(partner, world, player)
|
||||||
|
elif world.experimental[player] and valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey:
|
||||||
|
random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b)
|
||||||
|
world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original]
|
||||||
|
|
||||||
|
|
||||||
|
def add_pair(door_a, door_b, world, player):
|
||||||
|
pair_a, pair_b = None, None
|
||||||
|
for paired_door in world.paired_doors[player]:
|
||||||
|
if paired_door.door_a == door_a.name and paired_door.door_b == door_b.name:
|
||||||
|
paired_door.pair = True
|
||||||
|
return
|
||||||
|
if paired_door.door_a == door_b.name and paired_door.door_b == door_a.name:
|
||||||
|
paired_door.pair = True
|
||||||
|
return
|
||||||
|
if paired_door.door_a == door_a.name or paired_door.door_b == door_a.name:
|
||||||
|
pair_a = paired_door
|
||||||
|
if paired_door.door_a == door_b.name or paired_door.door_b == door_b.name:
|
||||||
|
pair_b = paired_door
|
||||||
|
if pair_a:
|
||||||
|
pair_a.pair = False
|
||||||
|
if pair_b:
|
||||||
|
pair_b.pair = False
|
||||||
|
world.paired_doors[player].append(PairedDoor(door_a, door_b))
|
||||||
|
|
||||||
|
|
||||||
|
def remove_pair(door, world, player):
|
||||||
|
for paired_door in world.paired_doors[player]:
|
||||||
|
if paired_door.door_a == door.name or paired_door.door_b == door.name:
|
||||||
|
paired_door.pair = False
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def stateful_door(door, kind):
|
||||||
|
if 0 <= door.doorListPos < 4:
|
||||||
|
return kind in [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable] #, DoorKind.BigKey]
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b):
|
||||||
|
r_kind = random.choices([DoorKind.Normal, DoorKind.Bombable, DoorKind.Dashable], [5, 2, 3], k=1)[0]
|
||||||
|
if r_kind != DoorKind.Normal:
|
||||||
|
if door.type == DoorType.Normal:
|
||||||
|
add_pair(door, partner, world, player)
|
||||||
|
if type_a != r_kind:
|
||||||
|
room_a.change(door.doorListPos, r_kind)
|
||||||
|
if type_b != r_kind:
|
||||||
|
room_b.change(partner.doorListPos, r_kind)
|
||||||
|
spoiler_type = 'Bomb Door' if r_kind == DoorKind.Bombable else 'Dash Door'
|
||||||
|
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
|
||||||
|
|
||||||
|
|
||||||
def determine_required_paths(world, player):
|
def determine_required_paths(world, player):
|
||||||
paths = {
|
paths = {
|
||||||
'Hyrule Castle': ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby'],
|
'Hyrule Castle': ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby'],
|
||||||
@@ -1168,13 +1300,18 @@ def find_inaccessible_regions(world, player):
|
|||||||
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 = set([r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon])
|
||||||
visited_regions = set()
|
visited_regions = set()
|
||||||
queue = collections.deque(regs)
|
queue = deque(regs)
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
next_region = queue.popleft()
|
next_region = queue.popleft()
|
||||||
visited_regions.add(next_region)
|
visited_regions.add(next_region)
|
||||||
|
if next_region.name == 'Inverted Dark Sanctuary': # special spawn point in cave
|
||||||
|
for ent in next_region.entrances:
|
||||||
|
parent = ent.parent_region
|
||||||
|
if parent and parent.type is not RegionType.Dungeon and parent not in queue and parent not in visited_regions:
|
||||||
|
queue.append(parent)
|
||||||
for ext in next_region.exits:
|
for ext in next_region.exits:
|
||||||
connect = ext.connected_region
|
connect = ext.connected_region
|
||||||
if connect is not None and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions:
|
if connect and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions:
|
||||||
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.difference(visited_regions) if valid_inaccessible_region(r)])
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
@@ -1302,6 +1439,7 @@ def check_for_pinball_fix(state, bad_region, world, player):
|
|||||||
@unique
|
@unique
|
||||||
class DROptions(Flag):
|
class DROptions(Flag):
|
||||||
Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart
|
Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart
|
||||||
|
Town_Portal = 0x02 # If on, Players will start with mirror scroll
|
||||||
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
|
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
|
||||||
|
|
||||||
# DATA GOES DOWN HERE
|
# DATA GOES DOWN HERE
|
||||||
|
|||||||
@@ -1098,6 +1098,8 @@ def create_doors(world, player):
|
|||||||
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
|
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('PoD Sexy Statue W', player).c_switch()
|
world.get_door('PoD Sexy Statue W', player).c_switch()
|
||||||
world.get_door('PoD Sexy Statue NW', player).c_switch()
|
world.get_door('PoD Sexy Statue NW', player).c_switch()
|
||||||
|
world.get_door('PoD Map Balcony WS', player).c_switch()
|
||||||
|
world.get_door('PoD Map Balcony South Stairs', player).c_switch()
|
||||||
world.get_door('PoD Bow Statue SW', player).c_switch()
|
world.get_door('PoD Bow Statue SW', player).c_switch()
|
||||||
world.get_door('PoD Bow Statue Down Ladder', player).c_switch()
|
world.get_door('PoD Bow Statue Down Ladder', player).c_switch()
|
||||||
world.get_door('PoD Dark Pegs Up Ladder', player).c_switch()
|
world.get_door('PoD Dark Pegs Up Ladder', player).c_switch()
|
||||||
@@ -1211,44 +1213,44 @@ def create_doors(world, player):
|
|||||||
|
|
||||||
def create_paired_doors(world, player):
|
def create_paired_doors(world, player):
|
||||||
world.paired_doors[player] = [
|
world.paired_doors[player] = [
|
||||||
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N'),
|
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N', True),
|
||||||
PairedDoor('TR Pokey 2 ES', 'TR Lava Island WS'), # TR Pokey Key
|
PairedDoor('TR Pokey 2 ES', 'TR Lava Island WS', True), # TR Pokey Key
|
||||||
PairedDoor('TR Dodgers NE', 'TR Lava Escape SE'), # TR Big key door by pipes
|
PairedDoor('TR Dodgers NE', 'TR Lava Escape SE', True), # TR Big key door by pipes
|
||||||
PairedDoor('PoD Falling Bridge WN', 'PoD Dark Maze EN'), # Pod Dark maze door
|
PairedDoor('PoD Falling Bridge WN', 'PoD Dark Maze EN', True), # Pod Dark maze door
|
||||||
PairedDoor('PoD Dark Maze E', 'PoD Big Chest Balcony W'), # PoD Bombable by Big Chest
|
PairedDoor('PoD Dark Maze E', 'PoD Big Chest Balcony W', True), # PoD Bombable by Big Chest
|
||||||
PairedDoor('PoD Arena Main NW', 'PoD Falling Bridge SW'), # Pod key door by bridge
|
PairedDoor('PoD Arena Main NW', 'PoD Falling Bridge SW', True), # Pod key door by bridge
|
||||||
PairedDoor('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S'),
|
PairedDoor('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S', True),
|
||||||
PairedDoor('Swamp Hub WN', 'Swamp Crystal Switch EN'), # Swamp key door crystal switch
|
PairedDoor('Swamp Hub WN', 'Swamp Crystal Switch EN', True), # Swamp key door crystal switch
|
||||||
PairedDoor('Swamp Hub North Ledge N', 'Swamp Push Statue S'), # Swamp key door above big chest
|
PairedDoor('Swamp Hub North Ledge N', 'Swamp Push Statue S', True), # Swamp key door above big chest
|
||||||
PairedDoor('PoD Map Balcony WS', 'PoD Arena Ledge ES'), # Pod bombable by arena
|
PairedDoor('PoD Map Balcony WS', 'PoD Arena Ledge ES', True), # Pod bombable by arena
|
||||||
PairedDoor('Swamp Hub Dead Ledge EN', 'Swamp Hammer Switch WN'), # Swamp bombable to random pots
|
PairedDoor('Swamp Hub Dead Ledge EN', 'Swamp Hammer Switch WN', True), # Swamp bombable to random pots
|
||||||
PairedDoor('Swamp Pot Row WN', 'Swamp Map Ledge EN'), # Swamp bombable to map chest
|
PairedDoor('Swamp Pot Row WN', 'Swamp Map Ledge EN', True), # Swamp bombable to map chest
|
||||||
PairedDoor('Swamp Pot Row WS', 'Swamp Trench 1 Approach ES'), # Swamp key door early room $38
|
PairedDoor('Swamp Pot Row WS', 'Swamp Trench 1 Approach ES', True), # Swamp key door early room $38
|
||||||
PairedDoor('PoD Middle Cage N', 'PoD Pit Room S'),
|
PairedDoor('PoD Middle Cage N', 'PoD Pit Room S', True),
|
||||||
PairedDoor('GT Crystal Circles SW', 'GT Left Moldorm Ledge NW'), # GT moldorm key door
|
PairedDoor('GT Crystal Circles SW', 'GT Left Moldorm Ledge NW', True), # GT moldorm key door
|
||||||
PairedDoor('Ice Conveyor SW', 'Ice Bomb Jump NW'), # Ice BJ key door
|
PairedDoor('Ice Conveyor SW', 'Ice Bomb Jump NW', True), # Ice BJ key door
|
||||||
PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE'),
|
PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE', True),
|
||||||
PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW'), # Skull 3 key door
|
PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW', True), # Skull 3 key door
|
||||||
PairedDoor('Skull 1 Lobby WS', 'Skull Pot Prison ES'), # Skull 1 key door - pot prison to big chest
|
PairedDoor('Skull 1 Lobby WS', 'Skull Pot Prison ES', True), # Skull 1 key door - pot prison to big chest
|
||||||
PairedDoor('Skull Map Room SE', 'Skull Pinball NE'), # Skull 1 - pinball key door
|
PairedDoor('Skull Map Room SE', 'Skull Pinball NE', True), # Skull 1 - pinball key door
|
||||||
PairedDoor('GT Dash Hall NE', 'GT Hidden Spikes SE'), # gt main big key door
|
PairedDoor('GT Dash Hall NE', 'GT Hidden Spikes SE', True), # gt main big key door
|
||||||
PairedDoor('Ice Spike Cross ES', 'Ice Spike Room WS'), # ice door to spike chest
|
PairedDoor('Ice Spike Cross ES', 'Ice Spike Room WS', True), # ice door to spike chest
|
||||||
PairedDoor('GT Conveyor Star Pits EN', 'GT Falling Bridge WN'), # gt right side key door to cape bridge
|
PairedDoor('GT Conveyor Star Pits EN', 'GT Falling Bridge WN', True), # gt right side key door to cape bridge
|
||||||
PairedDoor('GT Warp Maze (Rails) WS', 'GT Randomizer Room ES'), # gt bombable to rando room
|
PairedDoor('GT Warp Maze (Rails) WS', 'GT Randomizer Room ES', True), # gt bombable to rando room
|
||||||
PairedDoor('Ice Tall Hint SE', 'Ice Lonely Freezor NE'), # ice's big icy room key door to lonely freezor
|
PairedDoor('Ice Tall Hint SE', 'Ice Lonely Freezor NE', True), # ice's big icy room key door to lonely freezor
|
||||||
PairedDoor('Eastern Courtyard N', 'Eastern Darkness S'),
|
PairedDoor('Eastern Courtyard N', 'Eastern Darkness S', True),
|
||||||
PairedDoor('Mire Fishbone SE', 'Mire Spike Barrier NE'), # mire fishbone key door
|
PairedDoor('Mire Fishbone SE', 'Mire Spike Barrier NE', True), # mire fishbone key door
|
||||||
PairedDoor('Mire BK Door Room N', 'Mire Left Bridge S'), # mire big key door to bridges
|
PairedDoor('Mire BK Door Room N', 'Mire Left Bridge S', True), # mire big key door to bridges
|
||||||
PairedDoor('Eastern Big Key NE', 'Eastern Hint Tile Blocked Path SE'),
|
PairedDoor('Eastern Big Key NE', 'Eastern Hint Tile Blocked Path SE', True),
|
||||||
PairedDoor('TR Hub NW', 'TR Pokey 1 SW'), # TR somaria hub to pokey
|
PairedDoor('TR Hub NW', 'TR Pokey 1 SW', True), # TR somaria hub to pokey
|
||||||
PairedDoor('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN'),
|
PairedDoor('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN', True),
|
||||||
PairedDoor('Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW'), # TT random bomb to pots
|
PairedDoor('Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW', True), # TT random bomb to pots
|
||||||
PairedDoor('Thieves BK Corner NE', 'Thieves Hallway SE'), # TT big key door
|
PairedDoor('Thieves BK Corner NE', 'Thieves Hallway SE', True), # TT big key door
|
||||||
PairedDoor('Ice Switch Room ES', 'Ice Refill WS'), # Ice last key door to crystal switch
|
PairedDoor('Ice Switch Room ES', 'Ice Refill WS', True), # Ice last key door to crystal switch
|
||||||
PairedDoor('Mire Hub WS', 'Mire Conveyor Crystal ES'), # mire hub key door to attic
|
PairedDoor('Mire Hub WS', 'Mire Conveyor Crystal ES', True), # mire hub key door to attic
|
||||||
PairedDoor('Mire Hub Right EN', 'Mire Map Spot WN'), # mire hub key door to map
|
PairedDoor('Mire Hub Right EN', 'Mire Map Spot WN', True), # mire hub key door to map
|
||||||
PairedDoor('TR Dash Bridge WS', 'TR Crystal Maze ES'), # tr last key door to switch maze
|
PairedDoor('TR Dash Bridge WS', 'TR Crystal Maze ES', True), # tr last key door to switch maze
|
||||||
PairedDoor('Thieves Ambush E', 'Thieves Rail Ledge W') # TT dashable above
|
PairedDoor('Thieves Ambush E', 'Thieves Rail Ledge W', True) # TT dashable above
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ def parse_arguments(argv, no_defaults=False):
|
|||||||
The dungeon variants only mix up dungeons and keep the rest of
|
The dungeon variants only mix up dungeons and keep the rest of
|
||||||
the overworld vanilla.
|
the overworld vanilla.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--door_shuffle', default=defval('basic'), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed', 'experimental'],
|
parser.add_argument('--door_shuffle', default=defval('basic'), const='vanilla', nargs='?', choices=['vanilla', 'basic', 'crossed'],
|
||||||
help='''\
|
help='''\
|
||||||
Select Door Shuffling Algorithm. (default: %(default)s)
|
Select Door Shuffling Algorithm. (default: %(default)s)
|
||||||
Basic: Doors are mixed within a single dungeon.
|
Basic: Doors are mixed within a single dungeon.
|
||||||
@@ -182,8 +182,8 @@ def parse_arguments(argv, no_defaults=False):
|
|||||||
(Not yet implemented)
|
(Not yet implemented)
|
||||||
Vanilla: All doors are connected the same way they were in the
|
Vanilla: All doors are connected the same way they were in the
|
||||||
base game.
|
base game.
|
||||||
Experimental: Experimental mixes live here. Use at your own risk.
|
|
||||||
''')
|
''')
|
||||||
|
parser.add_argument('--experimental', default=defval(False), help='Enable experimental features', action='store_true')
|
||||||
parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
||||||
help='''\
|
help='''\
|
||||||
How many crystals are needed to defeat ganon. Any other
|
How many crystals are needed to defeat ganon. Any other
|
||||||
@@ -302,7 +302,7 @@ def parse_arguments(argv, no_defaults=False):
|
|||||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||||
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'retro', 'accessibility', 'hints', 'beemizer',
|
'retro', 'accessibility', 'hints', 'beemizer', 'experimental',
|
||||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||||
'remote_items']:
|
'remote_items']:
|
||||||
|
|||||||
+16
@@ -380,3 +380,19 @@ dungeon_bigs = {
|
|||||||
'Ganons Tower': 'Big Key (Ganons Tower)'
|
'Ganons Tower': 'Big Key (Ganons Tower)'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dungeon_hints = {
|
||||||
|
'Hyrule Castle': 'in Hyrule Castle',
|
||||||
|
'Eastern Palace': 'in Eastern Palace',
|
||||||
|
'Desert Palace': 'in Desert Palace',
|
||||||
|
'Tower of Hera': 'in Tower of Hera',
|
||||||
|
'Agahnims Tower': 'in Castle Tower',
|
||||||
|
'Palace of Darkness': 'in Palace of Darkness',
|
||||||
|
'Swamp Palace': 'in Swamp Palace)',
|
||||||
|
'Skull Woods': 'in Skull Woods',
|
||||||
|
'Thieves Town': 'in Thieves\' Town)',
|
||||||
|
'Ice Palace': 'in Ice Palace',
|
||||||
|
'Misery Mire': 'in Misery Mire',
|
||||||
|
'Turtle Rock': 'in Turtle Rock',
|
||||||
|
'Ganons Tower': 'in Ganon\'s Tower'
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
|
|
||||||
# fill in gtower locations with trash first
|
# fill in gtower locations with trash first
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if not gftower_trash or not world.ganonstower_vanilla[player]:
|
if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15))
|
gftower_trash_count = (random.randint(15, 50) if world.goal[player] == 'triforcehunt' else random.randint(0, 15))
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ def guiMain(args=None):
|
|||||||
doorShuffleFrame = Frame(drowDownFrame)
|
doorShuffleFrame = Frame(drowDownFrame)
|
||||||
doorShuffleVar = StringVar()
|
doorShuffleVar = StringVar()
|
||||||
doorShuffleVar.set('basic')
|
doorShuffleVar.set('basic')
|
||||||
doorShuffleOptionMenu = OptionMenu(doorShuffleFrame, doorShuffleVar, 'vanilla', 'basic', 'crossed', 'experimental')
|
doorShuffleOptionMenu = OptionMenu(doorShuffleFrame, doorShuffleVar, 'vanilla', 'basic', 'crossed')
|
||||||
doorShuffleOptionMenu.pack(side=RIGHT)
|
doorShuffleOptionMenu.pack(side=RIGHT)
|
||||||
doorShuffleLabel = Label(doorShuffleFrame, text='Door shuffle algorithm')
|
doorShuffleLabel = Label(doorShuffleFrame, text='Door shuffle algorithm')
|
||||||
doorShuffleLabel.pack(side=LEFT)
|
doorShuffleLabel.pack(side=LEFT)
|
||||||
|
|||||||
+17
-5
@@ -58,7 +58,7 @@ difficulties = {
|
|||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
retro = ['Small Key (Universal)'] * 17 + ['Rupees (20)'] * 10,
|
retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10,
|
||||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||||
progressive_sword_limit = 4,
|
progressive_sword_limit = 4,
|
||||||
progressive_shield_limit = 3,
|
progressive_shield_limit = 3,
|
||||||
@@ -85,7 +85,7 @@ difficulties = {
|
|||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15,
|
||||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||||
progressive_sword_limit = 3,
|
progressive_sword_limit = 3,
|
||||||
progressive_shield_limit = 2,
|
progressive_shield_limit = 2,
|
||||||
@@ -112,7 +112,7 @@ difficulties = {
|
|||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15,
|
||||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||||
progressive_sword_limit = 2,
|
progressive_sword_limit = 2,
|
||||||
progressive_shield_limit = 1,
|
progressive_shield_limit = 1,
|
||||||
@@ -206,7 +206,16 @@ def generate_itempool(world, player):
|
|||||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray)
|
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray)
|
||||||
world.rupoor_cost = min(world.customitemarray[69], 9999)
|
world.rupoor_cost = min(world.customitemarray[69], 9999)
|
||||||
else:
|
else:
|
||||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player])
|
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player])
|
||||||
|
|
||||||
|
if player in world.pool_adjustment.keys():
|
||||||
|
amt = world.pool_adjustment[player]
|
||||||
|
if amt < 0:
|
||||||
|
for i in range(0, amt):
|
||||||
|
pool.remove('Rupees (20)')
|
||||||
|
elif amt > 0:
|
||||||
|
for i in range(0, amt):
|
||||||
|
pool.append('Rupees (20)')
|
||||||
|
|
||||||
for item in precollected_items:
|
for item in precollected_items:
|
||||||
world.push_precollected(ItemFactory(item, player))
|
world.push_precollected(ItemFactory(item, player))
|
||||||
@@ -406,7 +415,7 @@ def set_up_shops(world, player):
|
|||||||
rss.locked = True
|
rss.locked = True
|
||||||
|
|
||||||
|
|
||||||
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro):
|
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, door_shuffle):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = {}
|
placed_items = {}
|
||||||
precollected_items = []
|
precollected_items = []
|
||||||
@@ -525,10 +534,13 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||||||
pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool]
|
pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool]
|
||||||
pool.extend(diff.retro)
|
pool.extend(diff.retro)
|
||||||
if mode == 'standard':
|
if mode == 'standard':
|
||||||
|
if door_shuffle == 'vanilla':
|
||||||
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||||
place_item(key_location, 'Small Key (Universal)')
|
place_item(key_location, 'Small Key (Universal)')
|
||||||
else:
|
else:
|
||||||
pool.extend(['Small Key (Universal)'])
|
pool.extend(['Small Key (Universal)'])
|
||||||
|
else:
|
||||||
|
pool.extend(['Small Key (Universal)'])
|
||||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||||
|
|
||||||
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
|
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
|
||||||
|
|||||||
+3
-2
@@ -806,7 +806,8 @@ def reduce_rules(small_rules, collected, collected_alt):
|
|||||||
|
|
||||||
# Soft lock stuff
|
# Soft lock stuff
|
||||||
def validate_key_layout(key_layout, world, player):
|
def validate_key_layout(key_layout, world, player):
|
||||||
if world.retro[player]: # retro is all good - don't care how the doors are laid out
|
# retro is all good - except for hyrule castle in standard mode
|
||||||
|
if world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle'):
|
||||||
return True
|
return True
|
||||||
flat_proposal = key_layout.flat_prop
|
flat_proposal = key_layout.flat_prop
|
||||||
state = ExplorationState(dungeon=key_layout.sector.name)
|
state = ExplorationState(dungeon=key_layout.sector.name)
|
||||||
@@ -815,7 +816,7 @@ def validate_key_layout(key_layout, world, player):
|
|||||||
for region in key_layout.start_regions:
|
for region in key_layout.start_regions:
|
||||||
state.visit_region(region, key_checks=True)
|
state.visit_region(region, key_checks=True)
|
||||||
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||||
return validate_key_layout_sub_loop(key_layout, state, {}, flat_proposal, None, None, world, player)
|
return validate_key_layout_sub_loop(key_layout, state, {}, flat_proposal, None, 0, world, player)
|
||||||
|
|
||||||
|
|
||||||
def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposal, prev_state, prev_avail, world, player):
|
def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposal, prev_state, prev_avail, world, player):
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
|||||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||||
from Utils import output_path, parse_player_names
|
from Utils import output_path, parse_player_names
|
||||||
|
|
||||||
__version__ = '0.0.12pre'
|
__version__ = '0.0.13pre'
|
||||||
|
|
||||||
|
|
||||||
def main(args, seed=None):
|
def main(args, seed=None):
|
||||||
@@ -56,6 +56,7 @@ def main(args, seed=None):
|
|||||||
world.enemy_health = args.enemy_health.copy()
|
world.enemy_health = args.enemy_health.copy()
|
||||||
world.enemy_damage = args.enemy_damage.copy()
|
world.enemy_damage = args.enemy_damage.copy()
|
||||||
world.beemizer = args.beemizer.copy()
|
world.beemizer = args.beemizer.copy()
|
||||||
|
world.experimental = args.experimental.copy()
|
||||||
|
|
||||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = 'c06e14396839bc443a6918e736f1e5a7'
|
RANDOMIZERBASEHASH = '818b2c659a610cb4112804cf612ffd37'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -591,7 +591,7 @@ def patch_rom(world, rom, player, team, enemized):
|
|||||||
patch_shuffled_dark_sanc(world, rom, player)
|
patch_shuffled_dark_sanc(world, rom, player)
|
||||||
|
|
||||||
# patch doors
|
# patch doors
|
||||||
dr_flags = DROptions.Eternal_Mini_Bosses
|
dr_flags = DROptions.Eternal_Mini_Bosses if not world.experimental[player] else DROptions.Town_Portal
|
||||||
if world.doorShuffle[player] == 'crossed':
|
if world.doorShuffle[player] == 'crossed':
|
||||||
rom.write_byte(0x139004, 2)
|
rom.write_byte(0x139004, 2)
|
||||||
rom.write_byte(0x151f1, 2)
|
rom.write_byte(0x151f1, 2)
|
||||||
@@ -1676,10 +1676,13 @@ def write_strings(rom, world, player, team):
|
|||||||
|
|
||||||
# Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable.
|
# Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable.
|
||||||
locations_to_hint = InconvenientLocations.copy()
|
locations_to_hint = InconvenientLocations.copy()
|
||||||
|
if world.doorShuffle[player] != 'crossed':
|
||||||
|
locations_to_hint.extend(InconvenientDungeonLocations)
|
||||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||||
locations_to_hint.extend(InconvenientVanillaLocations)
|
locations_to_hint.extend(InconvenientVanillaLocations)
|
||||||
random.shuffle(locations_to_hint)
|
random.shuffle(locations_to_hint)
|
||||||
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5
|
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5
|
||||||
|
hint_count -= 2 if world.doorShuffle[player] == 'crossed' else 0
|
||||||
del locations_to_hint[hint_count:]
|
del locations_to_hint[hint_count:]
|
||||||
for location in locations_to_hint:
|
for location in locations_to_hint:
|
||||||
if location == 'Swamp Left':
|
if location == 'Swamp Left':
|
||||||
@@ -1733,20 +1736,17 @@ def write_strings(rom, world, player, team):
|
|||||||
items_to_hint.extend(BigKeys)
|
items_to_hint.extend(BigKeys)
|
||||||
random.shuffle(items_to_hint)
|
random.shuffle(items_to_hint)
|
||||||
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
|
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
|
||||||
|
hint_count += 2 if world.doorShuffle[player] == 'crossed' else 0
|
||||||
while hint_count > 0:
|
while hint_count > 0:
|
||||||
this_item = items_to_hint.pop(0)
|
this_item = items_to_hint.pop(0)
|
||||||
this_location = world.find_items_not_key_only(this_item, player)
|
this_location = world.find_items_not_key_only(this_item, player)
|
||||||
random.shuffle(this_location)
|
random.shuffle(this_location)
|
||||||
#This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool.
|
|
||||||
if this_location:
|
|
||||||
if this_location[0].name == 'Skull Woods - Pinball Room':
|
|
||||||
this_location.pop(0)
|
|
||||||
if this_location:
|
if this_location:
|
||||||
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
|
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
hint_count -= 1
|
hint_count -= 1
|
||||||
|
|
||||||
# Adding a hint for the Thieves' Town Attic location in Crossed Doorshufle.
|
# Adding a hint for the Thieves' Town Attic location in Crossed door shuffle.
|
||||||
if world.doorShuffle[player] in ['crossed']:
|
if world.doorShuffle[player] in ['crossed']:
|
||||||
attic_hint = world.get_location("Thieves' Town - Attic", player).parent_region.dungeon.name
|
attic_hint = world.get_location("Thieves' Town - Attic", player).parent_region.dungeon.name
|
||||||
this_hint = 'A cracked floor can be found in ' + attic_hint + '.'
|
this_hint = 'A cracked floor can be found in ' + attic_hint + '.'
|
||||||
@@ -2300,7 +2300,7 @@ HintLocations = ['telepathic_tile_eastern_palace',
|
|||||||
'telepathic_tile_castle_tower',
|
'telepathic_tile_castle_tower',
|
||||||
'telepathic_tile_ice_large_room',
|
'telepathic_tile_ice_large_room',
|
||||||
'telepathic_tile_turtle_rock',
|
'telepathic_tile_turtle_rock',
|
||||||
'telepathic_tile_ice_entrace',
|
'telepathic_tile_ice_entrance',
|
||||||
'telepathic_tile_ice_stalfos_knights_room',
|
'telepathic_tile_ice_stalfos_knights_room',
|
||||||
'telepathic_tile_tower_of_hera_entrance',
|
'telepathic_tile_tower_of_hera_entrance',
|
||||||
'telepathic_tile_south_east_darkworld_cave',
|
'telepathic_tile_south_east_darkworld_cave',
|
||||||
@@ -2313,14 +2313,15 @@ HintLocations = ['telepathic_tile_eastern_palace',
|
|||||||
InconvenientLocations = ['Spike Cave',
|
InconvenientLocations = ['Spike Cave',
|
||||||
'Sahasrahla',
|
'Sahasrahla',
|
||||||
'Purple Chest',
|
'Purple Chest',
|
||||||
'Swamp Left',
|
|
||||||
'Mire Left',
|
|
||||||
'Tower of Hera - Big Key Chest',
|
'Tower of Hera - Big Key Chest',
|
||||||
|
'Magic Bat']
|
||||||
|
|
||||||
|
InconvenientDungeonLocations = ['Swamp Left',
|
||||||
|
'Mire Left',
|
||||||
'Eastern Palace - Big Key Chest',
|
'Eastern Palace - Big Key Chest',
|
||||||
'Thieves\' Town - Big Chest',
|
'Thieves\' Town - Big Chest',
|
||||||
'Ice Palace - Big Chest',
|
'Ice Palace - Big Chest',
|
||||||
'Ganons Tower - Big Chest',
|
'Ganons Tower - Big Chest']
|
||||||
'Magic Bat']
|
|
||||||
|
|
||||||
InconvenientVanillaLocations = ['Graveyard Cave',
|
InconvenientVanillaLocations = ['Graveyard Cave',
|
||||||
'Mimic Cave']
|
'Mimic Cave']
|
||||||
|
|||||||
+5
-1
@@ -251,6 +251,9 @@ class Room(object):
|
|||||||
self.doorList = []
|
self.doorList = []
|
||||||
self.modified = False
|
self.modified = False
|
||||||
|
|
||||||
|
def kind(self, door):
|
||||||
|
return self.doorList[door.doorListPos][1]
|
||||||
|
|
||||||
def door(self, pos, kind):
|
def door(self, pos, kind):
|
||||||
self.doorList.append((pos, kind))
|
self.doorList.append((pos, kind))
|
||||||
return self
|
return self
|
||||||
@@ -299,10 +302,11 @@ class Room(object):
|
|||||||
|
|
||||||
|
|
||||||
class PairedDoor(object):
|
class PairedDoor(object):
|
||||||
def __init__(self, door_a, door_b):
|
def __init__(self, door_a, door_b, original=False):
|
||||||
self.door_a = door_a
|
self.door_a = door_a
|
||||||
self.door_b = door_b
|
self.door_b = door_b
|
||||||
self.pair = True
|
self.pair = True
|
||||||
|
self.original = original
|
||||||
|
|
||||||
def address_a(self, world, player):
|
def address_a(self, world, player):
|
||||||
d = world.check_for_door(self.door_a, player)
|
d = world.check_for_door(self.door_a, player)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import collections
|
|
||||||
import logging
|
import logging
|
||||||
from BaseClasses import CollectionState
|
from BaseClasses import CollectionState, RegionType
|
||||||
from Regions import key_only_locations
|
from Regions import key_only_locations
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world, player):
|
def set_rules(world, player):
|
||||||
@@ -168,6 +168,7 @@ def global_rules(world, player):
|
|||||||
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player))
|
||||||
|
|
||||||
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
|
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
|
||||||
|
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
|
||||||
|
|
||||||
set_rule(world.get_entrance('PoD Arena Bonk Path', player), lambda state: state.has_Boots(player))
|
set_rule(world.get_entrance('PoD Arena Bonk Path', player), lambda state: state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('PoD Mimics 1 NW', player), lambda state: state.can_shoot_arrows(player))
|
||||||
@@ -302,7 +303,18 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('GT Mimics 2 WS', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('GT Mimics 2 WS', player), lambda state: state.can_shoot_arrows(player))
|
||||||
set_rule(world.get_entrance('GT Mimics 2 NE', player), lambda state: state.can_shoot_arrows(player))
|
set_rule(world.get_entrance('GT Mimics 2 NE', player), lambda state: state.can_shoot_arrows(player))
|
||||||
# consider access to refill room
|
# consider access to refill room
|
||||||
# consider can_kill_most_things to gauntlet
|
set_rule(world.get_entrance('GT Gauntlet 1 WN', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 2 EN', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 2 SW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 3 NW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 3 SW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 4 NW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 4 SW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 5 NW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Gauntlet 5 WS', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Wizzrobes 1 SW', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Wizzrobes 2 SE', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
set_rule(world.get_entrance('GT Wizzrobes 2 NE', player), lambda state: state.can_kill_most_things(player))
|
||||||
set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
set_rule(world.get_entrance('GT Lanmolas 2 ES', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||||
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||||
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
||||||
@@ -688,63 +700,59 @@ def no_glitches_rules(world, player):
|
|||||||
if (not world.dark_world_light_cone and check_is_dark_world(world.get_region(region, player))) or (not world.light_world_light_cone and not check_is_dark_world(world.get_region(region, player))):
|
if (not world.dark_world_light_cone and check_is_dark_world(world.get_region(region, player))) or (not world.light_world_light_cone and not check_is_dark_world(world.get_region(region, player))):
|
||||||
add_lamp_requirement(spot, player)
|
add_lamp_requirement(spot, player)
|
||||||
|
|
||||||
add_conditional_lamp('TR Dark Ride Up Stairs', 'TR Dark Ride', 'Entrance')
|
dark_rooms = {
|
||||||
add_conditional_lamp('TR Dark Ride SW', 'TR Dark Ride', 'Entrance')
|
'TR Dark Ride': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride SW'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Dark Shooters Up Stairs', 'Mire Dark Shooters', 'Entrance')
|
'Mire Dark Shooters': {'sewer': False, 'entrances': ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Dark Shooters SW', 'Mire Dark Shooters', 'Entrance')
|
'Mire Key Rupees': {'sewer': False, 'entrances': ['Mire Key Rupees NE'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Dark Shooters SE', 'Mire Dark Shooters', 'Entrance')
|
'Mire Block X': {'sewer': False, 'entrances': ['Mire Block X NW', 'Mire Block X WS'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Key Rupees NE', 'Mire Key Rupees', 'Entrance')
|
'Mire Tall Dark and Roomy': {'sewer': False, 'entrances': ['Mire Tall Dark and Roomy ES', 'Mire Tall Dark and Roomy WS', 'Mire Tall Dark and Roomy WN'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Block X NW', 'Mire Block X', 'Entrance')
|
'Mire Crystal Right': {'sewer': False, 'entrances': ['Mire Crystal Right ES'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Block X WS', 'Mire Block X', 'Entrance')
|
'Mire Crystal Mid': {'sewer': False, 'entrances': ['Mire Crystal Mid NW'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Tall Dark and Roomy ES', 'Mire Tall Dark and Roomy', 'Entrance')
|
'Mire Crystal Left': {'sewer': False, 'entrances': ['Mire Crystal Left WS'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Tall Dark and Roomy WS', 'Mire Tall Dark and Roomy', 'Entrance')
|
'Mire Crystal Top': {'sewer': False, 'entrances': ['Mire Crystal Top SW'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Tall Dark and Roomy WN', 'Mire Tall Dark and Roomy', 'Entrance')
|
'Mire Shooter Rupees': {'sewer': False, 'entrances': ['Mire Shooter Rupees EN'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Crystal Right ES', 'Mire Crystal Right', 'Entrance')
|
'PoD Dark Alley': {'sewer': False, 'entrances': ['PoD Dark Alley NE'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Crystal Mid NW', 'Mire Crystal Mid', 'Entrance')
|
'PoD Callback': {'sewer': False, 'entrances': ['PoD Callback WS', 'PoD Callback Warp'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Crystal Left WS', 'Mire Crystal Left', 'Entrance')
|
'PoD Turtle Party': {'sewer': False, 'entrances': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Crystal Top SW', 'Mire Crystal Top', 'Entrance')
|
'PoD Lonely Turtle': {'sewer': False, 'entrances': ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN'], 'locations': []},
|
||||||
add_conditional_lamp('Mire Shooter Rupees EN', 'Mire Shooter Rupees', 'Entrance')
|
'PoD Dark Pegs': {'sewer': False, 'entrances': ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs WN'], 'locations': []},
|
||||||
add_conditional_lamp('PoD Dark Alley NE', 'PoD Dark Alley', 'Entrance')
|
'PoD Dark Basement': {'sewer': False, 'entrances': ['PoD Dark Basement W Up Stairs', 'PoD Dark Basement E Up Stairs'], 'locations': ['Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right']},
|
||||||
add_conditional_lamp('PoD Callback WS', 'PoD Callback', 'Entrance')
|
'PoD Dark Maze': {'sewer': False, 'entrances': ['PoD Dark Maze EN', 'PoD Dark Maze E'], 'locations': ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom']},
|
||||||
add_conditional_lamp('PoD Callback Warp', 'PoD Callback', 'Entrance')
|
'Eastern Dark Square': {'sewer': False, 'entrances': ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN', 'Eastern Dark Square EN'], 'locations': []},
|
||||||
add_conditional_lamp('PoD Turtle Party ES', 'PoD Turtle Party', 'Entrance')
|
'Eastern Dark Pots': {'sewer': False, 'entrances': ['Eastern Dark Pots WN'], 'locations': ['Eastern Palace - Dark Square Pot Key']},
|
||||||
add_conditional_lamp('PoD Turtle Party NW', 'PoD Turtle Party', 'Entrance')
|
'Eastern Darkness': {'sewer': False, 'entrances': ['Eastern Darkness S', 'Eastern Darkness Up Stairs', 'Eastern Darkness NE'], 'locations': ['Eastern Palace - Dark Eyegore Key Drop']},
|
||||||
add_conditional_lamp('PoD Lonely Turtle SW', 'PoD Lonely Turtle', 'Entrance')
|
'Eastern Rupees': {'sewer': False, 'entrances': ['Eastern Rupees SE'], 'locations': []},
|
||||||
add_conditional_lamp('PoD Lonely Turtle EN', 'PoD Lonely Turtle', 'Entrance')
|
'Tower Lone Statue': {'sewer': False, 'entrances': ['Tower Lone Statue Down Stairs', 'Tower Lone Statue WN'], 'locations': []},
|
||||||
add_conditional_lamp('PoD Dark Pegs Up Ladder', 'PoD Dark Pegs', 'Entrance')
|
'Tower Dark Maze': {'sewer': False, 'entrances': ['Tower Dark Maze EN', 'Tower Dark Maze ES'], 'locations': ['Castle Tower - Dark Maze']},
|
||||||
add_conditional_lamp('PoD Dark Pegs WN', 'PoD Dark Pegs', 'Entrance')
|
'Tower Dark Chargers': {'sewer': False, 'entrances': ['Tower Dark Chargers WS', 'Tower Dark Chargers Up Stairs'], 'locations': []},
|
||||||
add_conditional_lamp('PoD Dark Basement W Up Stairs', 'PoD Dark Basement', 'Entrance')
|
'Tower Dual Statues': {'sewer': False, 'entrances': ['Tower Dual Statues Down Stairs', 'Tower Dual Statues WS'], 'locations': []},
|
||||||
add_conditional_lamp('PoD Dark Basement E Up Stairs', 'PoD Dark Basement', 'Entrance')
|
'Tower Dark Pits': {'sewer': False, 'entrances': ['Tower Dark Pits ES', 'Tower Dark Pits EN'], 'locations': []},
|
||||||
add_conditional_lamp('PoD Dark Maze EN', 'PoD Dark Maze', 'Entrance')
|
'Tower Dark Archers': {'sewer': False, 'entrances': ['Tower Dark Archers WN', 'Tower Dark Archers Up Stairs'], 'locations': ['Castle Tower - Dark Archer Key Drop']},
|
||||||
add_conditional_lamp('PoD Dark Maze E', 'PoD Dark Maze', 'Entrance')
|
'Sewers Dark Cross': {'sewer': True, 'entrances': ['Sewers Dark Cross Key Door N', 'Sewers Dark Cross South Stairs'], 'locations': ['Sewers - Dark Cross']},
|
||||||
add_conditional_lamp('Palace of Darkness - Dark Basement - Left', 'PoD Dark Basement', 'Location')
|
'Sewers Behind Tapestry': {'sewer': True, 'entrances': ['Sewers Behind Tapestry S', 'Sewers Behind Tapestry Down Stairs'], 'locations': []},
|
||||||
add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'PoD Dark Basement', 'Location')
|
'Sewers Rope Room': {'sewer': True, 'entrances': ['Sewers Rope Room Up Stairs', 'Sewers Rope Room North Stairs'], 'locations': []},
|
||||||
add_conditional_lamp('Palace of Darkness - Dark Maze - Top', 'PoD Dark Maze', 'Location')
|
'Sewers Water': {'sewer': True, 'entrances': ['Sewers Dark Cross Key Door S', 'Sewers Water W'], 'locations': []},
|
||||||
add_conditional_lamp('Palace of Darkness - Dark Maze - Bottom', 'PoD Dark Maze', 'Location')
|
'Sewers Key Rat': {'sewer': True, 'entrances': ['Sewers Key Rat E', 'Sewers Key Rat Key Door N'], 'locations': ['Hyrule Castle - Key Rat Key Drop']},
|
||||||
add_conditional_lamp('Eastern Dark Square NW', 'Eastern Dark Square', 'Entrance')
|
}
|
||||||
add_conditional_lamp('Eastern Dark Square Key Door WN', 'Eastern Dark Square', 'Entrance')
|
|
||||||
add_conditional_lamp('Eastern Dark Square EN', 'Eastern Dark Square', 'Entrance')
|
dark_debug_set = set()
|
||||||
add_conditional_lamp('Eastern Dark Pots WN', 'Eastern Dark Pots', 'Entrance')
|
for region, info in dark_rooms.items():
|
||||||
add_conditional_lamp('Eastern Darkness S', 'Eastern Darkness', 'Entrance')
|
is_dark = False
|
||||||
add_conditional_lamp('Eastern Darkness Up Stairs', 'Eastern Darkness', 'Entrance')
|
if not world.sewer_light_cone[player]:
|
||||||
add_conditional_lamp('Eastern Darkness NE', 'Eastern Darkness', 'Entrance')
|
is_dark = True
|
||||||
add_conditional_lamp('Eastern Rupees SE', 'Eastern Rupees', 'Entrance')
|
elif world.doorShuffle[player] != 'crossed' and not info['sewer']:
|
||||||
add_conditional_lamp('Eastern Palace - Dark Square Pot Key', 'Eastern Dark Square', 'Location')
|
is_dark = True
|
||||||
add_conditional_lamp('Eastern Palace - Dark Eyegore Key Drop', 'Eastern Darkness', 'Location')
|
elif world.doorShuffle[player] == 'crossed':
|
||||||
add_conditional_lamp('Tower Lone Statue Down Stairs', 'Tower Lone Statue', 'Entrance')
|
sewer_builder = world.dungeon_layouts[player]['Hyrule Castle']
|
||||||
add_conditional_lamp('Tower Lone Statue WN', 'Tower Lone Statue', 'Entrance')
|
is_dark = region not in sewer_builder.master_sector.region_set()
|
||||||
add_conditional_lamp('Tower Dark Maze EN', 'Tower Dark Maze', 'Entrance')
|
if is_dark:
|
||||||
add_conditional_lamp('Tower Dark Maze ES', 'Tower Dark Maze', 'Entrance')
|
dark_debug_set.add(region)
|
||||||
add_conditional_lamp('Tower Dark Chargers WS', 'Tower Dark Chargers', 'Entrance')
|
for ent in info['entrances']:
|
||||||
add_conditional_lamp('Tower Dark Chargers Up Stairs', 'Tower Dark Chargers', 'Entrance')
|
add_conditional_lamp(ent, region, 'Entrance')
|
||||||
add_conditional_lamp('Tower Dual Statues Down Stairs', 'Tower Dual Statues', 'Entrance')
|
for loc in info['locations']:
|
||||||
add_conditional_lamp('Tower Dual Statues WS', 'Tower Dual Statues', 'Entrance')
|
add_conditional_lamp(loc, region, 'Location')
|
||||||
add_conditional_lamp('Tower Dark Pits ES', 'Tower Dark Pits', 'Entrance')
|
logging.getLogger('').debug('Non Dark Regions: ' + ', '.join(set(dark_rooms.keys()).difference(dark_debug_set)))
|
||||||
add_conditional_lamp('Tower Dark Pits EN', 'Tower Dark Pits', 'Entrance')
|
|
||||||
add_conditional_lamp('Tower Dark Archers WN', 'Tower Dark Archers', 'Entrance')
|
|
||||||
add_conditional_lamp('Tower Dark Archers Up Stairs', 'Tower Dark Archers', 'Entrance')
|
|
||||||
add_conditional_lamp('Castle Tower - Dark Maze', 'Tower Dark Maze', 'Location')
|
|
||||||
add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Tower Dark Archers', 'Location')
|
|
||||||
add_conditional_lamp('Old Man', 'Old Man Cave', 'Location')
|
add_conditional_lamp('Old Man', 'Old Man Cave', 'Location')
|
||||||
add_conditional_lamp('Old Man Cave Exit (East)', 'Old Man Cave', 'Entrance')
|
add_conditional_lamp('Old Man Cave Exit (East)', 'Old Man Cave', 'Entrance')
|
||||||
add_conditional_lamp('Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave', 'Entrance')
|
add_conditional_lamp('Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave', 'Entrance')
|
||||||
@@ -752,19 +760,6 @@ def no_glitches_rules(world, player):
|
|||||||
add_conditional_lamp('Old Man House Front to Back', 'Old Man House', 'Entrance')
|
add_conditional_lamp('Old Man House Front to Back', 'Old Man House', 'Entrance')
|
||||||
add_conditional_lamp('Old Man House Back to Front', 'Old Man House', 'Entrance')
|
add_conditional_lamp('Old Man House Back to Front', 'Old Man House', 'Entrance')
|
||||||
|
|
||||||
if not world.sewer_light_cone[player]:
|
|
||||||
add_lamp_requirement(world.get_location('Sewers - Dark Cross', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Behind Tapestry S', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Behind Tapestry Down Stairs', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Rope Room Up Stairs', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Rope Room North Stairs', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Dark Cross South Stairs', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Dark Cross Key Door N', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Dark Cross Key Door S', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Water W', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Key Rat E', player), player)
|
|
||||||
add_lamp_requirement(world.get_entrance('Sewers Key Rat Key Door N', player), player)
|
|
||||||
|
|
||||||
|
|
||||||
def open_rules(world, player):
|
def open_rules(world, player):
|
||||||
# softlock protection as you can reach the sewers small key door with a guard drop key
|
# softlock protection as you can reach the sewers small key door with a guard drop key
|
||||||
@@ -795,8 +790,40 @@ def swordless_rules(world, player):
|
|||||||
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
|
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player))
|
||||||
|
|
||||||
|
|
||||||
|
std_kill_rooms = {
|
||||||
|
'Hyrule Dungeon Armory Main': ['Hyrule Dungeon Armory S'],
|
||||||
|
'Hyrule Dungeon Armory Boomerang': ['Hyrule Dungeon Armory Boomerang WS'],
|
||||||
|
'Eastern Stalfos Spawn': ['Eastern Stalfos Spawn ES', 'Eastern Stalfos Spawn NW'],
|
||||||
|
'Desert Compass Room': ['Desert Compass NW'],
|
||||||
|
'Desert Four Statues': ['Desert Four Statues NW', 'Desert Four Statues ES'],
|
||||||
|
'Hera Beetles': ['Hera Beetles WS'],
|
||||||
|
'Tower Gold Knights': ['Tower Gold Knights SW', 'Tower Gold Knights EN'],
|
||||||
|
'Tower Dark Archers': ['Tower Dark Archers WN'],
|
||||||
|
'Tower Red Spears': ['Tower Red Spears WN'],
|
||||||
|
'Tower Red Guards': ['Tower Red Guards EN', 'Tower Red Guards SW'],
|
||||||
|
'Tower Circle of Pots': ['Tower Circle of Pots NW'],
|
||||||
|
'PoD Turtle Party': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], # todo: hammer req. in main rules
|
||||||
|
'Thieves Basement Block': ['Thieves Basement Block WN'],
|
||||||
|
'Ice Stalfos Hint': ['Ice Stalfos Hint SE'],
|
||||||
|
'Ice Pengator Trap': ['Ice Pengator Trap NE'],
|
||||||
|
'Mire 2': ['Mire 2 NE'],
|
||||||
|
'Mire Cross': ['Mire Cross ES'],
|
||||||
|
'TR Twin Pokeys': ['TR Twin Pokeys EN', 'TR Twin Pokeys SW'],
|
||||||
|
'GT Petting Zoo': ['GT Petting Zoo SE'],
|
||||||
|
'GT DMs Room': ['GT DMs Room SW'],
|
||||||
|
'GT Gauntlet 1': ['GT Gauntlet 1 WN'],
|
||||||
|
'GT Gauntlet 2': ['GT Gauntlet 2 EN', 'GT Gauntlet 2 SW'],
|
||||||
|
'GT Gauntlet 3': ['GT Gauntlet 3 NW', 'GT Gauntlet 3 SW'],
|
||||||
|
'GT Gauntlet 4': ['GT Gauntlet 4 NW', 'GT Gauntlet 4 SW'],
|
||||||
|
'GT Gauntlet 5': ['GT Gauntlet 5 NW', 'GT Gauntlet 5 WS'],
|
||||||
|
'GT Wizzrobes 1': ['GT Wizzrobes 1 SW'],
|
||||||
|
'GT Wizzrobes 2': ['GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE']
|
||||||
|
} # all trap rooms?
|
||||||
|
|
||||||
|
|
||||||
def standard_rules(world, player):
|
def standard_rules(world, player):
|
||||||
# these are because of rails
|
# these are because of rails
|
||||||
|
if world.shuffle[player] != 'vanilla':
|
||||||
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
|
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.has('Zelda Delivered', player))
|
set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
|
|
||||||
@@ -815,21 +842,21 @@ def standard_rules(world, player):
|
|||||||
add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player))
|
add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player))
|
||||||
add_rule(world.get_location('Secret Passage', player), lambda state: state.can_kill_most_things(player))
|
add_rule(world.get_location('Secret Passage', player), lambda state: state.can_kill_most_things(player))
|
||||||
|
|
||||||
# todo: in crossed these chest/key drops are not necessarily present
|
escape_builder = world.dungeon_layouts[player]['Hyrule Castle']
|
||||||
add_rule(world.get_location('Hyrule Castle - Map Chest', player), lambda state: state.can_kill_most_things(player))
|
for region in escape_builder.master_sector.regions:
|
||||||
set_rule(world.get_location('Sewers - Dark Cross', player), lambda state: state.can_kill_most_things(player))
|
for loc in region.locations:
|
||||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.can_kill_most_things(player))
|
add_rule(loc, lambda state: state.can_kill_most_things(player))
|
||||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.can_kill_most_things(player))
|
if region.name in std_kill_rooms:
|
||||||
|
for ent in std_kill_rooms[region.name]:
|
||||||
|
add_rule(world.get_entrance(ent, player), lambda state: state.can_kill_most_things(player))
|
||||||
|
|
||||||
set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player), lambda state: state.can_kill_most_things(player))
|
|
||||||
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: state.can_kill_most_things(player))
|
|
||||||
set_rule(world.get_location('Hyrule Castle - Key Rat Key Drop', player), lambda state: state.can_kill_most_things(player))
|
|
||||||
set_rule(world.get_entrance('Hyrule Dungeon Armory S', player), lambda state: state.can_kill_most_things(player))
|
|
||||||
|
|
||||||
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player), lambda state: state.can_kill_most_things(player))
|
|
||||||
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
|
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
|
||||||
set_rule(world.get_entrance('Hyrule Castle Throne Room N', player), lambda state: state.has('Zelda Herself', player))
|
set_rule(world.get_entrance('Hyrule Castle Throne Room N', player), lambda state: state.has('Zelda Herself', player))
|
||||||
set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player))
|
|
||||||
|
def check_rule_list(state, r_list):
|
||||||
|
return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:])
|
||||||
|
rule_list, debug_path = find_rules_for_zelda_delivery(world, player)
|
||||||
|
set_rule(world.get_location('Zelda Drop Off', player), lambda state: state.has('Zelda Herself', player) and check_rule_list(state, rule_list))
|
||||||
|
|
||||||
for location in ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest']:
|
for location in ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest']:
|
||||||
add_rule(world.get_location(location, player), lambda state: state.has('Zelda Delivered', player))
|
add_rule(world.get_location(location, player), lambda state: state.has('Zelda Delivered', player))
|
||||||
@@ -853,6 +880,31 @@ def standard_rules(world, player):
|
|||||||
add_rule(world.get_entrance(entrance, player), lambda state: state.has('Zelda Delivered', player))
|
add_rule(world.get_entrance(entrance, player), lambda state: state.has('Zelda Delivered', player))
|
||||||
|
|
||||||
|
|
||||||
|
def find_rules_for_zelda_delivery(world, player):
|
||||||
|
# path rules for backtracking
|
||||||
|
start_region = world.get_region('Hyrule Dungeon Cellblock', player)
|
||||||
|
queue = deque([(start_region, [], [])])
|
||||||
|
visited = {start_region}
|
||||||
|
blank_state = CollectionState(world)
|
||||||
|
while len(queue) > 0:
|
||||||
|
region, path_rules, path = queue.popleft()
|
||||||
|
for ext in region.exits:
|
||||||
|
connect = ext.connected_region
|
||||||
|
if connect and connect.type == RegionType.Dungeon and connect not in visited:
|
||||||
|
rule = ext.access_rule
|
||||||
|
rule_list = list(path_rules)
|
||||||
|
next_path = list(path)
|
||||||
|
if not rule(blank_state):
|
||||||
|
rule_list.append(rule)
|
||||||
|
next_path.append(ext.name)
|
||||||
|
if connect.name == 'Sanctuary':
|
||||||
|
return rule_list, next_path
|
||||||
|
else:
|
||||||
|
visited.add(connect)
|
||||||
|
queue.append((connect, rule_list, next_path))
|
||||||
|
raise Exception('No path to Sanctuary found')
|
||||||
|
|
||||||
|
|
||||||
def set_big_bomb_rules(world, player):
|
def set_big_bomb_rules(world, player):
|
||||||
# this is a mess
|
# this is a mess
|
||||||
bombshop_entrance = world.get_region('Big Bomb Shop', player).entrances[0]
|
bombshop_entrance = world.get_region('Big Bomb Shop', player).entrances[0]
|
||||||
@@ -1291,7 +1343,7 @@ def set_bunny_rules(world, player):
|
|||||||
# a) being able to reach it, and
|
# a) being able to reach it, and
|
||||||
# b) being able to access all entrances from there to `region`
|
# b) being able to access all entrances from there to `region`
|
||||||
seen = set([region])
|
seen = set([region])
|
||||||
queue = collections.deque([(region, [])])
|
queue = deque([(region, [])])
|
||||||
while queue:
|
while queue:
|
||||||
(current, path) = queue.popleft()
|
(current, path) = queue.popleft()
|
||||||
for entrance in current.entrances:
|
for entrance in current.entrances:
|
||||||
@@ -1367,7 +1419,7 @@ def set_inverted_bunny_rules(world, player):
|
|||||||
# a) being able to reach it, and
|
# a) being able to reach it, and
|
||||||
# b) being able to access all entrances from there to `region`
|
# b) being able to access all entrances from there to `region`
|
||||||
seen = set([region])
|
seen = set([region])
|
||||||
queue = collections.deque([(region, [])])
|
queue = deque([(region, [])])
|
||||||
while queue:
|
while queue:
|
||||||
(current, path) = queue.popleft()
|
(current, path) = queue.popleft()
|
||||||
for entrance in current.entrances:
|
for entrance in current.entrances:
|
||||||
|
|||||||
@@ -1651,7 +1651,7 @@ class TextTable(object):
|
|||||||
text['telepathic_tile_castle_tower'] = CompressedTextMapper.convert("{NOBORDER}\nYou can reflect Agahnim's energy with Sword, Bug-net or Hammer.")
|
text['telepathic_tile_castle_tower'] = CompressedTextMapper.convert("{NOBORDER}\nYou can reflect Agahnim's energy with Sword, Bug-net or Hammer.")
|
||||||
text['telepathic_tile_ice_large_room'] = CompressedTextMapper.convert("{NOBORDER}\nAll right stop collaborate and listen\nIce is back with my brand new invention")
|
text['telepathic_tile_ice_large_room'] = CompressedTextMapper.convert("{NOBORDER}\nAll right stop collaborate and listen\nIce is back with my brand new invention")
|
||||||
text['telepathic_tile_turtle_rock'] = CompressedTextMapper.convert("{NOBORDER}\nYou shall not pass… without the red cane")
|
text['telepathic_tile_turtle_rock'] = CompressedTextMapper.convert("{NOBORDER}\nYou shall not pass… without the red cane")
|
||||||
text['telepathic_tile_ice_entrace'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
||||||
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
||||||
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
||||||
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2018~~~\nS: Andy\n\n ~~~2017~~~\nA: ajneb174\nS: ajneb174")
|
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2018~~~\nS: Andy\n\n ~~~2017~~~\nA: ajneb174\nS: ajneb174")
|
||||||
|
|||||||
@@ -188,16 +188,16 @@ def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan)
|
|||||||
|
|
||||||
for ent, offset in entrance_offsets.items():
|
for ent, offset in entrance_offsets.items():
|
||||||
# print(ent)
|
# print(ent)
|
||||||
str = ent
|
string = ent
|
||||||
for dp, data in entrance_data.items():
|
for dp, data in entrance_data.items():
|
||||||
byte_array = []
|
byte_array = []
|
||||||
address, size = data
|
address, size = data
|
||||||
for i in range(0, size):
|
for i in range(0, size):
|
||||||
byte_array.append(old_rom_data[address+(offset*size)+i])
|
byte_array.append(old_rom_data[address+(offset*size)+i])
|
||||||
bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
|
some_bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
|
||||||
str += '\t'+bytes
|
string += '\t'+some_bytes
|
||||||
# print("%s: %s" % (dp, bytes))
|
# print("%s: %s" % (dp, bytes))
|
||||||
print(str)
|
print(string)
|
||||||
|
|
||||||
|
|
||||||
def print_wiki_doors(d_regions, world, player):
|
def print_wiki_doors(d_regions, world, player):
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ org $0DFA53
|
|||||||
jsl.l LampCheckOverride
|
jsl.l LampCheckOverride
|
||||||
org $028046 ; <- 10046 - Bank02.asm : 217 (JSL EnableForceBlank) (Start of Module_LoadFile)
|
org $028046 ; <- 10046 - Bank02.asm : 217 (JSL EnableForceBlank) (Start of Module_LoadFile)
|
||||||
jsl.l OnFileLoadOverride
|
jsl.l OnFileLoadOverride
|
||||||
|
org $07A93F ; < 3A93F - Bank07.asm 6548 (LDA $8A : AND.b #$40 - Mirror checks)
|
||||||
|
jsl.l MirrorCheckOverride
|
||||||
|
|
||||||
org $05ef47
|
org $05ef47
|
||||||
Sprite_HeartContainer_Override: ;sprite_heart_upgrades.asm : 96-100 (LDA $040C : CMP.b #$1A : BNE .not_in_ganons_tower)
|
Sprite_HeartContainer_Override: ;sprite_heart_upgrades.asm : 96-100 (LDA $040C : CMP.b #$1A : BNE .not_in_ganons_tower)
|
||||||
|
|||||||
@@ -37,4 +37,15 @@ OnFileLoadOverride:
|
|||||||
jsl OnFileLoad ; what I wrote over
|
jsl OnFileLoad ; what I wrote over
|
||||||
lda DRFlags : and #$80 : beq + ;flag is off
|
lda DRFlags : and #$80 : beq + ;flag is off
|
||||||
lda $7ef086 : ora #$80 : sta $7ef086
|
lda $7ef086 : ora #$80 : sta $7ef086
|
||||||
|
+ lda DRFlags : and #$02 : beq +
|
||||||
|
lda $7ef353 : bne +
|
||||||
|
lda #$01 : sta $7ef353
|
||||||
|
+ rtl
|
||||||
|
|
||||||
|
MirrorCheckOverride:
|
||||||
|
lda $8A : and #$40 ; what I wrote over
|
||||||
|
beq +
|
||||||
|
lda DRFlags : and #$02 : beq ++
|
||||||
|
lda $7ef353 : cmp #$01 : beq +
|
||||||
|
++ lda #$01
|
||||||
+ rtl
|
+ rtl
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user