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.inaccessible_regions = {}
|
||||
self.key_logic = {}
|
||||
self.pool_adjustment = {}
|
||||
|
||||
for player in range(1, players + 1):
|
||||
def set_player_attr(attr, val):
|
||||
@@ -1515,11 +1516,11 @@ class Spoiler(object):
|
||||
else:
|
||||
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:
|
||||
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:
|
||||
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):
|
||||
if self.world.players == 1:
|
||||
@@ -1625,7 +1626,8 @@ class Spoiler(object):
|
||||
'enemy_health': self.world.enemy_health,
|
||||
'enemy_damage': self.world.enemy_damage,
|
||||
'players': self.world.players,
|
||||
'teams': self.world.teams
|
||||
'teams': self.world.teams,
|
||||
'experimental' : self.world.experimental
|
||||
}
|
||||
|
||||
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 damage: %s\n' % self.metadata['enemy_damage'][player])
|
||||
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:
|
||||
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:
|
||||
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()]))
|
||||
|
||||
+196
-58
@@ -1,6 +1,5 @@
|
||||
import random
|
||||
import collections
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, deque
|
||||
import logging
|
||||
import operator as op
|
||||
import time
|
||||
@@ -11,7 +10,7 @@ from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBa
|
||||
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 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 RoomData import DoorKind, PairedDoor
|
||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, validate_tr
|
||||
@@ -54,8 +53,6 @@ def link_doors(world, player):
|
||||
within_dungeon(world, player)
|
||||
elif world.doorShuffle[player] == 'crossed':
|
||||
cross_dungeon(world, player)
|
||||
elif world.doorShuffle[player] == 'experimental':
|
||||
experiment(world, player)
|
||||
else:
|
||||
logging.getLogger('').error('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
|
||||
player_dungeons = [dungeon for dungeon in world.dungeons if dungeon.player == player]
|
||||
for dungeon in player_dungeons:
|
||||
queue = collections.deque(dungeon.regions)
|
||||
queue = deque(dungeon.regions)
|
||||
while len(queue) > 0:
|
||||
region = world.get_region(queue.popleft(), player)
|
||||
if region.name not in dungeon.regions:
|
||||
@@ -87,31 +84,42 @@ def mark_regions(world, player):
|
||||
|
||||
def create_door_spoiler(world, player):
|
||||
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:
|
||||
door_a = queue.popleft()
|
||||
if door_a.type in [DoorType.Normal, DoorType.SpiralStairs]:
|
||||
door_b = door_a.dest
|
||||
if door_b is not None:
|
||||
logger.debug('spoiler: %s connected to %s', door_a.name, door_b.name)
|
||||
if not door_a.blocked and not door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'both', player)
|
||||
elif door_a.blocked:
|
||||
world.spoiler.set_door(door_b.name, door_a.name, 'entrance', player)
|
||||
elif door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'entrance', player)
|
||||
else:
|
||||
logger.warning('This is a bug')
|
||||
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:
|
||||
logger.warning('Door not connected: %s', door_a.name)
|
||||
builder = queue.popleft()
|
||||
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
|
||||
if door_b:
|
||||
done.add(door_b)
|
||||
if not door_a.blocked and not door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'both', player, builder.name)
|
||||
elif door_a.blocked:
|
||||
world.spoiler.set_door(door_b.name, door_a.name, 'entrance', player, builder.name)
|
||||
elif door_b.blocked:
|
||||
world.spoiler.set_door(door_a.name, door_b.name, 'entrance', player, builder.name)
|
||||
else:
|
||||
logger.warning('This is a bug during door spoiler')
|
||||
else:
|
||||
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):
|
||||
builders = []
|
||||
world.dungeon_layouts[player] = {}
|
||||
for dungeon in [dungeon for dungeon in world.dungeons if dungeon.player == player]:
|
||||
sector = Sector()
|
||||
sector.name = dungeon.name
|
||||
@@ -119,12 +127,13 @@ def vanilla_key_logic(world, player):
|
||||
builder = simple_dungeon_builder(sector.name, [sector])
|
||||
builder.master_sector = sector
|
||||
builders.append(builder)
|
||||
world.dungeon_layouts[player][builder.name] = builder
|
||||
|
||||
overworld_prep(world, player)
|
||||
entrances_map, potentials, connections = determine_entrance_list(world, player)
|
||||
|
||||
enabled_entrances = {}
|
||||
sector_queue = collections.deque(builders)
|
||||
sector_queue = deque(builders)
|
||||
last_key = None
|
||||
while len(sector_queue) > 0:
|
||||
builder = sector_queue.popleft()
|
||||
@@ -313,6 +322,7 @@ def within_dungeon(world, player):
|
||||
for builder in world.dungeon_layouts[player].values():
|
||||
shuffle_key_doors(builder, world, player)
|
||||
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):
|
||||
@@ -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):
|
||||
entrances_map, potentials, connections = connections_tuple
|
||||
enabled_entrances = {}
|
||||
sector_queue = collections.deque(dungeon_builders.values())
|
||||
sector_queue = deque(dungeon_builders.values())
|
||||
last_key = None
|
||||
while len(sector_queue) > 0:
|
||||
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):
|
||||
enabled[potential] = (region.name, region.dungeon)
|
||||
# see if this unexplored region connects elsewhere
|
||||
queue = collections.deque(new_region.exits)
|
||||
queue = deque(new_region.exits)
|
||||
visited = set()
|
||||
while len(queue) > 0:
|
||||
ext = queue.popleft()
|
||||
@@ -671,6 +681,33 @@ def cross_dungeon(world, player):
|
||||
gt = world.get_dungeon('Ganons Tower', player)
|
||||
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()
|
||||
total_keys = remaining = 29
|
||||
total_candidates = 0
|
||||
@@ -688,6 +725,7 @@ def cross_dungeon(world, player):
|
||||
total_candidates += builder.key_doors_num
|
||||
start_regions_map[name] = start_regions
|
||||
|
||||
|
||||
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
||||
for name, builder in dungeon_builders.items():
|
||||
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
|
||||
builder_order = [x for x in dungeon_builders.values() if x.flex > 0]
|
||||
builder_order.sort(key=lambda b: b.combo_size)
|
||||
queue = collections.deque(builder_order)
|
||||
queue = deque(builder_order)
|
||||
logger = logging.getLogger('')
|
||||
while len(queue) > 0 and remaining > 0:
|
||||
builder = queue.popleft()
|
||||
@@ -731,7 +769,7 @@ def cross_dungeon(world, player):
|
||||
if builder.flex > 0:
|
||||
builder.combo_size = ncr(len(builder.candidates), builder.key_doors_num)
|
||||
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:
|
||||
logger.info('Cross Dungeon: Increase failed for %s', name)
|
||||
builder.key_doors_num -= 1
|
||||
@@ -739,22 +777,15 @@ def cross_dungeon(world, player):
|
||||
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
||||
|
||||
# Last Step: Adjust Small Key Dungeon Pool
|
||||
for name, builder in dungeon_builders.items():
|
||||
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if actual_chest_keys == 0:
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||
if not world.retro[player]:
|
||||
for name, builder in dungeon_builders.items():
|
||||
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
||||
dungeon = world.get_dungeon(name, player)
|
||||
if actual_chest_keys == 0:
|
||||
dungeon.small_keys = []
|
||||
else:
|
||||
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)
|
||||
# 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):
|
||||
@@ -765,9 +796,12 @@ def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||
new_dungeon.bosses[boss_key] = gt_boss
|
||||
|
||||
|
||||
def experiment(world, player):
|
||||
# print_wiki_doors(dungeon_regions, world, player)
|
||||
cross_dungeon(world, player)
|
||||
def refine_hints(dungeon_builders):
|
||||
for name, builder in dungeon_builders.items():
|
||||
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):
|
||||
@@ -817,7 +851,7 @@ def convert_to_sectors(region_names, world, player):
|
||||
# those with split region starts like Desert/Skull combine for key layouts
|
||||
def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
|
||||
for recombine in recombinant_builders.values():
|
||||
queue = collections.deque(dungeon_builders.values())
|
||||
queue = deque(dungeon_builders.values())
|
||||
while len(queue) > 0:
|
||||
builder = queue.pop()
|
||||
if builder.name.startswith(recombine.name):
|
||||
@@ -865,8 +899,8 @@ def find_current_key_doors(builder, world, player):
|
||||
current_doors = []
|
||||
for region in builder.master_sector.regions:
|
||||
for ext in region.exits:
|
||||
d = world.check_for_door(ext.name, player)
|
||||
if d is not None and d.smallKey:
|
||||
d = ext.door
|
||||
if d and d.smallKey:
|
||||
current_doors.append(d)
|
||||
return current_doors
|
||||
|
||||
@@ -976,7 +1010,7 @@ def log_key_logic(d_name, key_logic):
|
||||
|
||||
def build_pair_list(flat_list):
|
||||
paired_list = []
|
||||
queue = collections.deque(flat_list)
|
||||
queue = deque(flat_list)
|
||||
while len(queue) > 0:
|
||||
d = queue.pop()
|
||||
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
|
||||
candidates = []
|
||||
checked_doors = list(checked)
|
||||
queue = collections.deque([(region, None, None)])
|
||||
queue = deque([(region, None, None)])
|
||||
while len(queue) > 0:
|
||||
current, last_door, last_region = queue.pop()
|
||||
for ext in current.exits:
|
||||
@@ -1061,7 +1095,7 @@ def ncr(n, r):
|
||||
def reassign_key_doors(builder, proposal, world, player):
|
||||
logger = logging.getLogger('')
|
||||
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:
|
||||
d = queue.pop()
|
||||
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)
|
||||
else:
|
||||
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:
|
||||
room.delete(d.doorListPos)
|
||||
d.smallKey = False
|
||||
@@ -1129,6 +1163,104 @@ def change_door_to_small_key(d, world, player):
|
||||
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):
|
||||
paths = {
|
||||
'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)
|
||||
all_regions = set([r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon])
|
||||
visited_regions = set()
|
||||
queue = collections.deque(regs)
|
||||
queue = deque(regs)
|
||||
while len(queue) > 0:
|
||||
next_region = queue.popleft()
|
||||
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:
|
||||
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)
|
||||
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':
|
||||
@@ -1302,6 +1439,7 @@ def check_for_pinball_fix(state, bad_region, world, player):
|
||||
@unique
|
||||
class DROptions(Flag):
|
||||
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
|
||||
|
||||
# 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 Sexy Statue W', 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 Down 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):
|
||||
world.paired_doors[player] = [
|
||||
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N'),
|
||||
PairedDoor('TR Pokey 2 ES', 'TR Lava Island WS'), # TR Pokey Key
|
||||
PairedDoor('TR Dodgers NE', 'TR Lava Escape SE'), # TR Big key door by pipes
|
||||
PairedDoor('PoD Falling Bridge WN', 'PoD Dark Maze EN'), # Pod Dark maze door
|
||||
PairedDoor('PoD Dark Maze E', 'PoD Big Chest Balcony W'), # PoD Bombable by Big Chest
|
||||
PairedDoor('PoD Arena Main NW', 'PoD Falling Bridge SW'), # Pod key door by bridge
|
||||
PairedDoor('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S'),
|
||||
PairedDoor('Swamp Hub WN', 'Swamp Crystal Switch EN'), # Swamp key door crystal switch
|
||||
PairedDoor('Swamp Hub North Ledge N', 'Swamp Push Statue S'), # Swamp key door above big chest
|
||||
PairedDoor('PoD Map Balcony WS', 'PoD Arena Ledge ES'), # Pod bombable by arena
|
||||
PairedDoor('Swamp Hub Dead Ledge EN', 'Swamp Hammer Switch WN'), # Swamp bombable to random pots
|
||||
PairedDoor('Swamp Pot Row WN', 'Swamp Map Ledge EN'), # Swamp bombable to map chest
|
||||
PairedDoor('Swamp Pot Row WS', 'Swamp Trench 1 Approach ES'), # Swamp key door early room $38
|
||||
PairedDoor('PoD Middle Cage N', 'PoD Pit Room S'),
|
||||
PairedDoor('GT Crystal Circles SW', 'GT Left Moldorm Ledge NW'), # GT moldorm key door
|
||||
PairedDoor('Ice Conveyor SW', 'Ice Bomb Jump NW'), # Ice BJ key door
|
||||
PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE'),
|
||||
PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW'), # Skull 3 key door
|
||||
PairedDoor('Skull 1 Lobby WS', 'Skull Pot Prison ES'), # Skull 1 key door - pot prison to big chest
|
||||
PairedDoor('Skull Map Room SE', 'Skull Pinball NE'), # Skull 1 - pinball key door
|
||||
PairedDoor('GT Dash Hall NE', 'GT Hidden Spikes SE'), # gt main big key door
|
||||
PairedDoor('Ice Spike Cross ES', 'Ice Spike Room WS'), # 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 Warp Maze (Rails) WS', 'GT Randomizer Room ES'), # 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('Eastern Courtyard N', 'Eastern Darkness S'),
|
||||
PairedDoor('Mire Fishbone SE', 'Mire Spike Barrier NE'), # mire fishbone key door
|
||||
PairedDoor('Mire BK Door Room N', 'Mire Left Bridge S'), # mire big key door to bridges
|
||||
PairedDoor('Eastern Big Key NE', 'Eastern Hint Tile Blocked Path SE'),
|
||||
PairedDoor('TR Hub NW', 'TR Pokey 1 SW'), # TR somaria hub to pokey
|
||||
PairedDoor('Eastern Dark Square Key Door WN', 'Eastern Cannonball Ledge Key Door EN'),
|
||||
PairedDoor('Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW'), # TT random bomb to pots
|
||||
PairedDoor('Thieves BK Corner NE', 'Thieves Hallway SE'), # TT big key door
|
||||
PairedDoor('Ice Switch Room ES', 'Ice Refill WS'), # Ice last key door to crystal switch
|
||||
PairedDoor('Mire Hub WS', 'Mire Conveyor Crystal ES'), # mire hub key door to attic
|
||||
PairedDoor('Mire Hub Right EN', 'Mire Map Spot WN'), # mire hub key door to map
|
||||
PairedDoor('TR Dash Bridge WS', 'TR Crystal Maze ES'), # tr last key door to switch maze
|
||||
PairedDoor('Thieves Ambush E', 'Thieves Rail Ledge W') # TT dashable above
|
||||
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N', True),
|
||||
PairedDoor('TR Pokey 2 ES', 'TR Lava Island WS', True), # TR Pokey Key
|
||||
PairedDoor('TR Dodgers NE', 'TR Lava Escape SE', True), # TR Big key door by pipes
|
||||
PairedDoor('PoD Falling Bridge WN', 'PoD Dark Maze EN', True), # Pod Dark maze door
|
||||
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', True), # Pod key door by bridge
|
||||
PairedDoor('Sewers Dark Cross Key Door N', 'Sewers Dark Cross Key Door S', True),
|
||||
PairedDoor('Swamp Hub WN', 'Swamp Crystal Switch EN', True), # Swamp key door crystal switch
|
||||
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', True), # Pod bombable by arena
|
||||
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', True), # Swamp bombable to map chest
|
||||
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', True),
|
||||
PairedDoor('GT Crystal Circles SW', 'GT Left Moldorm Ledge NW', True), # GT moldorm key door
|
||||
PairedDoor('Ice Conveyor SW', 'Ice Bomb Jump NW', True), # Ice BJ key door
|
||||
PairedDoor('Desert Tiles 2 SE', 'Desert Beamos Hall NE', True),
|
||||
PairedDoor('Skull 3 Lobby NW', 'Skull Star Pits SW', True), # Skull 3 key door
|
||||
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', True), # Skull 1 - pinball 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', True), # ice door to spike chest
|
||||
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', True), # gt bombable to rando room
|
||||
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', True),
|
||||
PairedDoor('Mire Fishbone SE', 'Mire Spike Barrier NE', True), # mire fishbone key door
|
||||
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', True),
|
||||
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', True),
|
||||
PairedDoor('Thieves Rail Ledge NW', 'Thieves Pot Alcove Bottom SW', True), # TT random bomb to pots
|
||||
PairedDoor('Thieves BK Corner NE', 'Thieves Hallway SE', True), # TT big key door
|
||||
PairedDoor('Ice Switch Room ES', 'Ice Refill WS', True), # Ice last key door to crystal switch
|
||||
PairedDoor('Mire Hub WS', 'Mire Conveyor Crystal ES', True), # mire hub key door to attic
|
||||
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', True), # tr last key door to switch maze
|
||||
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 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='''\
|
||||
Select Door Shuffling Algorithm. (default: %(default)s)
|
||||
Basic: Doors are mixed within a single dungeon.
|
||||
@@ -182,8 +182,8 @@ def parse_arguments(argv, no_defaults=False):
|
||||
(Not yet implemented)
|
||||
Vanilla: All doors are connected the same way they were in the
|
||||
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'],
|
||||
help='''\
|
||||
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',
|
||||
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||
'retro', 'accessibility', 'hints', 'beemizer',
|
||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||
'remote_items']:
|
||||
|
||||
+16
@@ -380,3 +380,19 @@ dungeon_bigs = {
|
||||
'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
|
||||
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
|
||||
|
||||
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)
|
||||
doorShuffleVar = StringVar()
|
||||
doorShuffleVar.set('basic')
|
||||
doorShuffleOptionMenu = OptionMenu(doorShuffleFrame, doorShuffleVar, 'vanilla', 'basic', 'crossed', 'experimental')
|
||||
doorShuffleOptionMenu = OptionMenu(doorShuffleFrame, doorShuffleVar, 'vanilla', 'basic', 'crossed')
|
||||
doorShuffleOptionMenu.pack(side=RIGHT)
|
||||
doorShuffleLabel = Label(doorShuffleFrame, text='Door shuffle algorithm')
|
||||
doorShuffleLabel.pack(side=LEFT)
|
||||
|
||||
+19
-7
@@ -58,7 +58,7 @@ difficulties = {
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
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],
|
||||
progressive_sword_limit = 4,
|
||||
progressive_shield_limit = 3,
|
||||
@@ -85,7 +85,7 @@ difficulties = {
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
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],
|
||||
progressive_sword_limit = 3,
|
||||
progressive_shield_limit = 2,
|
||||
@@ -112,7 +112,7 @@ difficulties = {
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
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],
|
||||
progressive_sword_limit = 2,
|
||||
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)
|
||||
world.rupoor_cost = min(world.customitemarray[69], 9999)
|
||||
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:
|
||||
world.push_precollected(ItemFactory(item, player))
|
||||
@@ -406,7 +415,7 @@ def set_up_shops(world, player):
|
||||
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 = []
|
||||
placed_items = {}
|
||||
precollected_items = []
|
||||
@@ -525,8 +534,11 @@ 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.extend(diff.retro)
|
||||
if mode == 'standard':
|
||||
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)')
|
||||
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'])
|
||||
place_item(key_location, 'Small Key (Universal)')
|
||||
else:
|
||||
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)
|
||||
|
||||
+3
-2
@@ -806,7 +806,8 @@ def reduce_rules(small_rules, collected, collected_alt):
|
||||
|
||||
# Soft lock stuff
|
||||
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
|
||||
flat_proposal = key_layout.flat_prop
|
||||
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:
|
||||
state.visit_region(region, key_checks=True)
|
||||
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):
|
||||
|
||||
@@ -23,7 +23,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||
from Utils import output_path, parse_player_names
|
||||
|
||||
__version__ = '0.0.12pre'
|
||||
__version__ = '0.0.13pre'
|
||||
|
||||
|
||||
def main(args, seed=None):
|
||||
@@ -56,6 +56,7 @@ def main(args, seed=None):
|
||||
world.enemy_health = args.enemy_health.copy()
|
||||
world.enemy_damage = args.enemy_damage.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)}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = 'c06e14396839bc443a6918e736f1e5a7'
|
||||
RANDOMIZERBASEHASH = '818b2c659a610cb4112804cf612ffd37'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -591,7 +591,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
patch_shuffled_dark_sanc(world, rom, player)
|
||||
|
||||
# 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':
|
||||
rom.write_byte(0x139004, 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.
|
||||
locations_to_hint = InconvenientLocations.copy()
|
||||
if world.doorShuffle[player] != 'crossed':
|
||||
locations_to_hint.extend(InconvenientDungeonLocations)
|
||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
locations_to_hint.extend(InconvenientVanillaLocations)
|
||||
random.shuffle(locations_to_hint)
|
||||
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:]
|
||||
for location in locations_to_hint:
|
||||
if location == 'Swamp Left':
|
||||
@@ -1733,20 +1736,17 @@ def write_strings(rom, world, player, team):
|
||||
items_to_hint.extend(BigKeys)
|
||||
random.shuffle(items_to_hint)
|
||||
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:
|
||||
this_item = items_to_hint.pop(0)
|
||||
this_location = world.find_items_not_key_only(this_item, player)
|
||||
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:
|
||||
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
|
||||
tt[hint_locations.pop(0)] = this_hint
|
||||
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']:
|
||||
attic_hint = world.get_location("Thieves' Town - Attic", player).parent_region.dungeon.name
|
||||
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_ice_large_room',
|
||||
'telepathic_tile_turtle_rock',
|
||||
'telepathic_tile_ice_entrace',
|
||||
'telepathic_tile_ice_entrance',
|
||||
'telepathic_tile_ice_stalfos_knights_room',
|
||||
'telepathic_tile_tower_of_hera_entrance',
|
||||
'telepathic_tile_south_east_darkworld_cave',
|
||||
@@ -2313,15 +2313,16 @@ HintLocations = ['telepathic_tile_eastern_palace',
|
||||
InconvenientLocations = ['Spike Cave',
|
||||
'Sahasrahla',
|
||||
'Purple Chest',
|
||||
'Swamp Left',
|
||||
'Mire Left',
|
||||
'Tower of Hera - Big Key Chest',
|
||||
'Eastern Palace - Big Key Chest',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Ice Palace - Big Chest',
|
||||
'Ganons Tower - Big Chest',
|
||||
'Magic Bat']
|
||||
|
||||
InconvenientDungeonLocations = ['Swamp Left',
|
||||
'Mire Left',
|
||||
'Eastern Palace - Big Key Chest',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Ice Palace - Big Chest',
|
||||
'Ganons Tower - Big Chest']
|
||||
|
||||
InconvenientVanillaLocations = ['Graveyard Cave',
|
||||
'Mimic Cave']
|
||||
|
||||
|
||||
+5
-1
@@ -251,6 +251,9 @@ class Room(object):
|
||||
self.doorList = []
|
||||
self.modified = False
|
||||
|
||||
def kind(self, door):
|
||||
return self.doorList[door.doorListPos][1]
|
||||
|
||||
def door(self, pos, kind):
|
||||
self.doorList.append((pos, kind))
|
||||
return self
|
||||
@@ -299,10 +302,11 @@ class Room(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_b = door_b
|
||||
self.pair = True
|
||||
self.original = original
|
||||
|
||||
def address_a(self, world, player):
|
||||
d = world.check_for_door(self.door_a, player)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import collections
|
||||
import logging
|
||||
from BaseClasses import CollectionState
|
||||
from BaseClasses import CollectionState, RegionType
|
||||
from Regions import key_only_locations
|
||||
from collections import deque
|
||||
|
||||
|
||||
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_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 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 NE', player), lambda state: state.can_shoot_arrows(player))
|
||||
# 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 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))
|
||||
@@ -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))):
|
||||
add_lamp_requirement(spot, player)
|
||||
|
||||
add_conditional_lamp('TR Dark Ride Up Stairs', 'TR Dark Ride', 'Entrance')
|
||||
add_conditional_lamp('TR Dark Ride SW', 'TR Dark Ride', 'Entrance')
|
||||
add_conditional_lamp('Mire Dark Shooters Up Stairs', 'Mire Dark Shooters', 'Entrance')
|
||||
add_conditional_lamp('Mire Dark Shooters SW', 'Mire Dark Shooters', 'Entrance')
|
||||
add_conditional_lamp('Mire Dark Shooters SE', 'Mire Dark Shooters', 'Entrance')
|
||||
add_conditional_lamp('Mire Key Rupees NE', 'Mire Key Rupees', 'Entrance')
|
||||
add_conditional_lamp('Mire Block X NW', 'Mire Block X', 'Entrance')
|
||||
add_conditional_lamp('Mire Block X WS', 'Mire Block X', 'Entrance')
|
||||
add_conditional_lamp('Mire Tall Dark and Roomy ES', 'Mire Tall Dark and Roomy', 'Entrance')
|
||||
add_conditional_lamp('Mire Tall Dark and Roomy WS', 'Mire Tall Dark and Roomy', 'Entrance')
|
||||
add_conditional_lamp('Mire Tall Dark and Roomy WN', 'Mire Tall Dark and Roomy', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Right ES', 'Mire Crystal Right', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Mid NW', 'Mire Crystal Mid', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Left WS', 'Mire Crystal Left', 'Entrance')
|
||||
add_conditional_lamp('Mire Crystal Top SW', 'Mire Crystal Top', 'Entrance')
|
||||
add_conditional_lamp('Mire Shooter Rupees EN', 'Mire Shooter Rupees', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Alley NE', 'PoD Dark Alley', 'Entrance')
|
||||
add_conditional_lamp('PoD Callback WS', 'PoD Callback', 'Entrance')
|
||||
add_conditional_lamp('PoD Callback Warp', 'PoD Callback', 'Entrance')
|
||||
add_conditional_lamp('PoD Turtle Party ES', 'PoD Turtle Party', 'Entrance')
|
||||
add_conditional_lamp('PoD Turtle Party NW', 'PoD Turtle Party', 'Entrance')
|
||||
add_conditional_lamp('PoD Lonely Turtle SW', 'PoD Lonely Turtle', 'Entrance')
|
||||
add_conditional_lamp('PoD Lonely Turtle EN', 'PoD Lonely Turtle', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Pegs Up Ladder', 'PoD Dark Pegs', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Pegs WN', 'PoD Dark Pegs', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Basement W Up Stairs', 'PoD Dark Basement', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Basement E Up Stairs', 'PoD Dark Basement', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Maze EN', 'PoD Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('PoD Dark Maze E', 'PoD Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Basement - Left', 'PoD Dark Basement', 'Location')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'PoD Dark Basement', 'Location')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Maze - Top', 'PoD Dark Maze', 'Location')
|
||||
add_conditional_lamp('Palace of Darkness - Dark Maze - Bottom', 'PoD Dark Maze', 'Location')
|
||||
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')
|
||||
add_conditional_lamp('Eastern Dark Pots WN', 'Eastern Dark Pots', 'Entrance')
|
||||
add_conditional_lamp('Eastern Darkness S', 'Eastern Darkness', 'Entrance')
|
||||
add_conditional_lamp('Eastern Darkness Up Stairs', 'Eastern Darkness', 'Entrance')
|
||||
add_conditional_lamp('Eastern Darkness NE', 'Eastern Darkness', 'Entrance')
|
||||
add_conditional_lamp('Eastern Rupees SE', 'Eastern Rupees', 'Entrance')
|
||||
add_conditional_lamp('Eastern Palace - Dark Square Pot Key', 'Eastern Dark Square', 'Location')
|
||||
add_conditional_lamp('Eastern Palace - Dark Eyegore Key Drop', 'Eastern Darkness', 'Location')
|
||||
add_conditional_lamp('Tower Lone Statue Down Stairs', 'Tower Lone Statue', 'Entrance')
|
||||
add_conditional_lamp('Tower Lone Statue WN', 'Tower Lone Statue', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Maze EN', 'Tower Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Maze ES', 'Tower Dark Maze', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Chargers WS', 'Tower Dark Chargers', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Chargers Up Stairs', 'Tower Dark Chargers', 'Entrance')
|
||||
add_conditional_lamp('Tower Dual Statues Down Stairs', 'Tower Dual Statues', 'Entrance')
|
||||
add_conditional_lamp('Tower Dual Statues WS', 'Tower Dual Statues', 'Entrance')
|
||||
add_conditional_lamp('Tower Dark Pits ES', 'Tower Dark Pits', 'Entrance')
|
||||
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')
|
||||
dark_rooms = {
|
||||
'TR Dark Ride': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride SW'], 'locations': []},
|
||||
'Mire Dark Shooters': {'sewer': False, 'entrances': ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE'], 'locations': []},
|
||||
'Mire Key Rupees': {'sewer': False, 'entrances': ['Mire Key Rupees NE'], 'locations': []},
|
||||
'Mire Block X': {'sewer': False, 'entrances': ['Mire Block X NW', 'Mire Block X WS'], 'locations': []},
|
||||
'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': []},
|
||||
'Mire Crystal Right': {'sewer': False, 'entrances': ['Mire Crystal Right ES'], 'locations': []},
|
||||
'Mire Crystal Mid': {'sewer': False, 'entrances': ['Mire Crystal Mid NW'], 'locations': []},
|
||||
'Mire Crystal Left': {'sewer': False, 'entrances': ['Mire Crystal Left WS'], 'locations': []},
|
||||
'Mire Crystal Top': {'sewer': False, 'entrances': ['Mire Crystal Top SW'], 'locations': []},
|
||||
'Mire Shooter Rupees': {'sewer': False, 'entrances': ['Mire Shooter Rupees EN'], 'locations': []},
|
||||
'PoD Dark Alley': {'sewer': False, 'entrances': ['PoD Dark Alley NE'], 'locations': []},
|
||||
'PoD Callback': {'sewer': False, 'entrances': ['PoD Callback WS', 'PoD Callback Warp'], 'locations': []},
|
||||
'PoD Turtle Party': {'sewer': False, 'entrances': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], 'locations': []},
|
||||
'PoD Lonely Turtle': {'sewer': False, 'entrances': ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN'], 'locations': []},
|
||||
'PoD Dark Pegs': {'sewer': False, 'entrances': ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs WN'], 'locations': []},
|
||||
'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']},
|
||||
'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']},
|
||||
'Eastern Dark Square': {'sewer': False, 'entrances': ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN', 'Eastern Dark Square EN'], 'locations': []},
|
||||
'Eastern Dark Pots': {'sewer': False, 'entrances': ['Eastern Dark Pots WN'], 'locations': ['Eastern Palace - Dark Square Pot Key']},
|
||||
'Eastern Darkness': {'sewer': False, 'entrances': ['Eastern Darkness S', 'Eastern Darkness Up Stairs', 'Eastern Darkness NE'], 'locations': ['Eastern Palace - Dark Eyegore Key Drop']},
|
||||
'Eastern Rupees': {'sewer': False, 'entrances': ['Eastern Rupees SE'], 'locations': []},
|
||||
'Tower Lone Statue': {'sewer': False, 'entrances': ['Tower Lone Statue Down Stairs', 'Tower Lone Statue WN'], 'locations': []},
|
||||
'Tower Dark Maze': {'sewer': False, 'entrances': ['Tower Dark Maze EN', 'Tower Dark Maze ES'], 'locations': ['Castle Tower - Dark Maze']},
|
||||
'Tower Dark Chargers': {'sewer': False, 'entrances': ['Tower Dark Chargers WS', 'Tower Dark Chargers Up Stairs'], 'locations': []},
|
||||
'Tower Dual Statues': {'sewer': False, 'entrances': ['Tower Dual Statues Down Stairs', 'Tower Dual Statues WS'], 'locations': []},
|
||||
'Tower Dark Pits': {'sewer': False, 'entrances': ['Tower Dark Pits ES', 'Tower Dark Pits EN'], 'locations': []},
|
||||
'Tower Dark Archers': {'sewer': False, 'entrances': ['Tower Dark Archers WN', 'Tower Dark Archers Up Stairs'], 'locations': ['Castle Tower - Dark Archer Key Drop']},
|
||||
'Sewers Dark Cross': {'sewer': True, 'entrances': ['Sewers Dark Cross Key Door N', 'Sewers Dark Cross South Stairs'], 'locations': ['Sewers - Dark Cross']},
|
||||
'Sewers Behind Tapestry': {'sewer': True, 'entrances': ['Sewers Behind Tapestry S', 'Sewers Behind Tapestry Down Stairs'], 'locations': []},
|
||||
'Sewers Rope Room': {'sewer': True, 'entrances': ['Sewers Rope Room Up Stairs', 'Sewers Rope Room North Stairs'], 'locations': []},
|
||||
'Sewers Water': {'sewer': True, 'entrances': ['Sewers Dark Cross Key Door S', 'Sewers Water W'], 'locations': []},
|
||||
'Sewers Key Rat': {'sewer': True, 'entrances': ['Sewers Key Rat E', 'Sewers Key Rat Key Door N'], 'locations': ['Hyrule Castle - Key Rat Key Drop']},
|
||||
}
|
||||
|
||||
dark_debug_set = set()
|
||||
for region, info in dark_rooms.items():
|
||||
is_dark = False
|
||||
if not world.sewer_light_cone[player]:
|
||||
is_dark = True
|
||||
elif world.doorShuffle[player] != 'crossed' and not info['sewer']:
|
||||
is_dark = True
|
||||
elif world.doorShuffle[player] == 'crossed':
|
||||
sewer_builder = world.dungeon_layouts[player]['Hyrule Castle']
|
||||
is_dark = region not in sewer_builder.master_sector.region_set()
|
||||
if is_dark:
|
||||
dark_debug_set.add(region)
|
||||
for ent in info['entrances']:
|
||||
add_conditional_lamp(ent, region, 'Entrance')
|
||||
for loc in info['locations']:
|
||||
add_conditional_lamp(loc, region, 'Location')
|
||||
logging.getLogger('').debug('Non Dark Regions: ' + ', '.join(set(dark_rooms.keys()).difference(dark_debug_set)))
|
||||
|
||||
add_conditional_lamp('Old Man', 'Old Man Cave', 'Location')
|
||||
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')
|
||||
@@ -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 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):
|
||||
# softlock protection as you can reach the sewers small key door with a guard drop key
|
||||
@@ -795,10 +790,42 @@ 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))
|
||||
|
||||
|
||||
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):
|
||||
# these are because of rails
|
||||
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))
|
||||
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 (West)', player), lambda state: state.has('Zelda Delivered', player))
|
||||
|
||||
# too restrictive for crossed?
|
||||
def uncle_item_rule(item):
|
||||
@@ -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('Secret Passage', player), lambda state: state.can_kill_most_things(player))
|
||||
|
||||
# todo: in crossed these chest/key drops are not necessarily present
|
||||
add_rule(world.get_location('Hyrule Castle - Map Chest', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Sewers - Dark Cross', player), lambda state: state.can_kill_most_things(player))
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), 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))
|
||||
escape_builder = world.dungeon_layouts[player]['Hyrule Castle']
|
||||
for region in escape_builder.master_sector.regions:
|
||||
for loc in region.locations:
|
||||
add_rule(loc, 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_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']:
|
||||
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))
|
||||
|
||||
|
||||
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):
|
||||
# this is a mess
|
||||
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
|
||||
# b) being able to access all entrances from there to `region`
|
||||
seen = set([region])
|
||||
queue = collections.deque([(region, [])])
|
||||
queue = deque([(region, [])])
|
||||
while queue:
|
||||
(current, path) = queue.popleft()
|
||||
for entrance in current.entrances:
|
||||
@@ -1367,7 +1419,7 @@ def set_inverted_bunny_rules(world, player):
|
||||
# a) being able to reach it, and
|
||||
# b) being able to access all entrances from there to `region`
|
||||
seen = set([region])
|
||||
queue = collections.deque([(region, [])])
|
||||
queue = deque([(region, [])])
|
||||
while queue:
|
||||
(current, path) = queue.popleft()
|
||||
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_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_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_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")
|
||||
|
||||
@@ -188,16 +188,16 @@ def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan)
|
||||
|
||||
for ent, offset in entrance_offsets.items():
|
||||
# print(ent)
|
||||
str = ent
|
||||
string = ent
|
||||
for dp, data in entrance_data.items():
|
||||
byte_array = []
|
||||
address, size = data
|
||||
for i in range(0, size):
|
||||
byte_array.append(old_rom_data[address+(offset*size)+i])
|
||||
bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
|
||||
str += '\t'+bytes
|
||||
some_bytes = ', '.join('0x{:02x}'.format(x) for x in byte_array)
|
||||
string += '\t'+some_bytes
|
||||
# print("%s: %s" % (dp, bytes))
|
||||
print(str)
|
||||
print(string)
|
||||
|
||||
|
||||
def print_wiki_doors(d_regions, world, player):
|
||||
|
||||
@@ -63,6 +63,8 @@ org $0DFA53
|
||||
jsl.l LampCheckOverride
|
||||
org $028046 ; <- 10046 - Bank02.asm : 217 (JSL EnableForceBlank) (Start of Module_LoadFile)
|
||||
jsl.l OnFileLoadOverride
|
||||
org $07A93F ; < 3A93F - Bank07.asm 6548 (LDA $8A : AND.b #$40 - Mirror checks)
|
||||
jsl.l MirrorCheckOverride
|
||||
|
||||
org $05ef47
|
||||
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
|
||||
lda DRFlags : and #$80 : beq + ;flag is off
|
||||
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
|
||||
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