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:
aerinon
2020-02-11 14:40:58 -07:00
parent ce1b28e3bd
commit 1f7c27009e
19 changed files with 476 additions and 228 deletions
+14 -5
View File
@@ -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
View File
@@ -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
+40 -38
View File
@@ -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
] ]
+3 -3
View File
@@ -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
View File
@@ -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'
}
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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):
+2 -1
View File
@@ -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)}
+13 -12
View File
@@ -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
View File
@@ -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)
+139 -87
View File
@@ -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:
+1 -1
View File
@@ -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")
+4 -4
View File
@@ -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):
+2
View File
@@ -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)
+11
View File
@@ -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