Merge pull request #20 from aerinon/DoorDevUnstable

Door dev unstable
This commit is contained in:
Mike A. Trethewey
2020-11-11 00:15:07 -08:00
committed by GitHub
39 changed files with 2682 additions and 745 deletions

View File

@@ -80,6 +80,9 @@ class World(object):
self.key_logic = {}
self.pool_adjustment = {}
self.key_layout = defaultdict(dict)
self.dungeon_portals = defaultdict(list)
self._portal_cache = {}
self.sanc_portal = {}
self.fish = BabelFish()
for player in range(1, players + 1):
@@ -124,6 +127,9 @@ class World(object):
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
set_player_attr('treasure_hunt_count', 0)
set_player_attr('keydropshuffle', False)
set_player_attr('mixed_travel', 'prevent')
def get_name_string_for_object(self, obj):
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
@@ -203,6 +209,18 @@ class World(object):
return door
raise RuntimeError('No such door %s for player %d' % (doorname, player))
def get_portal(self, portal_name, player):
if isinstance(portal_name, Portal):
return portal_name
try:
return self._portal_cache[(portal_name, player)]
except KeyError:
for portal in self.dungeon_portals[player]:
if portal.name == portal_name and portal.player == player:
self._portal_cache[(portal_name, player)] = portal
return portal
raise RuntimeError('No such portal %s for player %d' % (portal_name, player))
def check_for_door(self, doorname, player):
if isinstance(doorname, Door):
return doorname
@@ -268,7 +286,7 @@ class World(object):
pass
elif ret.has('Red Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 3:
ret.prog_items['Mirror Shield', item.player] += 1
elif ret.has('Blue Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 2:
elif ret.has('Blue Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 2:
ret.prog_items['Red Shield', item.player] += 1
elif self.difficulty_requirements[item.player].progressive_shield_limit >= 1:
ret.prog_items['Blue Shield', item.player] += 1
@@ -458,7 +476,7 @@ class CollectionState(object):
new_crystal_state = crystal_state
for exit in new_region.exits:
door = exit.door
if door is not None and door.crystal == CrystalBarrier.Either:
if door is not None and door.crystal == CrystalBarrier.Either and door.entrance.can_reach(self):
new_crystal_state = CrystalBarrier.Either
break
if new_region in rrp:
@@ -469,7 +487,7 @@ class CollectionState(object):
for exit in new_region.exits:
door = exit.door
if door is not None and not door.blocked:
door_crystal_state = new_crystal_state & (door.crystal or CrystalBarrier.Either)
door_crystal_state = door.crystal if door.crystal else new_crystal_state
bc[exit] = door_crystal_state
queue.append((exit, door_crystal_state))
elif door is None:
@@ -547,15 +565,20 @@ class CollectionState(object):
def _do_not_flood_the_keys(self, reachable_events):
adjusted_checks = list(reachable_events)
for event in reachable_events:
if event.name in flooded_keys.keys() and self.world.get_location(flooded_keys[event.name], event.player) not in reachable_events:
adjusted_checks.remove(event)
if event.name in flooded_keys.keys():
flood_location = self.world.get_location(flooded_keys[event.name], event.player)
if flood_location.item and flood_location not in self.locations_checked:
adjusted_checks.remove(event)
if len(adjusted_checks) < len(reachable_events):
return adjusted_checks
return reachable_events
def not_flooding_a_key(self, world, location):
if location.name in flooded_keys.keys():
return world.get_location(flooded_keys[location.name], location.player) in self.locations_checked
flood_location = world.get_location(flooded_keys[location.name], location.player)
item = flood_location.item
item_is_important = False if not item else item.advancement or item.bigkey or item.smallkey
return flood_location in self.locations_checked or not item_is_important
return True
def has(self, item, player, count=1):
@@ -563,8 +586,10 @@ class CollectionState(object):
return (item, player) in self.prog_items
return self.prog_items[item, player] >= count
def has_key(self, item, player, count=1):
def has_sm_key(self, item, player, count=1):
if self.world.retro[player]:
if self.world.mode[player] == 'standard' and self.world.doorShuffle[player] == 'vanilla' and item == 'Small Key (Escape)':
return True # Cannot access the shop until escape is finished. This is safe because the key is manually placed in make_custom_item_pool
return self.can_buy_unlimited('Small Key (Universal)', player)
if count == 1:
return (item, player) in self.prog_items
@@ -935,9 +960,10 @@ class Entrance(object):
world = self.parent_region.world if self.parent_region else None
return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'
class Dungeon(object):
def __init__(self, name, regions, big_key, small_keys, dungeon_items, player):
def __init__(self, name, regions, big_key, small_keys, dungeon_items, player, dungeon_id):
self.name = name
self.regions = regions
self.big_key = big_key
@@ -946,6 +972,7 @@ class Dungeon(object):
self.bosses = dict()
self.player = player
self.world = None
self.dungeon_id = dungeon_id
self.entrance_regions = []
@@ -1150,6 +1177,7 @@ class Door(object):
# 0-4 for spiral offset thing
self.doorIndex = -1
self.layer = -1 # 0 for normal floor, 1 for the inset layer
self.pseudo_bg = 0 # 0 for normal floor, 1 for pseudo bg
self.toggle = False
self.trapFlag = 0x0
self.quadrant = 2
@@ -1161,6 +1189,17 @@ class Door(object):
self.edge_id = None
self.edge_width = None
#portal items
self.portalAble = False
self.roomLayout = 0x22 # free scroll- both directions
self.entranceFlag = False
self.deadEnd = False
self.passage = True
self.dungeonLink = None
self.bk_shuffle_req = False
# self.incognitoPos = -1
# self.sectorLink = False
# logical properties
# self.connected = False # combine with Dest?
self.dest = None
@@ -1293,6 +1332,20 @@ class Door(object):
self.dead = True
return self
def portal(self, quadrant, roomLayout, pseudo_bg=0):
self.quadrant = quadrant
self.roomLayout = roomLayout
self.pseudo_bg = pseudo_bg
self.portalAble = True
return self
def dead_end(self, allowPassage=False):
self.deadEnd = True
if allowPassage:
self.passage = True
else:
self.passage = False
def __eq__(self, other):
return isinstance(other, self.__class__) and self.name == other.name
@@ -1314,7 +1367,6 @@ class Sector(object):
self.name = None
self.r_name_set = None
self.chest_locations = 0
self.big_chest_present = False
self.key_only_locations = 0
self.c_switch = False
self.orange_barrier = False
@@ -1376,10 +1428,9 @@ class Sector(object):
self.branch_factor -= cnt_dead - 1
for region in self.regions:
for ent in region.entrances:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or ent.parent_region.name == 'Sewer Drop':
# same sector as another entrance
if region.name not in ['Skull Pot Circle', 'Skull Back Drop', 'Desert East Lobby', 'Desert West Lobby']:
self.branch_factor += 1
if (ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] and ent.parent_region.name != 'Menu') or ent.parent_region.name == 'Sewer Drop':
self.branch_factor += 1
break # you only ever get one allowance for an entrance region, multiple entrances don't help
return self.branch_factor
def branches(self):
@@ -1422,6 +1473,117 @@ class Sector(object):
return f'{next(iter(self.region_set()))}'
class Portal(object):
def __init__(self, player, name, door, entrance_offset, exit_offset, boss_exit_idx):
self.player = player
self.name = name
self.door = door
self.ent_offset = entrance_offset
self.exit_offset = exit_offset
self.boss_exit_idx = boss_exit_idx
self.default = True
self.destination = False
self.deadEnd = False
self.light_world = False
def change_door(self, new_door):
if new_door != self.door:
self.default = False
self.door = new_door
def current_room(self):
return self.door.roomIndex
def relative_coords(self):
y_rel = (self.door.roomIndex & 0xf0) >> 3 #todo: fix the shift!!!!
x_rel = (self.door.roomIndex & 0x0f) * 2
quad = self.door.quadrant
if quad == 0:
return [y_rel, y_rel, y_rel, y_rel+1, x_rel, x_rel, x_rel, x_rel+1]
elif quad == 1:
return [y_rel, y_rel, y_rel, y_rel+1, x_rel+1, x_rel, x_rel+1, x_rel+1]
elif quad == 2:
return [y_rel+1, y_rel, y_rel+1, y_rel+1, x_rel, x_rel, x_rel, x_rel+1]
else:
return [y_rel+1, y_rel, y_rel+1, y_rel+1, x_rel+1, x_rel, x_rel+1, x_rel+1]
def scroll_x(self):
x_rel = (self.door.roomIndex & 0x0f) * 2
if self.door.doorIndex == 0:
return [0x00, x_rel]
elif self.door.doorIndex == 1:
return [0x80, x_rel]
else:
return [0x00, x_rel+1]
def scroll_y(self):
y_rel = ((self.door.roomIndex & 0xf0) >> 3) + 1
return [0x10, y_rel]
def link_y(self):
y_rel = ((self.door.roomIndex & 0xf0) >> 3) + 1
inset = False
if self.door.pseudo_bg == 1 or self.door.layer == 1:
inset = True
return [(0xd8 if not inset else 0xc0), y_rel]
def link_x(self):
x_rel = (self.door.roomIndex & 0x0f) * 2
if self.door.doorIndex == 0:
return [0x78, x_rel]
elif self.door.doorIndex == 1:
return [0xf8, x_rel]
else:
return [0x78, x_rel+1]
# def camera_y(self):
# return [0x87, 0x01]
def camera_x(self):
if self.door.doorIndex == 0:
return [0x7f, 0x00]
elif self.door.doorIndex == 1:
return [0xff, 0x00]
else:
return [0x7f, 0x01]
def bg_setting(self):
if self.door.layer == 0:
return 0x00 | self.door.pseudo_bg
else:
return 0x10 | self.door.pseudo_bg
def hv_scroll(self):
return self.door.roomLayout
def scroll_quad(self):
quad = self.door.quadrant
if quad == 0:
return 0x00
elif quad == 1:
return 0x10
elif quad == 2:
return 0x02
else:
return 0x12
def __str__(self):
return str(self.__unicode__())
def __unicode__(self):
return f'{self.name}:{self.door.name}'
class DungeonInfo(object):
def __init__(self, name):
self.name = name
self.total = 0
self.required_passage = {}
self.sole_entrance = None
# self.dead_ends = 0 total - 1 - req = dead_ends possible
class Boss(object):
def __init__(self, name, enemizer_name, defeat_rule, player):
self.name = name
@@ -1467,6 +1629,15 @@ class Location(object):
return True
return False
def forced_big_key(self):
if self.forced_item and self.forced_item.bigkey and self.player == self.forced_item.player:
item_dungeon = self.forced_item.name.split('(')[1][:-1]
if item_dungeon == 'Escape':
item_dungeon = 'Hyrule Castle'
if self.parent_region.dungeon.name == item_dungeon:
return True
return False
def __str__(self):
return str(self.__unicode__())
@@ -1592,9 +1763,10 @@ class Spoiler(object):
def __init__(self, world):
self.world = world
self.hashes = {}
self.entrances = OrderedDict()
self.doors = OrderedDict()
self.doorTypes = OrderedDict()
self.entrances = {}
self.doors = {}
self.doorTypes = {}
self.lobbies = {}
self.medallions = {}
self.playthrough = {}
self.unreachables = []
@@ -1617,6 +1789,12 @@ class Spoiler(object):
else:
self.doors[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction), ('dname', d_name)])
def set_lobby(self, lobby_name, door_name, player):
if self.world.players == 1:
self.lobbies[(lobby_name, player)] = {'lobby_name': lobby_name, 'door_name': door_name}
else:
self.lobbies[(lobby_name, player)] = {'player': player, 'lobby_name': lobby_name, 'door_name': door_name}
def set_door_type(self, doorNames, type, player):
if self.world.players == 1:
self.doorTypes[(doorNames, player)] = OrderedDict([('doorNames', doorNames), ('type', type)])
@@ -1696,6 +1874,11 @@ class Spoiler(object):
if self.world.players == 1:
self.bosses = self.bosses["1"]
for player in range(1, self.world.players + 1):
if self.world.intensity[player] >= 3:
for portal in self.world.dungeon_portals[player]:
self.set_lobby(portal.name, portal.door.name, player)
from Main import __version__ as ERVersion
self.metadata = {'version': ERVersion,
'logic': self.world.logic,
@@ -1723,7 +1906,8 @@ class Spoiler(object):
'enemy_damage': self.world.enemy_damage,
'players': self.world.players,
'teams': self.world.teams,
'experimental' : self.world.experimental
'experimental': self.world.experimental,
'keydropshuffle': self.world.keydropshuffle,
}
def to_json(self):
@@ -1731,6 +1915,7 @@ class Spoiler(object):
out = OrderedDict()
out['Entrances'] = list(self.entrances.values())
out['Doors'] = list(self.doors.values())
out['Lobbies'] = list(self.lobbies.values())
out['DoorTypes'] = list(self.doorTypes.values())
out.update(self.locations)
out['Starting Inventory'] = self.startinventory
@@ -1783,6 +1968,7 @@ class Spoiler(object):
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'))
outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
if self.doors:
outfile.write('\n\nDoors:\n\n')
outfile.write('\n'.join(
@@ -1792,6 +1978,12 @@ class Spoiler(object):
self.world.fish.translate("meta","doors",entry['exit']),
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] == 'crossed' else '') for
entry in self.doors.values()]))
if self.lobbies:
outfile.write('\n\nDungeon Lobbies:\n\n')
outfile.write('\n'.join(
[f"{'Player {0}: '.format(entry['player']) if self.world.players > 1 else ''}{entry['lobby_name']}: {entry['door_name']}"
for
entry in self.lobbies.values()]))
if self.doorTypes:
# doorNames: For some reason these come in combined, somehow need to split on the thing to translate
# doorTypes: Small Key, Bombable, Bonkable
@@ -1857,3 +2049,8 @@ flooded_keys = {
'Trench 1 Switch': 'Swamp Palace - Trench 1 Pot Key',
'Trench 2 Switch': 'Swamp Palace - Trench 2 Pot Key'
}
dungeon_names = [
'Hyrule Castle', 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Agahnims Tower', 'Palace of Darkness',
'Swamp Palace', 'Skull Woods', 'Thieves Town', 'Ice Palace', 'Misery Mire', 'Turtle Rock', 'Ganons Tower'
]

4
CLI.py
View File

@@ -95,7 +95,7 @@ def parse_cli(argv, no_defaults=False):
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
'remote_items']:
'remote_items', 'keydropshuffle', 'mixed_travel']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})
@@ -135,6 +135,7 @@ def parse_settings():
"enemy_health": "default",
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
"keydropshuffle": False,
"mapshuffle": False,
"compassshuffle": False,
"keyshuffle": False,
@@ -144,6 +145,7 @@ def parse_settings():
"intensity": 2,
"experimental": False,
"dungeon_counters": "default",
"mixed_travel": "prevent",
"multi": 1,
"names": "",

View File

@@ -4,16 +4,17 @@ import logging
import operator as op
import time
from enum import unique, Flag
from typing import DefaultDict, Dict, List
from functools import reduce
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier
from Regions import key_only_locations
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts, flexible_starts
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
from Dungeons import dungeon_bigs, dungeon_keys, dungeon_hints
from Items import ItemFactory
from RoomData import DoorKind, PairedDoor
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
from DungeonGenerator import dungeon_portals, dungeon_drops
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout
@@ -39,6 +40,25 @@ def link_doors(world, player):
connect_two_way(world, entrance, ext, player)
for entrance, ext in straight_staircases:
connect_two_way(world, entrance, ext, player)
connect_custom(world, player)
find_inaccessible_regions(world, player)
if world.intensity[player] >= 3 and world.doorShuffle[player] in ['basic', 'crossed']:
choose_portals(world, player)
else:
if world.shuffle[player] == 'vanilla':
if world.mode[player] == 'standard':
world.get_portal('Sanctuary', player).destination = True
world.get_portal('Desert East', player).destination = True
world.get_portal('Skull 2 West', player).destination = True
world.get_portal('Turtle Rock Lazy Eyes', player).destination = True
world.get_portal('Turtle Rock Eye Bridge', player).destination = True
else:
analyze_portals(world, player)
for portal in world.dungeon_portals[player]:
connect_portal(portal, world, player)
if world.doorShuffle[player] == 'vanilla':
for entrance, ext in open_edges:
@@ -135,9 +155,9 @@ def vanilla_key_logic(world, player):
builders.append(builder)
world.dungeon_layouts[player][builder.name] = builder
overworld_prep(world, player)
add_inaccessible_doors(world, player)
for builder in builders:
origin_list = find_accessible_entrances(world, player, default_dungeon_entrances[builder.name])
origin_list = find_accessible_entrances(world, player, builder)
start_regions = convert_regions(origin_list, world, player)
doors = convert_key_doors(default_small_key_doors[builder.name], world, player)
key_layout = build_key_layout(builder, start_regions, doors, world, player)
@@ -150,7 +170,7 @@ def vanilla_key_logic(world, player):
analyze_dungeon(key_layout, world, player)
world.key_logic[player][builder.name] = key_layout.key_logic
log_key_logic(builder.name, key_layout.key_logic)
if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player]:
if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player] and not world.keydropshuffle[player]:
validate_vanilla_key_logic(world, player)
@@ -169,9 +189,9 @@ def switch_dir(direction):
return oppositemap[direction]
def convert_key_doors(key_doors, world, player):
def convert_key_doors(k_doors, world, player):
result = []
for d in key_doors:
for d in k_doors:
if type(d) is tuple:
result.append((world.get_door(d[0], player), world.get_door(d[1], player)))
else:
@@ -179,6 +199,12 @@ def convert_key_doors(key_doors, world, player):
return result
def connect_custom(world, player):
if hasattr(world, 'custom_doors') and world.custom_doors[player]:
for entrance, ext in world.custom_doors[player]:
connect_two_way(world, entrance, ext, player)
def connect_simple_door(world, exit_name, region_name, player):
region = world.get_region(region_name, player)
world.get_entrance(exit_name, player).connect(region)
@@ -257,7 +283,8 @@ def remove_ugly_small_key_doors(world, player):
'TR Lava Escape SE', 'GT Hidden Spikes SE']:
door = world.get_door(d, player)
room = world.get_room(door.roomIndex, player)
room.change(door.doorListPos, DoorKind.Normal)
if not door.entranceFlag:
room.change(door.doorListPos, DoorKind.Normal)
door.smallKey = False
door.ugly = False
@@ -285,13 +312,298 @@ def pair_existing_key_doors(world, player, door_a, door_b):
world.paired_doors[player].append(PairedDoor(door_a, door_b))
def choose_portals(world, player):
if world.doorShuffle[player] in ['basic', 'crossed']:
cross_flag = world.doorShuffle[player] == 'crossed'
bk_shuffle = world.bigkeyshuffle[player]
# roast incognito doors
world.get_room(0x60, player).delete(5)
world.get_room(0x60, player).change(2, DoorKind.DungeonEntrance)
world.get_room(0x62, player).delete(5)
world.get_room(0x62, player).change(1, DoorKind.DungeonEntrance)
info_map = {}
for dungeon, portal_list in dungeon_portals.items():
info = DungeonInfo(dungeon)
region_map = defaultdict(list)
reachable_portals = []
inaccessible_portals = []
for portal in portal_list:
placeholder = world.get_region(portal + ' Placeholder', player)
portal_region = placeholder.exits[0].connected_region
name = portal_region.name
if portal_region.type == RegionType.LightWorld:
world.get_portal(portal, player).light_world = True
if name in world.inaccessible_regions[player]:
name_key = 'Desert Ledge' if name == 'Desert Palace Entrance (North) Spot' else name
region_map[name_key].append(portal)
inaccessible_portals.append(portal)
else:
reachable_portals.append(portal)
info.total = len(portal_list)
info.required_passage = region_map
if len(reachable_portals) == 0:
if len(inaccessible_portals) == 1:
info.sole_entrance = inaccessible_portals[0]
info.required_passage.clear()
else:
raise Exception('please inspect this case')
if len(reachable_portals) == 1:
info.sole_entrance = reachable_portals[0]
info_map[dungeon] = info
master_door_list = [x for x in world.doors if x.player == player and x.portalAble]
portal_assignment = defaultdict(list)
for dungeon, info in info_map.items():
outstanding_portals = list(dungeon_portals[dungeon])
if dungeon == 'Hyrule Castle' and world.mode[player] == 'standard':
sanc = world.get_portal('Sanctuary', player)
sanc.destination = True
clean_up_portal_assignment(portal_assignment, dungeon, sanc, master_door_list, outstanding_portals)
for target_region, possible_portals in info.required_passage.items():
candidates = find_portal_candidates(master_door_list, dungeon, need_passage=True, crossed=cross_flag,
bk_shuffle=bk_shuffle)
choice, portal = assign_portal(candidates, possible_portals, world, player)
portal.destination = True
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
dead_end_choices = info.total - 1 - len(portal_assignment[dungeon])
for i in range(0, dead_end_choices):
candidates = find_portal_candidates(master_door_list, dungeon, dead_end_allowed=True,
crossed=cross_flag, bk_shuffle=bk_shuffle)
possible_portals = outstanding_portals if not info.sole_entrance else [x for x in outstanding_portals if x != info.sole_entrance]
choice, portal = assign_portal(candidates, possible_portals, world, player)
if choice.deadEnd:
portal.deadEnd = True
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
the_rest = info.total - len(portal_assignment[dungeon])
for i in range(0, the_rest):
candidates = find_portal_candidates(master_door_list, dungeon, crossed=cross_flag,
bk_shuffle=bk_shuffle)
choice, portal = assign_portal(candidates, outstanding_portals, world, player)
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
for portal in world.dungeon_portals[player]:
connect_portal(portal, world, player)
hc_south = world.get_door('Hyrule Castle Lobby S', player)
if not hc_south.entranceFlag:
world.get_room(0x61, player).delete(6)
world.get_room(0x61, player).change(4, DoorKind.NormalLow)
sanctuary_door = world.get_door('Sanctuary S', player)
if not sanctuary_door.entranceFlag:
world.get_room(0x12, player).delete(3)
world.get_room(0x12, player).change(2, DoorKind.NormalLow)
hera_door = world.get_door('Hera Lobby S', player)
if not hera_door.entranceFlag:
world.get_room(0x77, player).change(0, DoorKind.NormalLow2)
if not world.swamp_patch_required[player]:
swamp_region = world.get_entrance('Swamp Palace', player).connected_region
if swamp_region.name != 'Swamp Lobby':
world.swamp_patch_required[player] = True
def analyze_portals(world, player):
info_map = {}
for dungeon, portal_list in dungeon_portals.items():
info = DungeonInfo(dungeon)
region_map = defaultdict(list)
reachable_portals = []
inaccessible_portals = []
for portal in portal_list:
placeholder = world.get_region(portal + ' Placeholder', player)
portal_region = placeholder.exits[0].connected_region
name = portal_region.name
if portal_region.type == RegionType.LightWorld:
world.get_portal(portal, player).light_world = True
if name in world.inaccessible_regions[player]:
name_key = 'Desert Ledge' if name == 'Desert Palace Entrance (North) Spot' else name
region_map[name_key].append(portal)
inaccessible_portals.append(portal)
else:
reachable_portals.append(portal)
info.total = len(portal_list)
info.required_passage = region_map
if len(reachable_portals) == 0:
if len(inaccessible_portals) == 1:
info.sole_entrance = inaccessible_portals[0]
info.required_passage.clear()
else:
raise Exception('please inspect this case')
if len(reachable_portals) == 1:
info.sole_entrance = reachable_portals[0]
info_map[dungeon] = info
for dungeon, info in info_map.items():
if dungeon == 'Hyrule Castle' and world.mode[player] == 'standard':
sanc = world.get_portal('Sanctuary', player)
sanc.destination = True
for target_region, possible_portals in info.required_passage.items():
if len(possible_portals) == 1:
world.get_portal(possible_portals[0], player).destination = True
elif len(possible_portals) > 1:
world.get_portal(random.choice(possible_portals), player).destination = True
def connect_portal(portal, world, player):
ent, ext = portal_map[portal.name]
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
ext = 'Inverted ' + ext
ent = 'Inverted ' + ent
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying
target_exit = world.get_entrance(ext, player)
target_exit.parent_region = portal_entrance.parent_region
portal_entrance.connected_region = target_exit.connected_region
placeholder = world.get_region(portal.name + ' Placeholder', player)
if len(placeholder.entrances) > 0:
edit_entrance = placeholder.entrances[0]
else:
edit_entrance = world.get_entrance(ent, player)
entrance_region = portal_entrance.parent_region
old_region = edit_entrance.connected_region
edit_entrance.connected_region = entrance_region
entrance_region.exits.remove(portal_entrance)
entrance_region.exits.append(target_exit)
entrance_region.entrances.append(edit_entrance)
old_region.entrances.remove(edit_entrance)
world.regions.remove(placeholder)
def connect_portal_copy(portal, world, player):
ent, ext = portal_map[portal.name]
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
ext = 'Inverted ' + ext
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying
target_exit = world.get_entrance(ext, player)
entrance_region = portal_entrance.parent_region
entrance_region.exits.remove(portal_entrance)
entrance_region.exits.append(target_exit)
target_exit.parent_region = entrance_region
placeholder = world.get_region(portal.name + ' Placeholder', player)
world.regions.remove(placeholder)
def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allowed=False, crossed=False, bk_shuffle=False):
filter_list = [x for x in door_list if bk_shuffle or not x.bk_shuffle_req]
if need_passage:
if crossed:
return [x for x in filter_list if x.passage and (x.dungeonLink is None or x.entrance.parent_region.dungeon.name == dungeon)]
else:
return [x for x in filter_list if x.passage and x.entrance.parent_region.dungeon.name == dungeon]
elif dead_end_allowed:
if crossed:
return [x for x in filter_list if x.dungeonLink is None or x.entrance.parent_region.dungeon.name == dungeon]
else:
return [x for x in filter_list if x.entrance.parent_region.dungeon.name == dungeon]
else:
if crossed:
return [x for x in filter_list if (not x.dungeonLink or x.entrance.parent_region.dungeon.name == dungeon) and not x.deadEnd]
else:
return [x for x in filter_list if x.entrance.parent_region.dungeon.name == dungeon and not x.deadEnd]
def assign_portal(candidates, possible_portals, world, player):
candidate = random.choice(candidates)
portal_choice = random.choice(possible_portals)
portal = world.get_portal(portal_choice, player)
if candidate != portal.door:
if candidate.entranceFlag:
for other_portal in world.dungeon_portals[player]:
if other_portal.door == candidate:
other_portal.door = None
break
old_door = portal.door
if old_door:
old_door.entranceFlag = False
if old_door.name not in ['Hyrule Castle Lobby S', 'Sanctuary S', 'Hera Lobby S']:
old_door_kind = DoorKind.NormalLow if old_door.layer or old_door.pseudo_bg else DoorKind.Normal
world.get_room(old_door.roomIndex, player).change(old_door.doorListPos, old_door_kind)
portal.change_door(candidate)
if candidate.name not in ['Hyrule Castle Lobby S', 'Sanctuary S']:
new_door_kind = DoorKind.DungeonEntranceLow if candidate.layer or candidate.pseudo_bg else DoorKind.DungeonEntrance
world.get_room(candidate.roomIndex, player).change(candidate.doorListPos, new_door_kind)
candidate.entranceFlag = True
return candidate, portal
def clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals):
portal_assignment[dungeon].append(portal)
master_door_list[:] = [x for x in master_door_list if x.roomIndex != portal.door.roomIndex]
if portal.door.dungeonLink and portal.door.dungeonLink.startswith('link'):
match_link = portal.door.dungeonLink
for door in master_door_list:
if door.dungeonLink == match_link:
door.dungeonLink = dungeon
outstanding_portals.remove(portal.name)
def create_dungeon_entrances(world, player):
entrance_map = defaultdict(list)
split_map: DefaultDict[str, DefaultDict[str, List]] = defaultdict(lambda: defaultdict(list))
originating: DefaultDict[str, DefaultDict[str, Dict]] = defaultdict(lambda: defaultdict(dict))
for key, portal_list in dungeon_portals.items():
if key in dungeon_drops.keys():
entrance_map[key].extend(dungeon_drops[key])
if key in split_portals.keys() and world.intensity[player] >= 3:
dead_ends = []
destinations = []
the_rest = []
for portal_name in portal_list:
portal = world.get_portal(portal_name, player)
entrance_map[key].append(portal.door.entrance.parent_region.name)
if portal.deadEnd:
dead_ends.append(portal)
elif portal.destination:
destinations.append(portal)
else:
the_rest.append(portal)
choices = list(split_portals[key])
for portal in dead_ends:
choice = random.choice(choices)
choices.remove(choice)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
for portal in the_rest:
if len(choices) == 0:
choices.append('Extra')
choice = random.choice(choices)
p_entrance = portal.door.entrance
r_name = p_entrance.parent_region.name
split_map[key][choice].append(r_name)
originating[key][choice][p_entrance.connected_region.name] = None
dest_choices = [x for x in choices if len(split_map[key][x]) > 0]
for portal in destinations:
restricted = portal.door.entrance.connected_region.name in world.inaccessible_regions[player]
if restricted:
filtered_choices = [x for x in choices if any(y not in world.inaccessible_regions[player] for y in originating[key][x].keys())]
else:
filtered_choices = dest_choices
choice = random.choice(filtered_choices)
r_name = portal.door.entrance.parent_region.name
split_map[key][choice].append(r_name)
else:
for portal_name in portal_list:
portal = world.get_portal(portal_name, player)
r_name = portal.door.entrance.parent_region.name
entrance_map[key].append(r_name)
if key in split_portals.keys():
for split_key in split_portals[key]:
if split_key not in split_map[key]:
split_map[key][split_key] = []
if world.intensity[player] < 3:
split_map[key][split_portal_defaults[key][r_name]].append(r_name)
return entrance_map, split_map
# def unpair_all_doors(world, player):
# for paired_door in world.paired_doors[player]:
# paired_door.pair = False
def within_dungeon(world, player):
fix_big_key_doors_with_ugly_smalls(world, player)
overworld_prep(world, player)
add_inaccessible_doors(world, player)
entrances_map, potentials, connections = determine_entrance_list(world, player)
connections_tuple = (entrances_map, potentials, connections)
@@ -301,7 +613,8 @@ def within_dungeon(world, player):
dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
dungeon_builders[key].entrance_list = list(entrances_map[key])
recombinant_builders = {}
builder_info = None, None, world, player
entrances, splits = create_dungeon_entrances(world, player)
builder_info = entrances, splits, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
@@ -323,7 +636,7 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map,
dungeon_entrances = default_dungeon_entrances
if split_dungeon_entrances is None:
split_dungeon_entrances = split_region_starts
builder_info = dungeon_entrances, split_region_starts, world, player
builder_info = dungeon_entrances, split_dungeon_entrances, world, player
for name, split_list in split_dungeon_entrances.items():
builder = dungeon_builders.pop(name)
@@ -332,11 +645,14 @@ def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map,
split_builders = split_dungeon_builder(builder, split_list, builder_info)
dungeon_builders.update(split_builders)
for sub_name, split_entrances in split_list.items():
sub_builder = dungeon_builders[name+' '+sub_name]
key = name+' '+sub_name
if key not in dungeon_builders:
continue
sub_builder = dungeon_builders[key]
sub_builder.split_flag = True
entrance_list = list(split_entrances)
if name in flexible_starts.keys():
add_shuffled_entrances(sub_builder.sectors, flexible_starts[name], entrance_list)
for ent in entrances_map[name]:
add_shuffled_entrances(sub_builder.sectors, ent, entrance_list)
filtered_entrance_list = [x for x in entrance_list if x in entrances_map[name]]
sub_builder.entrance_list = filtered_entrance_list
@@ -353,6 +669,9 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
name = builder.name
if split_dungeon:
name = ' '.join(builder.name.split(' ')[:-1])
if len(builder.sectors) == 0:
del dungeon_builders[builder.name]
continue
origin_list = list(builder.entrance_list)
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
@@ -372,18 +691,18 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
combine_layouts(recombinant_builders, dungeon_builders, entrances_map)
world.dungeon_layouts[player] = {}
for builder in dungeon_builders.values():
builder.entrance_list = builder.layout_starts = builder.path_entrances = find_accessible_entrances(world, player, builder.all_entrances)
builder.entrance_list = builder.layout_starts = builder.path_entrances = find_accessible_entrances(world, player, builder)
world.dungeon_layouts[player] = dungeon_builders
def determine_entrance_list(world, player):
def determine_entrance_list_vanilla(world, player):
entrance_map = {}
potential_entrances = {}
connections = {}
for key, r_names in region_starts.items():
entrance_map[key] = []
if world.mode[player] == 'standard' and key in standard_starts.keys():
r_names = standard_starts[key]
r_names = ['Hyrule Castle Lobby']
for region_name in r_names:
region = world.get_region(region_name, player)
for ent in region.entrances:
@@ -399,10 +718,44 @@ def determine_entrance_list(world, player):
return entrance_map, potential_entrances, connections
def determine_entrance_list(world, player):
entrance_map = {}
potential_entrances = {}
connections = {}
for key, portal_list in dungeon_portals.items():
entrance_map[key] = []
r_names = {}
if key in dungeon_drops.keys():
for drop in dungeon_drops[key]:
r_names[drop] = None
for portal_name in portal_list:
portal = world.get_portal(portal_name, player)
r_names[portal.door.entrance.parent_region.name] = portal
for region_name, portal in r_names.items():
region = world.get_region(region_name, player)
for ent in region.entrances:
parent = ent.parent_region
if (parent.type != RegionType.Dungeon and parent.name != 'Menu') or parent.name == 'Sewer Drop':
std_inaccessible = is_standard_inaccessible(key, portal, world, player)
if parent.name not in world.inaccessible_regions[player] and not std_inaccessible:
entrance_map[key].append(region_name)
else:
if parent not in potential_entrances.keys():
potential_entrances[parent] = []
if region_name not in potential_entrances[parent]:
potential_entrances[parent].append(region_name)
connections[region_name] = parent
return entrance_map, potential_entrances, connections
def is_standard_inaccessible(key, portal, world, player):
return world.mode[player] == 'standard' and key in standard_starts and (not portal or portal.name not in standard_starts[key])
def add_shuffled_entrances(sectors, region_list, entrance_list):
for sector in sectors:
for region in sector.regions:
if region.name in region_list:
if region.name in region_list and region.name not in entrance_list:
entrance_list.append(region.name)
@@ -477,7 +830,7 @@ def aga_tower_enabled(enabled):
def cross_dungeon(world, player):
fix_big_key_doors_with_ugly_smalls(world, player)
overworld_prep(world, player)
add_inaccessible_doors(world, player)
entrances_map, potentials, connections = determine_entrance_list(world, player)
connections_tuple = (entrances_map, potentials, connections)
@@ -485,7 +838,9 @@ def cross_dungeon(world, player):
for key in dungeon_regions.keys():
all_regions += dungeon_regions[key]
all_sectors.extend(convert_to_sectors(all_regions, world, player))
dungeon_builders = create_dungeon_builders(all_sectors, connections_tuple, world, player)
merge_sectors(all_sectors, world, player)
entrances, splits = create_dungeon_entrances(world, player)
dungeon_builders = create_dungeon_builders(all_sectors, connections_tuple, world, player, entrances, splits)
for builder in dungeon_builders.values():
builder.entrance_list = list(entrances_map[builder.name])
dungeon_obj = world.get_dungeon(builder.name, player)
@@ -493,11 +848,11 @@ def cross_dungeon(world, player):
for region in sector.regions:
region.dungeon = dungeon_obj
for loc in region.locations:
if loc.name in key_only_locations:
if loc.forced_item:
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
loc.forced_item = loc.item = ItemFactory(key_name, player)
recombinant_builders = {}
builder_info = None, None, world, player
builder_info = entrances, splits, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
@@ -512,9 +867,12 @@ def cross_dungeon(world, player):
at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player))
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)
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
if world.keydropshuffle[player]:
target_items = 35 if world.retro[player] else 96
else:
target_items = 34 if world.retro[player] else 63
d_items = target_items - all_dungeon_items_cnt
world.pool_adjustment[player] = d_items
smooth_door_pairs(world, player)
@@ -525,13 +883,61 @@ def cross_dungeon(world, player):
reassign_boss('GT Lanmolas 2', 'middle', builder, gt, world, player)
reassign_boss('GT Moldorm', 'top', builder, gt, world, player)
sanctuary = world.get_region('Sanctuary', player)
d_name = sanctuary.dungeon.name
if d_name != 'Hyrule Castle':
possible_portals = []
for portal_name in dungeon_portals[d_name]:
portal = world.get_portal(portal_name, player)
if portal.door.name == 'Sanctuary S':
possible_portals.clear()
possible_portals.append(portal)
break
if not portal.destination and not portal.deadEnd:
possible_portals.append(portal)
if len(possible_portals) == 1:
world.sanc_portal[player] = possible_portals[0]
else:
reachable_portals = []
for portal in possible_portals:
start_area = portal.door.entrance.parent_region
state = ExplorationState(dungeon=d_name)
state.visit_region(start_area)
state.add_all_doors_check_unattached(start_area, world, player)
explore_state(state, world, player)
if state.visited(sanctuary):
reachable_portals.append(portal)
world.sanc_portal[player] = random.choice(reachable_portals)
for portal in world.dungeon_portals[player]:
if portal.door.roomIndex >= 0:
room = world.get_room(portal.door.roomIndex, player)
if room.palette is None:
name = portal.door.entrance.parent_region.dungeon.name
room.palette = palette_map[name]
for name, builder in dungeon_builders.items():
for region in builder.master_sector.regions:
for ext in region.exits:
if ext.door and ext.door.roomIndex >= 0:
room = world.get_room(ext.door.roomIndex, player)
if room.palette is None:
room.palette = palette_map[name]
eastfairies = world.get_room(0x89, player)
eastfairies.palette = palette_map[world.get_region('Eastern Courtyard', player).dungeon.name]
# other ones that could use programmatic treatment: Skull Boss x29, Hera Fairies xa7, Ice Boss xde
refine_hints(dungeon_builders)
def assign_cross_keys(dungeon_builders, world, player):
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
start = time.process_time()
total_keys = remaining = 29
if world.retro[player]:
remaining = 61 if world.keydropshuffle[player] else 29
else:
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
total_keys = remaining
total_candidates = 0
start_regions_map = {}
# Step 1: Find Small Key Door Candidates
@@ -609,7 +1015,7 @@ def assign_cross_keys(dungeon_builders, world, player):
dungeon.small_keys = []
else:
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
logger.info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed"), time.process_time()-start)
logger.info(f'{world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed")}: {time.process_time()-start}')
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
@@ -658,7 +1064,7 @@ def convert_to_sectors(region_names, world, player):
if existing not in matching_sectors:
matching_sectors.append(existing)
else:
if door is not None and door.controller is None and door.dest is None:
if door and not door.controller and not door.dest and not door.entranceFlag:
outstanding_doors.append(door)
sector = Sector()
if not new_sector:
@@ -672,6 +1078,30 @@ def convert_to_sectors(region_names, world, player):
return sectors
def merge_sectors(all_sectors, world, player):
if world.mixed_travel[player] == 'force':
sectors_to_remove = {}
merge_sectors = {}
for sector in all_sectors:
r_set = sector.region_set()
if 'PoD Arena Ledge' in r_set:
sectors_to_remove['Arenahover'] = sector
elif 'PoD Big Chest Balcony' in r_set:
sectors_to_remove['Hammerjump'] = sector
elif 'Mire Chest View' in r_set:
sectors_to_remove['Mire BJ'] = sector
elif 'PoD Falling Bridge Ledge' in r_set:
merge_sectors['Hammerjump'] = sector
elif 'PoD Arena Bridge' in r_set:
merge_sectors['Arenahover'] = sector
elif 'Mire BK Chest Ledge' in r_set:
merge_sectors['Mire BJ'] = sector
for key, old_sector in sectors_to_remove.items():
merge_sectors[key].regions.extend(old_sector.regions)
merge_sectors[key].outstanding_doors.extend(old_sector.outstanding_doors)
all_sectors.remove(old_sector)
# 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():
@@ -693,7 +1123,9 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
def valid_region_to_explore(region, world, player):
return region.type == RegionType.Dungeon or region.name in world.inaccessible_regions[player]
return region and (region.type == RegionType.Dungeon
or region.name in world.inaccessible_regions[player]
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard'))
def shuffle_key_doors(builder, world, player):
@@ -756,10 +1188,10 @@ def calc_used_dungeon_items(builder):
base = 4
if builder.bk_required and not builder.bk_provided:
base += 1
if builder.name == 'Hyrule Castle':
base -= 1 # Missing compass/map
if builder.name == 'Agahnims Tower':
base -= 2 # Missing both compass/map
# if builder.name == 'Hyrule Castle':
# base -= 1 # Missing compass/map
# if builder.name == 'Agahnims Tower':
# base -= 2 # Missing both compass/map
# gt can lose map once compasses work
return base
@@ -882,7 +1314,7 @@ def find_key_door_candidates(region, checked, world, player):
d = ext.door
if d and d.controller:
d = d.controller
if d is not None and not d.blocked and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
if d and not d.blocked and not d.entranceFlag and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
valid = False
if 0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]:
room = world.get_room(d.roomIndex, player)
@@ -1013,7 +1445,7 @@ 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:
if door.type in [DoorType.Normal, DoorType.Interior] and door not in skip and not door.entranceFlag:
partner = door.dest
skip.add(partner)
room_a = world.get_room(door.roomIndex, player)
@@ -1110,11 +1542,6 @@ def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
def overworld_prep(world, player):
find_inaccessible_regions(world, player)
add_inaccessible_doors(world, player)
def find_inaccessible_regions(world, player):
world.inaccessible_regions[player] = []
if world.mode[player] != 'inverted':
@@ -1144,14 +1571,26 @@ def find_inaccessible_regions(world, player):
logger.debug('%s', r)
def find_accessible_entrances(world, player, entrances):
if world.mode[player] != 'inverted':
def find_accessible_entrances(world, player, builder):
entrances = [region.name for region in (portal.door.entrance.parent_region for portal in world.dungeon_portals[player]) if region.dungeon.name == builder.name]
entrances.extend(drop_entrances[builder.name])
if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle':
start_regions = ['Hyrule Castle Courtyard']
elif world.mode[player] != 'inverted':
start_regions = ['Links House', 'Sanctuary']
else:
start_regions = ['Inverted Links House', 'Inverted Dark Sanctuary']
regs = convert_regions(start_regions, world, player)
visited_regions = set()
visited_entrances = []
# Add Sanctuary as an additional entrance in open mode, since you can save and quit to there
if world.mode[player] == 'open' and world.get_region('Sanctuary', player).dungeon.name == builder.name and 'Sanctuary' not in entrances:
entrances.append('Sanctuary')
visited_entrances.append('Sanctuary')
regs.remove(world.get_region('Sanctuary', player))
queue = deque(regs)
while len(queue) > 0:
next_region = queue.popleft()
@@ -1164,7 +1603,7 @@ def find_accessible_entrances(world, player, entrances):
connect = ext.connected_region
if connect is None or ext.door and ext.door.blocked:
continue
if connect.name in entrances:
if connect.name in entrances and connect not in visited_entrances:
visited_entrances.append(connect.name)
elif connect and connect not in queue and connect not in visited_regions:
queue.append(connect)
@@ -1176,6 +1615,9 @@ def valid_inaccessible_region(r):
def add_inaccessible_doors(world, player):
if world.mode[player] == 'standard':
create_door(world, player, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Ledge')
create_door(world, player, 'Hyrule Castle Entrance (West)', 'Hyrule Castle Ledge')
# todo: ignore standard mode hyrule castle ledge?
for inaccessible_region in world.inaccessible_regions[player]:
region = world.get_region(inaccessible_region, player)
@@ -1201,14 +1643,16 @@ def check_required_paths(paths, world, player):
if dungeon_name in world.dungeon_layouts[player].keys():
builder = world.dungeon_layouts[player][dungeon_name]
if len(paths[dungeon_name]) > 0:
states_to_explore = defaultdict(list)
states_to_explore = {}
for path in paths[dungeon_name]:
if type(path) is tuple:
states_to_explore[tuple([path[0]])].append(path[1])
states_to_explore[tuple([path[0]])] = path[1]
else:
states_to_explore[tuple(builder.path_entrances)].append(path)
states_to_explore[tuple(builder.path_entrances)] = path
cached_initial_state = None
for start_regs, dest_regs in states_to_explore.items():
if type(dest_regs) is not list:
dest_regs = [dest_regs]
check_paths = convert_regions(dest_regs, world, player)
start_regions = convert_regions(start_regs, world, player)
initial = start_regs == tuple(builder.path_entrances)
@@ -1233,10 +1677,8 @@ def check_required_paths(paths, world, player):
def determine_init_crystal(initial, state, start_regions):
if initial:
if initial or state is None:
return CrystalBarrier.Orange
if state is None:
raise Exception('Please start path checking from the entrances')
if len(start_regions) > 1:
raise NotImplementedError('Path checking for multiple start regions (not the entrances) not implemented, use more paths instead')
start_region = start_regions[0]
@@ -1247,7 +1689,7 @@ def determine_init_crystal(initial, state, start_regions):
elif start_region in state.visited_orange:
return CrystalBarrier.Orange
else:
raise Exception('Can\'t get to %s from initial state', start_region.name)
raise Exception(f'Can\'t get to {start_region.name} from initial state')
def explore_state(state, world, player):
@@ -1260,13 +1702,14 @@ def explore_state(state, world, player):
def check_if_regions_visited(state, check_paths):
valid = True
valid = False
breaking_region = None
for region_target in check_paths:
if not state.visited_at_all(region_target):
valid = False
breaking_region = region_target
if state.visited_at_all(region_target):
valid = True
break
else:
breaking_region = region_target
return valid, breaking_region
@@ -1292,12 +1735,15 @@ class DROptions(Flag):
Town_Portal = 0x02 # If on, Players will start with mirror scroll
Map_Info = 0x04
Debug = 0x08
Rails = 0x10 # If on, draws rails
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
# DATA GOES DOWN HERE
logical_connections = [
('Hyrule Dungeon North Abyss Catwalk Dropdown', 'Hyrule Dungeon North Abyss'),
('Hyrule Dungeon Cellblock Door', 'Hyrule Dungeon Cell'),
('Hyrule Dungeon Cell Exit', 'Hyrule Dungeon Cellblock'),
('Hyrule Castle Throne Room Tapestry', 'Hyrule Castle Behind Tapestry'),
('Hyrule Castle Tapestry Backwards', 'Hyrule Castle Throne Room'),
('Sewers Secret Room Push Block', 'Sewers Secret Room Blocked Path'),
@@ -1369,6 +1815,8 @@ logical_connections = [
('Thieves Blocked Entry Path', 'Thieves Basement Block'),
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
('Thieves Conveyor Block Path', 'Thieves Conveyor Bridge'),
("Thieves Blind's Cell Door", "Thieves Blind's Cell Interior"),
("Thieves Blind's Cell Exit", "Thieves Blind's Cell"),
('Ice Cross Bottom Push Block Left', 'Ice Floor Switch'),
('Ice Cross Right Push Block Top', 'Ice Bomb Drop'),
('Ice Big Key Push Block', 'Ice Dead End'),
@@ -1426,6 +1874,7 @@ logical_connections = [
('GT Hookshot South-North Path', 'GT Hookshot North Platform'),
('GT Hookshot Platform Blue Barrier', 'GT Hookshot South Entry'),
('GT Hookshot Entry Blue Barrier', 'GT Hookshot South Platform'),
('GT Hookshot Entry Boomerang Path', 'GT Hookshot South Platform'),
('GT Double Switch Orange Barrier', 'GT Double Switch Switches'),
('GT Double Switch Orange Barrier 2', 'GT Double Switch Key Spot'),
('GT Double Switch Transition Blue', 'GT Double Switch Exit'),
@@ -2075,3 +2524,61 @@ boss_indicator = {
'Turtle Rock': (0x18, 'TR Boss SW'),
'Ganons Tower': (0x1a, 'GT Agahnim 2 SW')
}
palette_map = {
'Hyrule Castle': 0x0, 'Eastern Palace': 0xb, 'Desert Palace': 0x4, 'Agahnims Tower': 0xc,
'Swamp Palace': 0x8, 'Palace of Darkness': 0x10, 'Misery Mire': 0x11, 'Skull Woods': 0xe,
'Ice Palace': 0x14, 'Tower of Hera': 0x6, 'Thieves Town': 0x17, 'Turtle Rock': 0x19,
'Ganons Tower': 0x1b
}
# todo: inverted
portal_map = {
'Sanctuary': ('Sanctuary', 'Sanctuary Exit'),
'Hyrule Castle West': ('Hyrule Castle Entrance (West)', 'Hyrule Castle Exit (West)'),
'Hyrule Castle South': ('Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)'),
'Hyrule Castle East': ('Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)'),
'Eastern': ('Eastern Palace', 'Eastern Palace Exit'),
'Desert West': ('Desert Palace Entrance (West)', 'Desert Palace Exit (West)'),
'Desert South': ('Desert Palace Entrance (South)', 'Desert Palace Exit (South)'),
'Desert East': ('Desert Palace Entrance (East)', 'Desert Palace Exit (East)'),
'Desert Back': ('Desert Palace Entrance (North)', 'Desert Palace Exit (North)'),
'Turtle Rock Lazy Eyes': ('Dark Death Mountain Ledge (West)', 'Turtle Rock Ledge Exit (West)'),
'Turtle Rock Eye Bridge': ('Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Isolated Ledge Exit'),
'Turtle Rock Chest': ('Dark Death Mountain Ledge (East)', 'Turtle Rock Ledge Exit (East)'),
'Agahnims Tower': ('Agahnims Tower', 'Agahnims Tower Exit'),
'Swamp': ('Swamp Palace', 'Swamp Palace Exit'),
'Palace of Darkness': ('Palace of Darkness', 'Palace of Darkness Exit'),
'Mire': ('Misery Mire', 'Misery Mire Exit'),
'Skull 2 West': ('Skull Woods Second Section Door (West)', 'Skull Woods Second Section Exit (West)'),
'Skull 2 East': ('Skull Woods Second Section Door (East)', 'Skull Woods Second Section Exit (East)'),
'Skull 1': ('Skull Woods First Section Door', 'Skull Woods First Section Exit'),
'Skull 3': ('Skull Woods Final Section', 'Skull Woods Final Section Exit'),
'Ice': ('Ice Palace', 'Ice Palace Exit'),
'Hera': ('Tower of Hera', 'Tower of Hera Exit'),
'Thieves Town': ('Thieves Town', 'Thieves Town Exit'),
'Turtle Rock Main': ('Turtle Rock', 'Turtle Rock Exit (Front)'),
'Ganons Tower': ('Ganons Tower', 'Ganons Tower Exit'),
}
split_portals = {
'Desert Palace': ['Back', 'Main'],
'Skull Woods': ['1', '2', '3']
}
split_portal_defaults = {
'Desert Palace': {
'Desert Back Lobby': 'Back',
'Desert Main Lobby': 'Main',
'Desert West Lobby': 'Main',
'Desert East Lobby': 'Main'
},
'Skull Woods': {
'Skull 1 Lobby': '1',
'Skull 2 East Lobby': '2',
'Skull 2 West Lobby': '2',
'Skull 3 Lobby': '3'
}
}

240
Doors.py
View File

@@ -1,5 +1,5 @@
from BaseClasses import Door, DoorType, Direction, CrystalBarrier
from BaseClasses import Door, DoorType, Direction, CrystalBarrier, Portal
from RoomData import PairedDoor
# constants
@@ -46,18 +46,21 @@ def create_doors(world, player):
create_door(player, 'Hyrule Castle Lobby W', Nrml).dir(We, 0x61, Mid, High).toggler().pos(0),
create_door(player, 'Hyrule Castle Lobby E', Nrml).dir(Ea, 0x61, Mid, High).toggler().pos(2),
create_door(player, 'Hyrule Castle Lobby WN', Nrml).dir(We, 0x61, Top, High).pos(1),
create_door(player, 'Hyrule Castle Lobby S', Nrml).dir(So, 0x61, Mid, High).pos(4).portal(Z, 0x22, 1),
create_door(player, 'Hyrule Castle Lobby North Stairs', StrS).dir(No, 0x61, Mid, High),
create_door(player, 'Hyrule Castle West Lobby E', Nrml).dir(Ea, 0x60, Mid, Low).toggler().pos(1),
create_door(player, 'Hyrule Castle West Lobby N', Nrml).dir(No, 0x60, Right, Low).pos(0),
create_door(player, 'Hyrule Castle West Lobby EN', Nrml).dir(Ea, 0x60, Top, High).pos(3),
create_door(player, 'Hyrule Castle West Lobby S', Nrml).dir(So, 0x60, Right, High).pos(2).portal(X, 0x02),
create_door(player, 'Hyrule Castle East Lobby W', Nrml).dir(We, 0x62, Mid, Low).toggler().pos(0),
create_door(player, 'Hyrule Castle East Lobby N', Nrml).dir(No, 0x62, Mid, High).pos(3),
create_door(player, 'Hyrule Castle East Lobby NW', Nrml).dir(No, 0x62, Left, Low).pos(2),
create_door(player, 'Hyrule Castle East Lobby S', Nrml).dir(So, 0x62, Left, High).pos(1).portal(Z, 0x22),
create_door(player, 'Hyrule Castle East Hall W', Nrml).dir(We, 0x52, Top, Low).pos(0),
create_door(player, 'Hyrule Castle East Hall S', Nrml).dir(So, 0x52, Mid, High).pos(2),
create_door(player, 'Hyrule Castle East Hall SW', Nrml).dir(So, 0x52, Left, Low).pos(1),
create_door(player, 'Hyrule Castle East Hall S', Nrml).dir(So, 0x52, Mid, High).pos(2).portal(Z, 0x22),
create_door(player, 'Hyrule Castle East Hall SW', Nrml).dir(So, 0x52, Left, Low).pos(1).portal(Z, 0x22, 1),
create_door(player, 'Hyrule Castle West Hall E', Nrml).dir(Ea, 0x50, Top, Low).pos(0),
create_door(player, 'Hyrule Castle West Hall S', Nrml).dir(So, 0x50, Right, Low).pos(1),
create_door(player, 'Hyrule Castle West Hall S', Nrml).dir(So, 0x50, Right, Low).pos(1).portal(X, 0x02, 1),
create_door(player, 'Hyrule Castle Back Hall W', Nrml).dir(We, 0x01, Top, Low).pos(0),
create_door(player, 'Hyrule Castle Back Hall E', Nrml).dir(Ea, 0x01, Top, Low).pos(1),
create_door(player, 'Hyrule Castle Back Hall Down Stairs', Sprl).dir(Dn, 0x01, 0, HTL).ss(A, 0x2a, 0x00),
@@ -80,7 +83,7 @@ def create_doors(world, player):
create_door(player, 'Hyrule Dungeon Guardroom Catwalk Edge', Open).dir(Ea, 0x81, None, High).edge(3, S, 0x10),
create_door(player, 'Hyrule Dungeon Guardroom Abyss Edge', Open).dir(Ea, 0x81, None, Low).edge(4, X, 0x18),
create_door(player, 'Hyrule Dungeon Guardroom N', Nrml).dir(No, 0x81, Left, Low).pos(0),
create_door(player, 'Hyrule Dungeon Armory S', Nrml).dir(So, 0x71, Left, Low).trap(0x2).pos(1),
create_door(player, 'Hyrule Dungeon Armory S', Nrml).dir(So, 0x71, Left, Low).trap(0x2).pos(1).portal(Z, 0x00, 1),
create_door(player, 'Hyrule Dungeon Armory ES', Intr).dir(Ea, 0x71, Left, Low).pos(2),
create_door(player, 'Hyrule Dungeon Armory Boomerang WS', Intr).dir(We, 0x71, Left, Low).pos(2),
create_door(player, 'Hyrule Dungeon Armory Interior Key Door N', Intr).dir(No, 0x71, Left, High).small_key().pos(0),
@@ -89,19 +92,21 @@ def create_doors(world, player):
create_door(player, 'Hyrule Dungeon Staircase Up Stairs', Sprl).dir(Up, 0x70, 2, LTH).ss(A, 0x32, 0x94, True),
create_door(player, 'Hyrule Dungeon Staircase Down Stairs', Sprl).dir(Dn, 0x70, 1, HTH).ss(A, 0x11, 0x58),
create_door(player, 'Hyrule Dungeon Cellblock Up Stairs', Sprl).dir(Up, 0x80, 0, HTH).ss(A, 0x1a, 0x44),
create_door(player, 'Hyrule Dungeon Cellblock Door', Lgcl).big_key(),
create_door(player, 'Hyrule Dungeon Cell Exit', Lgcl),
# sewers
create_door(player, 'Sewers Behind Tapestry S', Nrml).dir(So, 0x41, Mid, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Sewers Behind Tapestry S', Nrml).dir(So, 0x41, Mid, High).no_exit().trap(0x4).pos(0).portal(Z, 0x22),
create_door(player, 'Sewers Behind Tapestry Down Stairs', Sprl).dir(Dn, 0x41, 0, HTH).ss(S, 0x12, 0xb0),
create_door(player, 'Sewers Rope Room Up Stairs', Sprl).dir(Up, 0x42, 0, HTH).ss(S, 0x1b, 0x9c),
create_door(player, 'Sewers Rope Room North Stairs', StrS).dir(No, 0x42, Mid, High),
create_door(player, 'Sewers Dark Cross South Stairs', StrS).dir(So, 0x32, Mid, High),
create_door(player, 'Sewers Dark Cross Key Door N', Nrml).dir(No, 0x32, Mid, High).small_key().pos(0),
create_door(player, 'Sewers Water S', Nrml).dir(So, 0x22, Mid, High).small_key().pos(0),
create_door(player, 'Sewers Water S', Nrml).dir(So, 0x22, Mid, High).small_key().pos(0).portal(Z, 0x22),
create_door(player, 'Sewers Water W', Nrml).dir(We, 0x22, Bot, High).pos(1),
create_door(player, 'Sewers Key Rat E', Nrml).dir(Ea, 0x21, Bot, High).pos(1),
create_door(player, 'Sewers Key Rat Key Door N', Nrml).dir(No, 0x21, Right, High).small_key().pos(0),
create_door(player, 'Sewers Secret Room Key Door S', Nrml).dir(So, 0x11, Right, High).small_key().pos(2),
create_door(player, 'Sewers Secret Room Key Door S', Nrml).dir(So, 0x11, Right, High).small_key().pos(2).portal(X, 0x02),
create_door(player, 'Sewers Rat Path WS', Intr).dir(We, 0x11, Bot, High).pos(1),
create_door(player, 'Sewers Rat Path WN', Intr).dir(We, 0x11, Top, High).pos(0),
create_door(player, 'Sewers Secret Room ES', Intr).dir(Ea, 0x11, Bot, High).pos(1),
@@ -111,11 +116,13 @@ def create_doors(world, player):
create_door(player, 'Sewers Pull Switch Down Stairs', Sprl).dir(Dn, 0x02, 0, HTL).ss(S, 0x12, 0x80),
create_door(player, 'Sewers Yet More Rats S', Intr).dir(So, 0x02, Mid, Low).pos(1),
create_door(player, 'Sewers Pull Switch N', Intr).dir(No, 0x02, Mid, Low).pos(1),
create_door(player, 'Sewers Pull Switch S', Nrml).dir(So, 0x02, Mid, Low).trap(0x4).toggler().pos(0),
create_door(player, 'Sewers Pull Switch S', Nrml).dir(So, 0x02, Mid, Low).trap(0x4).toggler().pos(0).portal(Z, 0x20, 1),
# logically one way the sanc, but should be linked - also toggle
create_door(player, 'Sanctuary N', Nrml).dir(No, 0x12, Mid, High).no_exit().toggler().pos(0),
create_door(player, 'Sanctuary S', Nrml).dir(So, 0x12, Mid, High).pos(2).portal(Z, 0x22, 1),
# Eastern Palace
create_door(player, 'Eastern Lobby S', Nrml).dir(So, 0xc9, Mid, High).pos(4).portal(Z, 0x20),
create_door(player, 'Eastern Lobby N', Intr).dir(No, 0xc9, Mid, High).pos(0),
create_door(player, 'Eastern Lobby Bridge S', Intr).dir(So, 0xc9, Mid, High).pos(0),
create_door(player, 'Eastern Lobby NW', Intr).dir(No, 0xc9, Left, High).pos(2),
@@ -123,11 +130,11 @@ def create_doors(world, player):
create_door(player, 'Eastern Lobby NE', Intr).dir(No, 0xc9, Right, High).pos(3),
create_door(player, 'Eastern Lobby Right Ledge SE', Intr).dir(So, 0xc9, Right, High).pos(3),
create_door(player, 'Eastern Lobby Bridge N', Nrml).dir(No, 0xc9, Mid, High).pos(1).trap(0x2),
create_door(player, 'Eastern Cannonball S', Nrml).dir(So, 0xb9, Mid, High).pos(2),
create_door(player, 'Eastern Cannonball S', Nrml).dir(So, 0xb9, Mid, High).pos(2).portal(Z, 0x22),
create_door(player, 'Eastern Cannonball N', Nrml).dir(No, 0xb9, Mid, High).pos(1),
create_door(player, 'Eastern Cannonball Ledge WN', Nrml).dir(We, 0xb9, Top, High).pos(3),
create_door(player, 'Eastern Cannonball Ledge Key Door EN', Nrml).dir(Ea, 0xb9, Top, High).small_key().pos(0),
create_door(player, 'Eastern Courtyard Ledge S', Nrml).dir(So, 0xa9, Mid, High).pos(5),
create_door(player, 'Eastern Courtyard Ledge S', Nrml).dir(So, 0xa9, Mid, High).pos(5).portal(Z, 0x22),
create_door(player, 'Eastern Courtyard Ledge W', Nrml).dir(We, 0xa9, Mid, High).trap(0x4).pos(0),
create_door(player, 'Eastern Courtyard Ledge E', Nrml).dir(Ea, 0xa9, Mid, High).trap(0x2).pos(1),
create_door(player, 'Eastern East Wing W', Nrml).dir(We, 0xaa, Mid, High).pos(4),
@@ -147,7 +154,7 @@ def create_doors(world, player):
create_door(player, 'Eastern Compass Room EN', Intr).dir(Ea, 0xa8, Top, High).pos(3),
create_door(player, 'Eastern Hint Tile WN', Intr).dir(We, 0xa8, Top, High).pos(3),
create_door(player, 'Eastern Hint Tile EN', Nrml).dir(Ea, 0xa8, Top, Low).pos(4),
create_door(player, 'Eastern Hint Tile Blocked Path SE', Nrml).dir(So, 0xa8, Right, High).small_key().pos(2).kill(),
create_door(player, 'Eastern Hint Tile Blocked Path SE', Nrml).dir(So, 0xa8, Right, High).small_key().pos(2).kill().portal(X, 0x02),
create_door(player, 'Eastern Hint Tile Push Block', Lgcl),
create_door(player, 'Eastern Courtyard WN', Nrml).dir(We, 0xa9, Top, Low).pos(3),
create_door(player, 'Eastern Courtyard EN', Nrml).dir(Ea, 0xa9, Top, Low).pos(4),
@@ -155,14 +162,14 @@ def create_doors(world, player):
create_door(player, 'Eastern Courtyard Potholes', Hole),
create_door(player, 'Eastern Fairies\' Warp', Warp),
create_door(player, 'Eastern Map Valley WN', Nrml).dir(We, 0xaa, Top, Low).pos(1),
create_door(player, 'Eastern Map Valley SW', Nrml).dir(So, 0xaa, Left, High).pos(5),
create_door(player, 'Eastern Map Valley SW', Nrml).dir(So, 0xaa, Left, High).pos(5).portal(Z, 0x02),
create_door(player, 'Eastern Dark Square NW', Nrml).dir(No, 0xba, Left, High).trap(0x2).pos(1),
create_door(player, 'Eastern Dark Square Key Door WN', Nrml).dir(We, 0xba, Top, High).small_key().pos(0),
create_door(player, 'Eastern Dark Square EN', Intr).dir(Ea, 0xba, Top, High).pos(2),
create_door(player, 'Eastern Dark Pots WN', Intr).dir(We, 0xba, Top, High).pos(2),
create_door(player, 'Eastern Big Key EN', Nrml).dir(Ea, 0xb8, Top, High).pos(1),
create_door(player, 'Eastern Big Key NE', Nrml).dir(No, 0xb8, Right, High).big_key().pos(0),
create_door(player, 'Eastern Darkness S', Nrml).dir(So, 0x99, Mid, High).small_key().pos(1),
create_door(player, 'Eastern Darkness S', Nrml).dir(So, 0x99, Mid, High).small_key().pos(1).portal(Z, 0x20),
create_door(player, 'Eastern Darkness NE', Intr).dir(No, 0x99, Right, High).pos(2),
create_door(player, 'Eastern Rupees SE', Intr).dir(So, 0x99, Right, High).pos(2),
# Up is a keydoor and down is not. Only the up stairs should be considered a key door for now.
@@ -180,6 +187,7 @@ def create_doors(world, player):
create_door(player, 'Eastern Boss SE', Nrml).dir(So, 0xc8, Right, High).no_exit().trap(0x4).pos(0),
# Desert Palace
create_door(player, 'Desert Main Lobby S', Nrml).dir(So, 0x84, Mid, High).pos(0).portal(Z, 0x22),
create_door(player, 'Desert Main Lobby NW Edge', Open).dir(No, 0x84, None, High).edge(3, A, 0x20),
create_door(player, 'Desert Main Lobby N Edge', Open).dir(No, 0x84, None, High).edge(4, A, 0xa0),
create_door(player, 'Desert Main Lobby NE Edge', Open).dir(No, 0x84, None, High).edge(5, S, 0x20),
@@ -191,12 +199,13 @@ def create_doors(world, player):
create_door(player, 'Desert Dead End Edge', Open).dir(So, 0x74, None, High).edge(4, Z, 0xa0),
create_door(player, 'Desert East Wing W Edge', Open).dir(We, 0x85, None, High).edge(5, A, 0xa0),
create_door(player, 'Desert East Wing N Edge', Open).dir(No, 0x85, None, High).edge(6, A, 0x20),
create_door(player, 'Desert East Lobby S', Nrml).dir(So, 0x85, Right, High).pos(2).portal(X, 0x00),
create_door(player, 'Desert East Lobby WS', Intr).dir(We, 0x85, Bot, High).pos(3),
create_door(player, 'Desert East Wing ES', Intr).dir(Ea, 0x85, Bot, High).pos(3),
create_door(player, 'Desert East Wing Key Door EN', Intr).dir(Ea, 0x85, Top, High).small_key().pos(1),
create_door(player, 'Desert Compass Key Door WN', Intr).dir(We, 0x85, Top, High).small_key().pos(1),
create_door(player, 'Desert Compass NW', Nrml).dir(No, 0x85, Right, High).trap(0x4).pos(0),
create_door(player, 'Desert Cannonball S', Nrml).dir(So, 0x75, Right, High).pos(1),
create_door(player, 'Desert Cannonball S', Nrml).dir(So, 0x75, Right, High).pos(1).portal(Z, 0x02),
create_door(player, 'Desert Arrow Pot Corner S Edge', Open).dir(So, 0x75, None, High).edge(6, Z, 0x20),
create_door(player, 'Desert Arrow Pot Corner W Edge', Open).dir(We, 0x75, None, High).edge(2, Z, 0x20),
create_door(player, 'Desert Arrow Pot Corner NW', Intr).dir(No, 0x75, Left, High).pos(0),
@@ -219,10 +228,12 @@ def create_doors(world, player):
create_door(player, 'Desert Big Chest SW', Intr).dir(So, 0x73, Left, High).pos(0),
create_door(player, 'Desert West Wing N Edge', Open).dir(No, 0x83, None, High).edge(2, S, 0x20),
create_door(player, 'Desert West Wing WS', Intr).dir(We, 0x83, Bot, High).pos(2),
create_door(player, 'Desert West S', Nrml).dir(So, 0x83, Left, High).pos(1).portal(Z, 0x00),
create_door(player, 'Desert West Lobby ES', Intr).dir(Ea, 0x83, Bot, High).pos(2),
create_door(player, 'Desert West Lobby NW', Intr).dir(No, 0x83, Left, High).pos(0),
create_door(player, 'Desert Fairy Fountain SW', Intr).dir(So, 0x83, Left, High).pos(0),
# Desert Back
create_door(player, 'Desert Back Lobby S', Nrml).dir(So, 0x63, Left, High).pos(2).portal(Z, 0x00),
create_door(player, 'Desert Back Lobby NW', Intr).dir(No, 0x63, Left, High).pos(1),
create_door(player, 'Desert Tiles 1 SW', Intr).dir(So, 0x63, Left, High).pos(1),
create_door(player, 'Desert Tiles 1 Up Stairs', Sprl).dir(Up, 0x63, 0, HTH).ss(A, 0x1b, 0x6c, True).small_key().pos(0),
@@ -232,13 +243,14 @@ def create_doors(world, player):
create_door(player, 'Desert Four Statues ES', Intr).dir(Ea, 0x53, Bot, High).pos(1),
create_door(player, 'Desert Beamos Hall WS', Intr).dir(We, 0x53, Bot, High).pos(1),
create_door(player, 'Desert Beamos Hall NE', Nrml).dir(No, 0x53, Right, High).small_key().pos(2),
create_door(player, 'Desert Tiles 2 SE', Nrml).dir(So, 0x43, Right, High).small_key().pos(2).kill(),
create_door(player, 'Desert Tiles 2 SE', Nrml).dir(So, 0x43, Right, High).small_key().pos(2).kill().portal(X, 0x00),
create_door(player, 'Desert Tiles 2 NE', Intr).dir(No, 0x43, Right, High).small_key().pos(1),
create_door(player, 'Desert Wall Slide SE', Intr).dir(So, 0x43, Right, High).small_key().pos(1),
create_door(player, 'Desert Wall Slide NW', Nrml).dir(No, 0x43, Left, High).big_key().pos(0).no_entrance(),
create_door(player, 'Desert Boss SW', Nrml).dir(So, 0x33, Left, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Desert Boss SW', Nrml).dir(So, 0x33, Left, High).no_exit().trap(0x4).pos(0), # .portal(Z, 0x00), problem with enemizer
# Hera
create_door(player, 'Hera Lobby S', Nrml).dir(So, 0x77, Mid, Low).pos(0).portal(Z, 0x22, 1),
create_door(player, 'Hera Lobby Down Stairs', Sprl).dir(Dn, 0x77, 3, HTL).ss(Z, 0x21, 0x90, False, True),
create_door(player, 'Hera Lobby Key Stairs', Sprl).dir(Dn, 0x77, 1, HTL).ss(A, 0x12, 0x80).small_key().pos(1),
create_door(player, 'Hera Lobby Up Stairs', Sprl).dir(Up, 0x77, 2, HTL).ss(X, 0x2b, 0x5c, False, True),
@@ -275,6 +287,7 @@ def create_doors(world, player):
create_door(player, 'Hera Boss Inner Hole', Hole),
# Castle Tower
create_door(player, 'Tower Lobby S', Nrml).dir(So, 0xe0, Left, High).pos(3).portal(Z, 0x00),
create_door(player, 'Tower Lobby NW', Intr).dir(No, 0xe0, Left, High).pos(1),
create_door(player, 'Tower Gold Knights SW', Intr).dir(So, 0xe0, Left, High).pos(1),
create_door(player, 'Tower Gold Knights EN', Intr).dir(Ea, 0xe0, Top, High).pos(0),
@@ -311,6 +324,7 @@ def create_doors(world, player):
create_door(player, 'Tower Agahnim 1 SW', Nrml).dir(So, 0x20, Left, High).no_exit().trap(0x4).pos(0),
# Palace of Darkness
create_door(player, 'PoD Lobby S', Nrml).dir(So, 0x4a, Mid, High).pos(4).portal(Z, 0x20),
create_door(player, 'PoD Lobby N', Intr).dir(No, 0x4a, Mid, High).pos(3),
create_door(player, 'PoD Lobby NW', Intr).dir(No, 0x4a, Left, High).pos(0),
create_door(player, 'PoD Lobby NE', Intr).dir(No, 0x4a, Right, High).pos(1),
@@ -323,7 +337,7 @@ def create_doors(world, player):
create_door(player, 'PoD Shooter Room Up Stairs', Sprl).dir(Up, 0x09, 1, HTH).ss(A, 0x1b, 0x6c, True, True),
create_door(player, 'PoD Warp Room Up Stairs', Sprl).dir(Up, 0x09, 0, HTH).ss(S, 0x1a, 0x6c, True, True),
create_door(player, 'PoD Warp Room Warp', Warp),
create_door(player, 'PoD Pit Room S', Nrml).dir(So, 0x3a, Mid, High).small_key().pos(0),
create_door(player, 'PoD Pit Room S', Nrml).dir(So, 0x3a, Mid, High).small_key().pos(0).portal(Z, 0x22),
create_door(player, 'PoD Pit Room NW', Nrml).dir(No, 0x3a, Left, High).pos(1),
create_door(player, 'PoD Pit Room NE', Nrml).dir(No, 0x3a, Right, High).pos(2),
create_door(player, 'PoD Pit Room Freefall', Hole),
@@ -335,8 +349,8 @@ def create_doors(world, player):
create_door(player, 'PoD Basement Ledge Up Stairs', Sprl).dir(Up, 0x0a, 0, HTH).ss(A, 0x1a, 0xec).small_key().pos(0),
create_door(player, 'PoD Basement Ledge Drop Down', Lgcl),
create_door(player, 'PoD Stalfos Basement Warp', Warp),
create_door(player, 'PoD Arena Main SW', Nrml).dir(So, 0x2a, Left, High).pos(4),
create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5),
create_door(player, 'PoD Arena Main SW', Nrml).dir(So, 0x2a, Left, High).pos(4).portal(Z, 0x22),
create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5).portal(X, 0x22),
create_door(player, 'PoD Arena Main NW', Nrml).dir(No, 0x2a, Left, High).small_key().pos(1),
create_door(player, 'PoD Arena Main NE', Nrml).dir(No, 0x2a, Right, High).no_exit().trap(0x4).pos(0),
create_door(player, 'PoD Arena Main Crystal Path', Lgcl),
@@ -353,14 +367,14 @@ def create_doors(world, player):
create_door(player, 'PoD Map Balcony WS', Nrml).dir(We, 0x2b, Bot, High).pos(1),
create_door(player, 'PoD Map Balcony South Stairs', StrS).dir(So, 0x2b, Left, High),
create_door(player, 'PoD Conveyor North Stairs', StrS).dir(No, 0x3b, Left, High),
create_door(player, 'PoD Conveyor SW', Nrml).dir(So, 0x3b, Left, High).pos(0),
create_door(player, 'PoD Conveyor SW', Nrml).dir(So, 0x3b, Left, High).pos(0).portal(Z, 0x02),
create_door(player, 'PoD Mimics 1 NW', Nrml).dir(No, 0x4b, Left, High).trap(0x4).pos(0),
create_door(player, 'PoD Mimics 1 SW', Intr).dir(So, 0x4b, Left, High).pos(1),
create_door(player, 'PoD Jelly Hall NW', Intr).dir(No, 0x4b, Left, High).pos(1),
create_door(player, 'PoD Jelly Hall NE', Intr).dir(No, 0x4b, Right, High).pos(2),
create_door(player, 'PoD Warp Hint SE', Intr).dir(So, 0x4b, Right, High).pos(2),
create_door(player, 'PoD Warp Hint Warp', Warp),
create_door(player, 'PoD Falling Bridge SW', Nrml).dir(So, 0x1a, Left, High).small_key().pos(3),
create_door(player, 'PoD Falling Bridge SW', Nrml).dir(So, 0x1a, Left, High).small_key().pos(3).portal(Z, 0x02),
create_door(player, 'PoD Falling Bridge WN', Nrml).dir(We, 0x1a, Top, High).small_key().pos(1),
create_door(player, 'PoD Falling Bridge EN', Intr).dir(Ea, 0x1a, Top, High).pos(4),
create_door(player, 'PoD Falling Bridge Path N', Lgcl),
@@ -371,13 +385,13 @@ def create_doors(world, player):
create_door(player, 'PoD Compass Room WN', Intr).dir(We, 0x1a, Top, High).pos(4),
create_door(player, 'PoD Compass Room SE', Intr).dir(So, 0x1a, Mid, High).small_key().pos(0),
create_door(player, 'PoD Harmless Hellway NE', Intr).dir(No, 0x1a, Right, High).small_key().pos(0),
create_door(player, 'PoD Harmless Hellway SE', Nrml).dir(So, 0x1a, Right, High).pos(5),
create_door(player, 'PoD Harmless Hellway SE', Nrml).dir(So, 0x1a, Right, High).pos(5).portal(X, 0x00),
create_door(player, 'PoD Compass Room W Down Stairs', Sprl).dir(Dn, 0x1a, 0, HTH).ss(S, 0x12, 0x50, True, True),
create_door(player, 'PoD Compass Room E Down Stairs', Sprl).dir(Dn, 0x1a, 1, HTH).ss(S, 0x11, 0xb0, True, True),
create_door(player, 'PoD Dark Basement W Up Stairs', Sprl).dir(Up, 0x6a, 0, HTH).ss(S, 0x1b, 0x3c, True),
create_door(player, 'PoD Dark Basement E Up Stairs', Sprl).dir(Up, 0x6a, 1, HTH).ss(S, 0x1b, 0x9c, True),
create_door(player, 'PoD Dark Alley NE', Nrml).dir(No, 0x6a, Right, High).big_key().pos(0),
create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1).kill(),
create_door(player, 'PoD Mimics 2 SW', Nrml).dir(So, 0x1b, Left, High).pos(1).kill().portal(Z, 0x00),
create_door(player, 'PoD Mimics 2 NW', Intr).dir(No, 0x1b, Left, High).pos(0),
create_door(player, 'PoD Bow Statue SW', Intr).dir(So, 0x1b, Left, High).pos(0),
create_door(player, 'PoD Bow Statue Down Ladder', Lddr).no_entrance(),
@@ -391,6 +405,7 @@ def create_doors(world, player):
create_door(player, 'PoD Callback Warp', Warp),
create_door(player, 'PoD Boss SE', Nrml).dir(So, 0x5a, Right, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Swamp Lobby S', Nrml).dir(So, 0x28, Mid, High).pos(1).portal(Z, 0x22),
create_door(player, 'Swamp Lobby Moat', Lgcl),
create_door(player, 'Swamp Entrance Down Stairs', Sprl).dir(Dn, 0x28, 0, HTH).ss(A, 0x11, 0x80).small_key().pos(0),
create_door(player, 'Swamp Entrance Moat', Lgcl),
@@ -417,7 +432,7 @@ def create_doors(world, player):
create_door(player, 'Swamp Hammer Switch SW', Intr).dir(So, 0x37, Left, High).small_key().pos(2),
create_door(player, 'Swamp Hammer Switch WN', Nrml).dir(We, 0x37, Top, High).pos(0),
create_door(player, 'Swamp Hub ES', Nrml).dir(Ea, 0x36, Bot, High).pos(4),
create_door(player, 'Swamp Hub S', Nrml).dir(So, 0x36, Mid, High).pos(5),
create_door(player, 'Swamp Hub S', Nrml).dir(So, 0x36, Mid, High).pos(5), # .portal(Z, 0x22, 1), couldn't figure this out
create_door(player, 'Swamp Hub WS', Nrml).dir(We, 0x36, Bot, High).pos(3),
create_door(player, 'Swamp Hub WN', Nrml).dir(We, 0x36, Top, High).small_key().pos(2),
create_door(player, 'Swamp Hub Hook Path', Lgcl),
@@ -458,7 +473,7 @@ def create_doors(world, player):
create_door(player, 'Swamp Attic Down Stairs', Sprl).dir(Dn, 0x54, 0, HTH).ss(Z, 0x12, 0x80).kill(),
create_door(player, 'Swamp Attic Left Pit', Hole),
create_door(player, 'Swamp Attic Right Pit', Hole),
create_door(player, 'Swamp Push Statue S', Nrml).dir(So, 0x26, Mid, High).small_key().pos(0),
create_door(player, 'Swamp Push Statue S', Nrml).dir(So, 0x26, Mid, High).small_key().pos(0).portal(Z, 0x20),
create_door(player, 'Swamp Push Statue NW', Intr).dir(No, 0x26, Left, High).pos(1),
create_door(player, 'Swamp Push Statue NE', Intr).dir(No, 0x26, Right, High).pos(2),
create_door(player, 'Swamp Push Statue Down Stairs', Sprl).dir(Dn, 0x26, 2, HTH).ss(X, 0x12, 0xc0, False, True),
@@ -479,7 +494,7 @@ def create_doors(world, player):
create_door(player, 'Swamp Basement Shallows NW', Nrml).dir(No, 0x76, Left, High).toggler().pos(2),
create_door(player, 'Swamp Basement Shallows EN', Intr).dir(Ea, 0x76, Top, High).pos(0),
create_door(player, 'Swamp Basement Shallows ES', Intr).dir(Ea, 0x76, Bot, High).pos(1),
create_door(player, 'Swamp Waterfall Room SW', Nrml).dir(So, 0x66, Left, Low).toggler().pos(1),
create_door(player, 'Swamp Waterfall Room SW', Nrml).dir(So, 0x66, Left, Low).toggler().pos(1).portal(Z, 0x20, 1),
create_door(player, 'Swamp Waterfall Room NW', Intr).dir(No, 0x66, Left, Low).pos(3),
create_door(player, 'Swamp Waterfall Room NE', Intr).dir(No, 0x66, Right, Low).pos(0),
create_door(player, 'Swamp Refill SW', Intr).dir(So, 0x66, Left, Low).pos(3),
@@ -495,10 +510,11 @@ def create_doors(world, player):
create_door(player, 'Swamp T NW', Nrml).dir(No, 0x16, Left, High).pos(3),
create_door(player, 'Swamp Boss SW', Nrml).dir(So, 0x06, Left, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Skull 1 Lobby S', Nrml).dir(So, 0x58, Left, High).pos(4).portal(Z, 0x00),
create_door(player, 'Skull 1 Lobby WS', Nrml).dir(We, 0x58, Bot, High).small_key().pos(1),
create_door(player, 'Skull 1 Lobby ES', Intr).dir(Ea, 0x58, Bot, High).pos(5),
create_door(player, 'Skull Map Room WS', Intr).dir(We, 0x58, Bot, High).pos(5),
create_door(player, 'Skull Map Room SE', Nrml).dir(So, 0x58, Right, High).small_key().pos(2),
create_door(player, 'Skull Map Room SE', Nrml).dir(So, 0x58, Right, High).small_key().pos(2).portal(X, 0x02),
create_door(player, 'Skull Pot Circle WN', Intr).dir(We, 0x58, Top, High).pos(3),
create_door(player, 'Skull Pull Switch EN', Intr).dir(Ea, 0x58, Top, High).pos(3),
create_door(player, 'Skull Pot Circle Star Path', Lgcl),
@@ -512,7 +528,8 @@ def create_doors(world, player):
create_door(player, 'Skull Left Drop ES', Intr).dir(Ea, 0x67, Bot, High).pos(1),
create_door(player, 'Skull Compass Room WS', Intr).dir(We, 0x67, Bot, High).pos(1),
create_door(player, 'Skull Pot Prison ES', Nrml).dir(Ea, 0x57, Bot, High).small_key().pos(2),
create_door(player, 'Skull Pot Prison SE', Nrml).dir(So, 0x57, Right, High).pos(5),
create_door(player, 'Skull Pot Prison SE', Nrml).dir(So, 0x57, Right, High).pos(5).portal(X, 0x00),
create_door(player, 'Skull 2 East Lobby SW', Nrml).dir(So, 0x57, Left, High).pos(3).portal(Z, 0x00),
create_door(player, 'Skull 2 East Lobby WS', Nrml).dir(We, 0x57, Bot, High).pos(4),
create_door(player, 'Skull 2 East Lobby NW', Intr).dir(No, 0x57, Left, High).pos(1),
create_door(player, 'Skull Big Key SW', Intr).dir(So, 0x57, Left, High).pos(1),
@@ -520,26 +537,29 @@ def create_doors(world, player):
create_door(player, 'Skull Lone Pot EN', Intr).dir(Ea, 0x57, Top, High).pos(0),
create_door(player, 'Skull Small Hall ES', Nrml).dir(Ea, 0x56, Bot, High).pos(3),
create_door(player, 'Skull Small Hall WS', Intr).dir(We, 0x56, Bot, High).pos(2),
create_door(player, 'Skull 2 West Lobby S', Nrml).dir(So, 0x56, Left, High).pos(1).portal(Z, 0x00),
create_door(player, 'Skull 2 West Lobby ES', Intr).dir(Ea, 0x56, Bot, High).pos(2),
create_door(player, 'Skull 2 West Lobby NW', Intr).dir(No, 0x56, Left, High).small_key().pos(0),
create_door(player, 'Skull X Room SW', Intr).dir(So, 0x56, Left, High).small_key().pos(0),
create_door(player, 'Skull Back Drop Star Path', Lgcl),
create_door(player, 'Skull 3 Lobby SW', Nrml).dir(So, 0x59, Left, High).pos(1).portal(Z, 0x02),
create_door(player, 'Skull 3 Lobby NW', Nrml).dir(No, 0x59, Left, High).small_key().pos(0),
create_door(player, 'Skull 3 Lobby EN', Intr).dir(Ea, 0x59, Top, High).pos(2),
create_door(player, 'Skull East Bridge WN', Intr).dir(We, 0x59, Top, High).pos(2),
create_door(player, 'Skull East Bridge WS', Intr).dir(We, 0x59, Bot, High).pos(3),
create_door(player, 'Skull West Bridge Nook ES', Intr).dir(Ea, 0x59, Bot, High).pos(3),
create_door(player, 'Skull Star Pits SW', Nrml).dir(So, 0x49, Left, High).small_key().pos(2),
create_door(player, 'Skull Star Pits SW', Nrml).dir(So, 0x49, Left, High).small_key().pos(2).portal(Z, 0x00),
create_door(player, 'Skull Star Pits ES', Intr).dir(Ea, 0x49, Bot, High).pos(3),
create_door(player, 'Skull Torch Room WS', Intr).dir(We, 0x49, Bot, High).pos(3),
create_door(player, 'Skull Torch Room WN', Intr).dir(We, 0x49, Top, High).pos(1),
create_door(player, 'Skull Vines EN', Intr).dir(Ea, 0x49, Top, High).pos(1),
create_door(player, 'Skull Vines NW', Nrml).dir(No, 0x49, Left, High).pos(0),
create_door(player, 'Skull Spike Corner SW', Nrml).dir(So, 0x39, Left, High).no_exit().trap(0x4).pos(0),
create_door(player, 'Skull Spike Corner SW', Nrml).dir(So, 0x39, Left, High).no_exit().trap(0x4).pos(0).portal(Z, 0x00),
create_door(player, 'Skull Spike Corner ES', Intr).dir(Ea, 0x39, Bot, High).small_key().pos(1),
create_door(player, 'Skull Final Drop WS', Intr).dir(We, 0x39, Bot, High).small_key().pos(1),
create_door(player, 'Skull Final Drop Hole', Hole),
create_door(player, 'Thieves Lobby S', Nrml).dir(So, 0xdb, Mid, High).pos(1).portal(Z, 0x22),
create_door(player, 'Thieves Lobby N Edge', Open).dir(No, 0xdb, None, Low).edge(7, A, 0x10),
create_door(player, 'Thieves Lobby NE Edge', Open).dir(No, 0xdb, None, Low).edge(8, S, 0x18),
create_door(player, 'Thieves Lobby E', Nrml).dir(Ea, 0xdb, Mid, High).no_exit().trap(0x4).pos(0),
@@ -561,10 +581,10 @@ def create_doors(world, player):
create_door(player, 'Thieves Compass Room N Edge', Open).dir(No, 0xdc, None, Low).edge(10, A, 0x10),
create_door(player, 'Thieves Compass Room WS Edge', Open).dir(We, 0xdc, None, Low).edge(8, Z, 0x50),
create_door(player, 'Thieves Compass Room W', Nrml).dir(We, 0xdc, Mid, High).pos(0),
create_door(player, 'Thieves Hallway SE', Nrml).dir(So, 0xbc, Right, High).small_key().pos(1),
create_door(player, 'Thieves Hallway SE', Nrml).dir(So, 0xbc, Right, High).small_key().pos(1).portal(X, 0x02),
create_door(player, 'Thieves Hallway NE', Nrml).dir(No, 0xbc, Right, High).pos(7),
create_door(player, 'Thieves Pot Alcove Mid WS', Nrml).dir(We, 0xbc, Bot, High).pos(5),
create_door(player, 'Thieves Pot Alcove Bottom SW', Nrml).dir(So, 0xbc, Left, High).pos(3),
create_door(player, 'Thieves Pot Alcove Bottom SW', Nrml).dir(So, 0xbc, Left, High).pos(3).portal(Z, 0x00),
create_door(player, 'Thieves Conveyor Maze WN', Nrml).dir(We, 0xbc, Top, High).pos(4),
create_door(player, 'Thieves Hallway WS', Intr).dir(We, 0xbc, Bot, High).small_key().pos(0),
create_door(player, 'Thieves Pot Alcove Mid ES', Intr).dir(Ea, 0xbc, Bot, High).small_key().pos(0),
@@ -587,7 +607,7 @@ def create_doors(world, player):
create_door(player, 'Thieves Triple Bypass SE', Intr).dir(So, 0xbb, Right, High).pos(3),
create_door(player, 'Thieves Hellway Crystal EN', Intr).dir(Ea, 0xbb, Top, High).pos(1),
create_door(player, 'Thieves Triple Bypass WN', Intr).dir(We, 0xbb, Top, High).pos(1),
create_door(player, 'Thieves Spike Switch SW', Nrml).dir(So, 0xab, Left, High).pos(1),
create_door(player, 'Thieves Spike Switch SW', Nrml).dir(So, 0xab, Left, High).pos(1).portal(Z, 0x00),
create_door(player, 'Thieves Spike Switch Up Stairs', Sprl).dir(Up, 0xab, 0, HTH).ss(Z, 0x1a, 0x6c, True, True).small_key().pos(0),
create_door(player, 'Thieves Attic Down Stairs', Sprl).dir(Dn, 0x64, 0, HTH).ss(Z, 0x11, 0x80, True, True),
create_door(player, 'Thieves Attic ES', Intr).dir(Ea, 0x64, Bot, High).pos(0),
@@ -607,6 +627,8 @@ def create_doors(world, player):
create_door(player, 'Thieves Lonely Zazak NW', Intr).dir(No, 0x45, Left, High).pos(1),
create_door(player, 'Thieves Lonely Zazak ES', Intr).dir(Ea, 0x45, Right, High).pos(3),
create_door(player, 'Thieves Blind\'s Cell WS', Intr).dir(We, 0x45, Right, High).pos(3),
create_door(player, "Thieves Blind's Cell Door", Lgcl).big_key(),
create_door(player, "Thieves Blind's Cell Exit", Lgcl),
create_door(player, 'Thieves Conveyor Bridge EN', Nrml).dir(Ea, 0x44, Top, High).pos(2),
create_door(player, 'Thieves Conveyor Bridge ES', Nrml).dir(Ea, 0x44, Bot, High).pos(3),
create_door(player, 'Thieves Conveyor Bridge Block Path', Lgcl),
@@ -616,6 +638,7 @@ def create_doors(world, player):
create_door(player, 'Thieves Conveyor Block WN', Intr).dir(We, 0x44, Top, High).pos(0),
create_door(player, 'Thieves Trap EN', Intr).dir(Ea, 0x44, Left, Top).pos(0),
create_door(player, 'Ice Lobby SE', Nrml).dir(So, 0x0e, Right, High).pos(2).portal(X, 0x00),
create_door(player, 'Ice Lobby WS', Intr).dir(We, 0x0e, Bot, High).pos(1),
create_door(player, 'Ice Jelly Key ES', Intr).dir(Ea, 0x0e, Bot, High).pos(1),
create_door(player, 'Ice Jelly Key Down Stairs', Sprl).dir(Dn, 0x0e, 0, HTH).ss(Z, 0x11, 0x80, True, True).small_key().pos(0),
@@ -631,7 +654,7 @@ def create_doors(world, player):
create_door(player, 'Ice Cross Right Push Block Bottom', Lgcl), # dynamic
create_door(player, 'Ice Cross Top Push Block Bottom', Lgcl), # dynamic
create_door(player, 'Ice Cross Top Push Block Right', Lgcl), # dynamic
create_door(player, 'Ice Cross Bottom SE', Nrml).dir(So, 0x1e, Right, High).pos(3),
create_door(player, 'Ice Cross Bottom SE', Nrml).dir(So, 0x1e, Right, High).pos(3).portal(X, 0x00),
create_door(player, 'Ice Cross Right ES', Nrml).dir(Ea, 0x1e, Bot, High).trap(0x4).pos(0),
create_door(player, 'Ice Bomb Drop Hole', Hole),
create_door(player, 'Ice Compass Room NE', Nrml).dir(No, 0x2e, Right, High).pos(0),
@@ -642,7 +665,7 @@ def create_doors(world, player):
create_door(player, 'Ice Big Key Down Ladder', Lddr),
create_door(player, 'Ice Stalfos Hint SE', Intr).dir(So, 0x3e, Right, High).pos(0),
create_door(player, 'Ice Conveyor NE', Intr).dir(No, 0x3e, Right, High).no_exit().pos(0),
create_door(player, 'Ice Conveyor SW', Nrml).dir(So, 0x3e, Left, High).small_key().pos(1),
create_door(player, 'Ice Conveyor SW', Nrml).dir(So, 0x3e, Left, High).small_key().pos(1).portal(Z, 0x20),
create_door(player, 'Ice Bomb Jump NW', Nrml).dir(No, 0x4e, Left, High).small_key().pos(1),
create_door(player, 'Ice Bomb Jump Ledge Orange Barrier', Lgcl),
create_door(player, 'Ice Bomb Jump Catwalk Orange Barrier', Lgcl),
@@ -651,7 +674,7 @@ def create_doors(world, player):
create_door(player, 'Ice Narrow Corridor Down Stairs', Sprl).dir(Dn, 0x4e, 0, HTH).ss(S, 0x52, 0xc0, True, True),
create_door(player, 'Ice Pengator Trap Up Stairs', Sprl).dir(Up, 0x6e, 0, HTH).ss(S, 0x5a, 0xac, True, True),
create_door(player, 'Ice Pengator Trap NE', Nrml).dir(No, 0x6e, Right, High).trap(0x4).pos(0),
create_door(player, 'Ice Spike Cross SE', Nrml).dir(So, 0x5e, Right, High).pos(2),
create_door(player, 'Ice Spike Cross SE', Nrml).dir(So, 0x5e, Right, High).pos(2).portal(X, 0x00),
create_door(player, 'Ice Spike Cross ES', Nrml).dir(Ea, 0x5e, Bot, High).small_key().pos(0),
create_door(player, 'Ice Spike Cross WS', Intr).dir(We, 0x5e, Bot, High).pos(3),
create_door(player, 'Ice Firebar ES', Intr).dir(Ea, 0x5e, Bot, High).pos(3),
@@ -673,7 +696,7 @@ def create_doors(world, player):
create_door(player, 'Ice Freezors Ledge ES', Intr).dir(Ea, 0x7e, Bot, High).pos(2),
create_door(player, 'Ice Tall Hint WS', Intr).dir(We, 0x7e, Bot, High).pos(1),
create_door(player, 'Ice Tall Hint EN', Nrml).dir(Ea, 0x7e, Top, High).pos(2),
create_door(player, 'Ice Tall Hint SE', Nrml).dir(So, 0x7e, Right, High).small_key().pos(0),
create_door(player, 'Ice Tall Hint SE', Nrml).dir(So, 0x7e, Right, High).small_key().pos(0).portal(X, 0x02),
create_door(player, 'Ice Hookshot Ledge WN', Nrml).dir(We, 0x7f, Top, High).no_exit().trap(0x4).pos(0).kill(),
create_door(player, 'Ice Hookshot Ledge Path', Lgcl),
create_door(player, 'Ice Hookshot Balcony Path', Lgcl),
@@ -686,7 +709,7 @@ def create_doors(world, player):
create_door(player, 'Iced T Up Stairs', Sprl).dir(Up, 0xae, 0, HTH).ss(S, 0x1a, 0x3c, True, True),
create_door(player, 'Ice Catwalk WN', Nrml).dir(We, 0xaf, Top, High).pos(1),
create_door(player, 'Ice Catwalk NW', Nrml).dir(No, 0xaf, Left, High).pos(0),
create_door(player, 'Ice Many Pots SW', Nrml).dir(So, 0x9f, Left, High).trap(0x2).pos(1),
create_door(player, 'Ice Many Pots SW', Nrml).dir(So, 0x9f, Left, High).trap(0x2).pos(1).portal(Z, 0x00),
create_door(player, 'Ice Many Pots WS', Nrml).dir(We, 0x9f, Bot, High).trap(0x4).pos(0),
create_door(player, 'Ice Crystal Right ES', Nrml).dir(Ea, 0x9e, Bot, High).pos(3),
create_door(player, 'Ice Crystal Right Orange Barrier', Lgcl),
@@ -706,18 +729,19 @@ def create_doors(world, player):
create_door(player, 'Ice Anti-Fairy SE', Intr).dir(So, 0xbe, Right, High).pos(2),
create_door(player, 'Ice Switch Room NE', Intr).dir(No, 0xbe, Right, High).pos(2),
create_door(player, 'Ice Switch Room ES', Nrml).dir(Ea, 0xbe, Bot, High).small_key().pos(1),
create_door(player, 'Ice Switch Room SE', Nrml).dir(So, 0xbe, Right, High).trap(0x4).pos(0),
create_door(player, 'Ice Switch Room SE', Nrml).dir(So, 0xbe, Right, High).trap(0x4).pos(0).portal(X, 0x00),
create_door(player, 'Ice Refill WS', Nrml).dir(We, 0xbf, Bot, High).small_key().pos(0),
create_door(player, 'Ice Fairy Warp', Warp),
create_door(player, 'Ice Antechamber NE', Nrml).dir(No, 0xce, Right, High).trap(0x4).pos(0),
create_door(player, 'Ice Antechamber Hole', Hole),
create_door(player, 'Mire Lobby S', Nrml).dir(So, 0x98, Left, High).pos(0).portal(Z, 0x20),
create_door(player, 'Mire Lobby Gap', Lgcl),
create_door(player, 'Mire Post-Gap Gap', Lgcl),
create_door(player, 'Mire Post-Gap Down Stairs', Sprl).dir(Dn, 0x98, 0, HTH).ss(X, 0x11, 0x90, False, True),
create_door(player, 'Mire 2 Up Stairs', Sprl).dir(Up, 0xd2, 0, HTH).ss(X, 0x1a, 0x7c, False, True),
create_door(player, 'Mire 2 NE', Nrml).dir(No, 0xd2, Right, High).trap(0x4).pos(0),
create_door(player, 'Mire Hub SE', Nrml).dir(So, 0xc2, Right, High).pos(5),
create_door(player, 'Mire Hub SE', Nrml).dir(So, 0xc2, Right, High).pos(5).portal(X, 0x22),
create_door(player, 'Mire Hub ES', Nrml).dir(Ea, 0xc2, Bot, High).pos(6),
create_door(player, 'Mire Hub E', Nrml).dir(Ea, 0xc2, Mid, High).pos(4),
create_door(player, 'Mire Hub NE', Nrml).dir(No, 0xc2, Right, High).pos(7),
@@ -746,7 +770,7 @@ def create_doors(world, player):
create_door(player, 'Mire Map Spot Blue Barrier', Lgcl),
create_door(player, 'Mire Crystal Dead End Left Barrier', Lgcl),
create_door(player, 'Mire Crystal Dead End Right Barrier', Lgcl),
create_door(player, 'Mire Hidden Shooters SE', Nrml).dir(So, 0xb2, Right, High).pos(6),
create_door(player, 'Mire Hidden Shooters SE', Nrml).dir(So, 0xb2, Right, High).pos(6).portal(X, 0x00),
create_door(player, 'Mire Hidden Shooters ES', Nrml).dir(Ea, 0xb2, Bot, High).pos(7),
create_door(player, 'Mire Hidden Shooters WS', Intr).dir(We, 0xb2, Bot, High).pos(1),
create_door(player, 'Mire Cross ES', Intr).dir(Ea, 0xb2, Bot, High).pos(1),
@@ -754,22 +778,22 @@ def create_doors(world, player):
create_door(player, 'Mire Hidden Shooters Block Path N', Lgcl),
create_door(player, 'Mire Hidden Shooters NE', Intr).dir(No, 0xb2, Right, High).pos(2),
create_door(player, 'Mire Minibridge SE', Intr).dir(So, 0xb2, Right, High).pos(2),
create_door(player, 'Mire Cross SW', Nrml).dir(So, 0xb2, Left, High).pos(5),
create_door(player, 'Mire Cross SW', Nrml).dir(So, 0xb2, Left, High).pos(5).portal(Z, 0x00),
create_door(player, 'Mire Minibridge NE', Nrml).dir(No, 0xb2, Right, High).pos(4),
create_door(player, 'Mire BK Door Room EN', Nrml).dir(Ea, 0xb2, Top, Low).pos(3),
create_door(player, 'Mire BK Door Room N', Nrml).dir(No, 0xb2, Mid, High).big_key().pos(0),
create_door(player, 'Mire Spikes WS', Nrml).dir(We, 0xb3, Bot, High).pos(3),
create_door(player, 'Mire Spikes SW', Nrml).dir(So, 0xb3, Left, High).pos(4),
create_door(player, 'Mire Spikes SW', Nrml).dir(So, 0xb3, Left, High).pos(4).portal(Z, 0x00),
create_door(player, 'Mire Spikes NW', Intr).dir(No, 0xb3, Left, High).small_key().pos(0),
create_door(player, 'Mire Ledgehop SW', Intr).dir(So, 0xb3, Left, High).small_key().pos(0),
create_door(player, 'Mire Ledgehop WN', Nrml).dir(We, 0xb3, Top, Low).pos(1),
create_door(player, 'Mire Ledgehop NW', Nrml).dir(No, 0xb3, Left, High).pos(2),
create_door(player, 'Mire Bent Bridge SW', Nrml).dir(So, 0xa3, Left, High).pos(1),
create_door(player, 'Mire Bent Bridge SW', Nrml).dir(So, 0xa3, Left, High).pos(1).portal(Z, 0x02),
create_door(player, 'Mire Bent Bridge W', Nrml).dir(We, 0xa3, Mid, High).pos(0),
create_door(player, 'Mire Over Bridge E', Nrml).dir(Ea, 0xa2, Mid, High).pos(2),
create_door(player, 'Mire Over Bridge W', Nrml).dir(We, 0xa2, Mid, High).pos(1),
create_door(player, 'Mire Right Bridge SE', Nrml).dir(So, 0xa2, Right, High).pos(3),
create_door(player, 'Mire Left Bridge S', Nrml).dir(So, 0xa2, Mid, High).small_key().pos(0),
create_door(player, 'Mire Right Bridge SE', Nrml).dir(So, 0xa2, Right, High).pos(3).portal(X, 0x22),
create_door(player, 'Mire Left Bridge S', Nrml).dir(So, 0xa2, Mid, High).small_key().pos(0).portal(Z, 0x22),
create_door(player, 'Mire Left Bridge Hook Path', Lgcl),
create_door(player, 'Mire Left Bridge Down Stairs', Sprl).dir(Dn, 0xa2, 0, HTL).ss(A, 0x12, 0x00),
create_door(player, 'Mire Fishbone E', Nrml).dir(Ea, 0xa1, Mid, High).pos(1),
@@ -777,7 +801,7 @@ def create_doors(world, player):
create_door(player, 'Mire South Fish Blue Barrier', Lgcl),
create_door(player, 'Mire Fishbone SE', Nrml).dir(So, 0xa1, Right, High).small_key().pos(0),
create_door(player, 'Mire Spike Barrier NE', Nrml).dir(No, 0xb1, Right, High).small_key().pos(1),
create_door(player, 'Mire Spike Barrier SE', Nrml).dir(So, 0xb1, Right, High).pos(2),
create_door(player, 'Mire Spike Barrier SE', Nrml).dir(So, 0xb1, Right, High).pos(2).portal(X, 0x02),
create_door(player, 'Mire Spike Barrier ES', Intr).dir(Ea, 0xb1, Bot, High).pos(3),
create_door(player, 'Mire Square Rail WS', Intr).dir(We, 0xb1, Bot, High).pos(3),
create_door(player, 'Mire Square Rail NW', Intr).dir(No, 0xb1, Left, High).big_key().pos(0),
@@ -786,10 +810,10 @@ def create_doors(world, player):
create_door(player, 'Mire Wizzrobe Bypass EN', Nrml).dir(Ea, 0xc1, Top, High).pos(5),
create_door(player, 'Mire Wizzrobe Bypass NE', Nrml).dir(No, 0xc1, Right, High).pos(6),
create_door(player, 'Mire Conveyor Crystal ES', Nrml).dir(Ea, 0xc1, Bot, High).small_key().pos(1),
create_door(player, 'Mire Conveyor Crystal SE', Nrml).dir(So, 0xc1, Right, High).pos(7),
create_door(player, 'Mire Conveyor Crystal SE', Nrml).dir(So, 0xc1, Right, High).pos(7).portal(X, 0x00),
create_door(player, 'Mire Conveyor Crystal WS', Intr).dir(We, 0xc1, Bot, High).small_key().pos(0),
create_door(player, 'Mire Tile Room ES', Intr).dir(Ea, 0xc1, Bot, High).small_key().pos(0),
create_door(player, 'Mire Tile Room SW', Nrml).dir(So, 0xc1, Left, High).pos(4),
create_door(player, 'Mire Tile Room SW', Nrml).dir(So, 0xc1, Left, High).pos(4).portal(Z, 0x00),
create_door(player, 'Mire Tile Room NW', Intr).dir(No, 0xc1, Left, High).pos(3),
create_door(player, 'Mire Compass Room SW', Intr).dir(So, 0xc1, Left, High).pos(3),
create_door(player, 'Mire Compass Room EN', Intr).dir(Ea, 0xc1, Top, High).pos(2),
@@ -838,12 +862,13 @@ def create_doors(world, player):
create_door(player, 'Mire Antechamber NW', Nrml).dir(No, 0xa0, Left, High).big_key().pos(0),
create_door(player, 'Mire Boss SW', Nrml).dir(So, 0x90, Left, High).no_exit().trap(0x4).pos(0),
create_door(player, 'TR Main Lobby SE', Nrml).dir(So, 0xd6, Right, High).pos(1).portal(X, 0x02),
create_door(player, 'TR Lobby Ledge NE', Nrml).dir(No, 0xd6, Right, High).pos(2),
create_door(player, 'TR Main Lobby Gap', Lgcl),
create_door(player, 'TR Lobby Ledge Gap', Lgcl),
create_door(player, 'TR Compass Room NW', Nrml).dir(No, 0xd6, Left, High).pos(0),
create_door(player, 'TR Hub SW', Nrml).dir(So, 0xc6, Left, High).pos(4),
create_door(player, 'TR Hub SE', Nrml).dir(So, 0xc6, Right, High).pos(5),
create_door(player, 'TR Hub SW', Nrml).dir(So, 0xc6, Left, High).pos(4).portal(Z, 0x22),
create_door(player, 'TR Hub SE', Nrml).dir(So, 0xc6, Right, High).pos(5).portal(X, 0x22),
create_door(player, 'TR Hub ES', Nrml).dir(Ea, 0xc6, Bot, High).pos(3),
create_door(player, 'TR Hub EN', Nrml).dir(Ea, 0xc6, Top, High).pos(2),
create_door(player, 'TR Hub NW', Nrml).dir(No, 0xc6, Left, High).small_key().pos(0),
@@ -851,9 +876,9 @@ def create_doors(world, player):
create_door(player, 'TR Torches Ledge WS', Nrml).dir(We, 0xc7, Bot, High).pos(2),
create_door(player, 'TR Torches WN', Nrml).dir(We, 0xc7, Top, High).pos(1),
create_door(player, 'TR Torches NW', Nrml).dir(No, 0xc7, Left, High).trap(0x4).pos(0),
create_door(player, 'TR Roller Room SW', Nrml).dir(So, 0xb7, Left, High).pos(0),
create_door(player, 'TR Pokey 1 SW', Nrml).dir(So, 0xb6, Left, High).small_key().pos(2),
create_door(player, 'TR Tile Room SE', Nrml).dir(So, 0xb6, Right, High).pos(4),
create_door(player, 'TR Roller Room SW', Nrml).dir(So, 0xb7, Left, High).pos(0).portal(Z, 0x02),
create_door(player, 'TR Pokey 1 SW', Nrml).dir(So, 0xb6, Left, High).small_key().pos(2).portal(Z, 0x00),
create_door(player, 'TR Tile Room SE', Nrml).dir(So, 0xb6, Right, High).pos(4).portal(X, 0x00),
create_door(player, 'TR Tile Room NE', Intr).dir(No, 0xb6, Right, High).pos(1),
create_door(player, 'TR Refill SE', Intr).dir(So, 0xb6, Right, High).pos(1),
create_door(player, 'TR Pokey 1 NW', Intr).dir(No, 0xb6, Left, High).small_key().pos(3),
@@ -865,7 +890,7 @@ def create_doors(world, player):
create_door(player, 'TR Pipe Ledge Drop Down', Lgcl),
create_door(player, 'TR Lava Dual Pipes EN', Nrml).dir(Ea, 0x14, Top, High).pos(5),
create_door(player, 'TR Lava Dual Pipes WN', Nrml).dir(We, 0x14, Top, High).pos(3),
create_door(player, 'TR Lava Dual Pipes SW', Nrml).dir(So, 0x14, Left, High).pos(4),
create_door(player, 'TR Lava Dual Pipes SW', Nrml).dir(So, 0x14, Left, High).pos(4).portal(Z, 0x22),
create_door(player, 'TR Lava Island WS', Nrml).dir(We, 0x14, Bot, High).small_key().pos(1),
create_door(player, 'TR Lava Island ES', Nrml).dir(Ea, 0x14, Bot, High).pos(6),
create_door(player, 'TR Lava Escape SE', Nrml).dir(So, 0x14, Right, High).small_key().pos(0),
@@ -881,12 +906,14 @@ def create_doors(world, player):
create_door(player, 'TR Hallway ES', Intr).dir(Ea, 0x24, Bot, High).pos(7),
create_door(player, 'TR Big View WS', Intr).dir(We, 0x24, Bot, High).pos(7),
create_door(player, 'TR Big Chest Gap', Lgcl),
create_door(player, 'TR Big Chest Entrance SE', Nrml).dir(So, 0x24, Right, High).pos(4).kill().portal(X, 0x00),
create_door(player, 'TR Big Chest Entrance Gap', Lgcl),
create_door(player, 'TR Big Chest NE', Intr).dir(No, 0x24, Right, High).pos(3),
create_door(player, 'TR Dodgers SE', Intr).dir(So, 0x24, Right, High).no_exit().pos(3),
create_door(player, 'TR Dodgers NE', Nrml).dir(No, 0x24, Right, High).big_key().pos(0),
create_door(player, 'TR Lazy Eyes SE', Nrml).dir(So, 0x23, Right, High).pos(0).portal(X, 0x00),
create_door(player, 'TR Lazy Eyes ES', Nrml).dir(Ea, 0x23, Bot, High).pos(1),
create_door(player, 'TR Dash Room SW', Nrml).dir(So, 0x04, Left, High).pos(4),
create_door(player, 'TR Dash Room SW', Nrml).dir(So, 0x04, Left, High).pos(4).portal(Z, 0x00),
create_door(player, 'TR Dash Room ES', Intr).dir(Ea, 0x04, Bot, High).pos(2),
create_door(player, 'TR Tongue Pull WS', Intr).dir(We, 0x04, Bot, High).pos(2),
create_door(player, 'TR Tongue Pull NE', Intr).dir(No, 0x04, Right, High).pos(3),
@@ -895,11 +922,12 @@ def create_doors(world, player):
create_door(player, 'TR Crystaroller SW', Intr).dir(So, 0x04, Left, High).pos(1),
create_door(player, 'TR Crystaroller Down Stairs', Sprl).dir(Dn, 0x04, 0, HTH).ss(A, 0x12, 0x80, True, True).small_key().pos(0),
create_door(player, 'TR Dark Ride Up Stairs', Sprl).dir(Up, 0xb5, 0, HTH).ss(A, 0x1b, 0x6c),
create_door(player, 'TR Dark Ride SW', Nrml).dir(So, 0xb5, Left, High).trap(0x4).pos(0),
create_door(player, 'TR Dark Ride SW', Nrml).dir(So, 0xb5, Left, High).trap(0x4).pos(0).portal(Z, 0x22),
create_door(player, 'TR Dash Bridge NW', Nrml).dir(No, 0xc5, Left, High).pos(1),
create_door(player, 'TR Dash Bridge SW', Nrml).dir(So, 0xc5, Left, High).pos(2),
create_door(player, 'TR Dash Bridge SW', Nrml).dir(So, 0xc5, Left, High).pos(2).portal(Z, 0x02),
create_door(player, 'TR Dash Bridge WS', Nrml).dir(We, 0xc5, Bot, High).small_key().pos(0),
create_door(player, 'TR Eye Bridge NW', Nrml).dir(No, 0xd5, Left, High).pos(1),
create_door(player, 'TR Eye Bridge SW', Nrml).dir(So, 0xd5, Left, High).pos(0).portal(Z, 0x02),
create_door(player, 'TR Crystal Maze ES', Nrml).dir(Ea, 0xc4, Bot, High).small_key().pos(0),
create_door(player, 'TR Crystal Maze Forwards Path', Lgcl),
create_door(player, 'TR Crystal Maze Blue Path', Lgcl),
@@ -907,8 +935,9 @@ def create_doors(world, player):
create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High),
create_door(player, 'TR Final Abyss South Stairs', StrS).dir(So, 0xb4, Mid, High),
create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0),
create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0),
create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0), # .portal(Z, 0x00), -enemizer doesn't work
create_door(player, 'GT Lobby S', Nrml).dir(So, 0x0c, Mid, High).pos(0).portal(Z, 0x22),
create_door(player, 'GT Lobby Left Down Stairs', Sprl).dir(Dn, 0x0c, 1, HTL).ss(A, 0x0f, 0x80),
create_door(player, 'GT Lobby Up Stairs', Sprl).dir(Up, 0x0c, 2, HTH).ss(A, 0x1b, 0xec),
create_door(player, 'GT Lobby Right Down Stairs', Sprl).dir(Dn, 0x0c, 0, HTL).ss(S, 0x12, 0x80),
@@ -922,8 +951,8 @@ def create_doors(world, player):
create_door(player, 'GT Big Chest NW', Intr).dir(No, 0x8c, Left, High).pos(1),
create_door(player, 'GT Blocked Stairs Down Stairs', Sprl).dir(Dn, 0x8c, 3, HTH).ss(Z, 0x12, 0x40, True, True).kill(),
create_door(player, 'GT Blocked Stairs Block Path', Lgcl),
create_door(player, 'GT Big Chest SW', Nrml).dir(So, 0x8c, Left, High).pos(4),
create_door(player, 'GT Bob\'s Room SE', Nrml).dir(So, 0x8c, Right, High).pos(5).kill(),
create_door(player, 'GT Big Chest SW', Nrml).dir(So, 0x8c, Left, High).pos(4).portal(Z, 0x00),
create_door(player, 'GT Bob\'s Room SE', Nrml).dir(So, 0x8c, Right, High).pos(5).kill().portal(X, 0x00),
create_door(player, 'GT Bob\'s Room Hole', Hole),
create_door(player, 'GT Tile Room WN', Nrml).dir(We, 0x8d, Top, High).pos(2),
create_door(player, 'GT Tile Room EN', Intr).dir(Ea, 0x8d, Top, High).small_key().pos(1),
@@ -933,7 +962,7 @@ def create_doors(world, player):
create_door(player, 'GT Speed Torch North Path', Lgcl),
create_door(player, 'GT Speed Torch WS', Intr).dir(We, 0x8d, Bot, High).pos(4),
create_door(player, 'GT Pots n Blocks ES', Intr).dir(Ea, 0x8d, Bot, High).pos(4),
create_door(player, 'GT Speed Torch SE', Nrml).dir(So, 0x8d, Right, High).trap(0x4).pos(0),
create_door(player, 'GT Speed Torch SE', Nrml).dir(So, 0x8d, Right, High).trap(0x4).pos(0).portal(X, 0x02),
create_door(player, 'GT Crystal Conveyor NE', Nrml).dir(No, 0x9d, Right, High).pos(0).kill(),
create_door(player, 'GT Crystal Conveyor WN', Intr).dir(We, 0x9d, Top, High).pos(2),
create_door(player, 'GT Compass Room EN', Intr).dir(Ea, 0x9d, Top, High).pos(2),
@@ -954,10 +983,11 @@ def create_doors(world, player):
create_door(player, 'GT Hookshot South-North Path', Lgcl),
create_door(player, 'GT Hookshot Platform Blue Barrier', Lgcl),
create_door(player, 'GT Hookshot Entry Blue Barrier', Lgcl),
create_door(player, 'GT Hookshot Entry Boomerang Path', Lgcl),
create_door(player, 'GT Hookshot NW', Nrml).dir(No, 0x8b, Left, High).pos(4),
create_door(player, 'GT Hookshot ES', Intr).dir(Ea, 0x8b, Bot, High).small_key().pos(1),
create_door(player, 'GT Map Room WS', Intr).dir(We, 0x8b, Bot, High).small_key().pos(1),
create_door(player, 'GT Hookshot SW', Nrml).dir(So, 0x8b, Left, High).pos(3),
create_door(player, 'GT Hookshot SW', Nrml).dir(So, 0x8b, Left, High).pos(3).portal(Z, 0x02),
create_door(player, 'GT Double Switch NW', Nrml).dir(No, 0x9b, Left, High).pos(1).kill(),
create_door(player, 'GT Double Switch Orange Barrier', Lgcl),
create_door(player, 'GT Double Switch Orange Barrier 2', Lgcl),
@@ -991,11 +1021,11 @@ def create_doors(world, player):
create_door(player, 'GT Warp Maze - Main Rails Right Mid Warp', Warp),
create_door(player, 'GT Warp Maze - Pot Rail Warp', Warp),
create_door(player, 'GT Warp Maze (Rails) WS', Nrml).dir(We, 0x7d, Bot, High).pos(1),
create_door(player, 'GT Petting Zoo SE', Nrml).dir(So, 0x7d, Right, High).trap(0x4).pos(0),
create_door(player, 'GT Petting Zoo SE', Nrml).dir(So, 0x7d, Right, High).trap(0x4).pos(0).portal(X, 0x00),
create_door(player, 'GT Conveyor Star Pits EN', Nrml).dir(Ea, 0x7b, Top, High).small_key().pos(1),
create_door(player, 'GT Hidden Star ES', Nrml).dir(Ea, 0x7b, Bot, High).pos(2).kill(),
create_door(player, 'GT Hidden Star Warp', Warp),
create_door(player, 'GT DMs Room SW', Nrml).dir(So, 0x7b, Left, High).trap(0x4).pos(0),
create_door(player, 'GT DMs Room SW', Nrml).dir(So, 0x7b, Left, High).trap(0x4).pos(0).portal(Z, 0x00),
create_door(player, 'GT Falling Bridge WN', Nrml).dir(We, 0x7c, Top, High).small_key().pos(2),
create_door(player, 'GT Falling Bridge WS', Nrml).dir(We, 0x7c, Bot, High).pos(3),
create_door(player, 'GT Randomizer Room ES', Nrml).dir(Ea, 0x7c, Bot, High).pos(1),
@@ -1014,7 +1044,7 @@ def create_doors(world, player):
create_door(player, 'GT Mimics 2 NE', Intr).dir(No, 0x6b, Right, High).pos(1),
create_door(player, 'GT Dash Hall SE', Intr).dir(So, 0x6b, Right, High).pos(1),
create_door(player, 'GT Dash Hall NE', Nrml).dir(No, 0x6b, Right, High).big_key().pos(0),
create_door(player, 'GT Hidden Spikes SE', Nrml).dir(So, 0x5b, Right, High).small_key().pos(0),
create_door(player, 'GT Hidden Spikes SE', Nrml).dir(So, 0x5b, Right, High).small_key().pos(0).portal(X, 0x02),
create_door(player, 'GT Hidden Spikes EN', Nrml).dir(Ea, 0x5b, Top, High).trap(0x2).pos(1),
create_door(player, 'GT Cannonball Bridge WN', Nrml).dir(We, 0x5c, Top, High).pos(1),
create_door(player, 'GT Cannonball Bridge SE', Intr).dir(So, 0x5c, Right, High).pos(0),
@@ -1025,7 +1055,7 @@ def create_doors(world, player):
create_door(player, 'GT Gauntlet 2 EN', Intr).dir(Ea, 0x5d, Top, High).pos(2),
create_door(player, 'GT Gauntlet 2 SW', Intr).dir(So, 0x5d, Left, High).pos(0),
create_door(player, 'GT Gauntlet 3 NW', Intr).dir(No, 0x5d, Left, High).pos(0),
create_door(player, 'GT Gauntlet 3 SW', Nrml).dir(So, 0x5d, Left, High).trap(0x2).pos(1),
create_door(player, 'GT Gauntlet 3 SW', Nrml).dir(So, 0x5d, Left, High).trap(0x2).pos(1).portal(Z, 0x00),
create_door(player, 'GT Gauntlet 4 NW', Nrml).dir(No, 0x6d, Left, High).trap(0x4).pos(0),
create_door(player, 'GT Gauntlet 4 SW', Intr).dir(So, 0x6d, Left, High).pos(1),
create_door(player, 'GT Gauntlet 5 NW', Intr).dir(No, 0x6d, Left, High).pos(1),
@@ -1042,7 +1072,7 @@ def create_doors(world, player):
create_door(player, 'GT Dashing Bridge NE', Intr).dir(No, 0xa5, Right, High).pos(1),
create_door(player, 'GT Wizzrobes 2 SE', Intr).dir(So, 0xa5, Right, High).pos(1),
create_door(player, 'GT Wizzrobes 2 NE', Nrml).dir(No, 0xa5, Right, High).trap(0x4).pos(0),
create_door(player, 'GT Conveyor Bridge SE', Nrml).dir(So, 0x95, Right, High).pos(0),
create_door(player, 'GT Conveyor Bridge SE', Nrml).dir(So, 0x95, Right, High).pos(0).portal(X, 0x02),
create_door(player, 'GT Conveyor Bridge EN', Nrml).dir(Ea, 0x95, Top, High).pos(1),
create_door(player, 'GT Torch Cross WN', Nrml).dir(We, 0x96, Top, High).pos(1),
create_door(player, 'GT Torch Cross ES', Intr).dir(Ea, 0x96, Bot, High).pos(0),
@@ -1056,7 +1086,7 @@ def create_doors(world, player):
create_door(player, 'GT Bomb Conveyor EN', Intr).dir(Ea, 0x3d, Top, High).small_key().pos(1),
create_door(player, 'GT Bomb Conveyor SW', Intr).dir(So, 0x3d, Left, High).pos(3),
create_door(player, 'GT Crystal Circles NW', Intr).dir(No, 0x3d, Left, High).pos(3),
create_door(player, 'GT Crystal Circles SW', Nrml).dir(So, 0x3d, Left, High).small_key().pos(2),
create_door(player, 'GT Crystal Circles SW', Nrml).dir(So, 0x3d, Left, High).small_key().pos(2).portal(Z, 0x00),
create_door(player, 'GT Left Moldorm Ledge NW', Nrml).dir(No, 0x4d, Left, High).small_key().pos(0).kill(),
create_door(player, 'GT Left Moldorm Ledge Drop Down', Lgcl),
create_door(player, 'GT Right Moldorm Ledge Drop Down', Lgcl),
@@ -1209,6 +1239,7 @@ def create_doors(world, player):
world.get_door('GT Hookshot South-East Path', player).c_switch()
world.get_door('GT Hookshot ES', player).c_switch()
world.get_door('GT Hookshot Platform Blue Barrier', player).c_switch()
world.get_door('GT Hookshot Entry Boomerang Path', player).c_switch()
world.get_door('GT Double Switch Orange Path', player).c_switch()
world.get_door('GT Double Switch Blue Path', player).c_switch()
world.get_door('GT Spike Crystals WN', player).c_switch()
@@ -1239,6 +1270,70 @@ def create_doors(world, player):
assign_entrances(world, player)
dungeon_portals = [
create_portal(player, 'Sanctuary', world.get_door('Sanctuary S', player), 0x02, 0x02),
create_portal(player, 'Hyrule Castle West', world.get_door('Hyrule Castle West Lobby S', player), 0x03, 0x04),
create_portal(player, 'Hyrule Castle South', world.get_door('Hyrule Castle Lobby S', player), 0x04, 0x06),
create_portal(player, 'Hyrule Castle East', world.get_door('Hyrule Castle East Lobby S', player), 0x05, 0x08),
create_portal(player, 'Eastern', world.get_door('Eastern Lobby S', player), 0x08, 0x12, 0),
create_portal(player, 'Desert South', world.get_door('Desert Main Lobby S', player), 0x09, 0x14),
create_portal(player, 'Desert East', world.get_door('Desert East Lobby S', player), 0x0a, 0x16),
create_portal(player, 'Desert West', world.get_door('Desert West S', player), 0x0b, 0x18),
create_portal(player, 'Desert Back', world.get_door('Desert Back Lobby S', player), 0x0c, 0x1a, 1),
create_portal(player, 'Turtle Rock Lazy Eyes', world.get_door('TR Lazy Eyes SE', player), 0x15, 0x2c),
create_portal(player, 'Turtle Rock Eye Bridge', world.get_door('TR Eye Bridge SW', player), 0x18, 0x32),
create_portal(player, 'Turtle Rock Chest', world.get_door('TR Big Chest Entrance SE', player), 0x19, 0x34),
create_portal(player, 'Agahnims Tower', world.get_door('Tower Lobby S', player), 0x24, 0x4a),
create_portal(player, 'Swamp', world.get_door('Swamp Lobby S', player), 0x25, 0x4c, 4),
create_portal(player, 'Palace of Darkness', world.get_door('PoD Lobby S', player), 0x26, 0x4e, 5),
create_portal(player, 'Mire', world.get_door('Mire Lobby S', player), 0x27, 0x50, 7),
create_portal(player, 'Skull 2 West', world.get_door('Skull 2 West Lobby S', player), 0x28, 0x52),
create_portal(player, 'Skull 2 East', world.get_door('Skull 2 East Lobby SW', player), 0x29, 0x54),
create_portal(player, 'Skull 1', world.get_door('Skull 1 Lobby S', player), 0x2a, 0x56),
create_portal(player, 'Skull 3', world.get_door('Skull 3 Lobby SW', player), 0x2b, 0x58, 6),
create_portal(player, 'Ice', world.get_door('Ice Lobby SE', player), 0x2d, 0x5c, 8),
create_portal(player, 'Hera', world.get_door('Hera Lobby S', player), 0x33, 0x5a, 2),
create_portal(player, 'Thieves Town', world.get_door('Thieves Lobby S', player), 0x34, 0x6a, 10),
create_portal(player, 'Turtle Rock Main', world.get_door('TR Main Lobby SE', player), 0x35, 0x68, 9),
create_portal(player, 'Ganons Tower', world.get_door('GT Lobby S', player), 0x37, 0x70),
]
world.dungeon_portals[player] += dungeon_portals
world.get_door('Sanctuary S', player).dead_end(allowPassage=True)
world.get_door('TR Big Chest Entrance SE', player).passage = False
world.get_door('Sewers Secret Room Key Door S', player).dungeonLink = 'Hyrule Castle'
world.get_door('Desert Cannonball S', player).dead_end()
# world.get_door('Desert Boss SW', player).dead_end()
# world.get_door('Desert Boss SW', player).dungeonLink = 'Desert Palace'
world.get_door('Skull 1 Lobby S', player).dungeonLink = 'Skull Woods'
world.get_door('Skull Map Room SE', player).dungeonLink = 'Skull Woods'
world.get_door('Skull Spike Corner SW', player).dungeonLink = 'Skull Woods'
world.get_door('Skull Spike Corner SW', player).dead_end()
world.get_door('Thieves Pot Alcove Bottom SW', player).dead_end()
world.get_door('Ice Conveyor SW', player).dead_end(allowPassage=True)
world.get_door('Mire Right Bridge SE', player).dead_end(allowPassage=True)
world.get_door('TR Roller Room SW', player).dead_end()
world.get_door('TR Tile Room SE', player).dead_end()
# world.get_door('TR Boss SW', player).dead_end()
# world.get_door('TR Boss SW', player).dungeonLink = 'Turtle Rock'
world.get_door('GT Petting Zoo SE', player).dead_end()
world.get_door('GT DMs Room SW', player).dead_end()
world.get_door("GT Bob\'s Room SE", player).passage = False
world.get_door('PoD Mimics 2 SW', player).bk_shuffle_req = True
world.get_door('Desert Tiles 2 SE', player).bk_shuffle_req = True # key-drop note (todo)
# can't unlink from boss right now
world.get_door('Hera Lobby S', player).dungeonLink = 'Tower of Hera'
# can't unlink from skull woods right now
world.get_door('Skull 2 West Lobby S', player).dungeonLink = 'Skull Woods'
world.get_door('Ice Spike Cross SE', player).dungeonLink = 'linkIceFalls'
world.get_door('Ice Tall Hint SE', player).dungeonLink = 'linkIceFalls'
world.get_door('Ice Switch Room SE', player).dungeonLink = 'linkIceFalls'
world.get_door('Ice Cross Bottom SE', player).dungeonLink = 'linkIceFalls2'
world.get_door('Ice Conveyor SW', player).dungeonLink = 'linkIceFalls2'
def create_paired_doors(world, player):
world.paired_doors[player] = [
@@ -1304,3 +1399,8 @@ def create_door(player, name, door_type):
def ugly_door(door):
door.ugly = True
return door
def create_portal(player, name, door, ent, ext, boss_exit_idx=-1):
door.entranceFlag = True
return Portal(player, name, door, ent, ext, boss_exit_idx)

View File

@@ -9,9 +9,9 @@ import operator as op
import time
from typing import List
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector
from BaseClasses import Hook, hook_from_door
from Regions import key_only_locations, dungeon_events, flooded_keys_reverse
from Regions import dungeon_events, flooded_keys_reverse
from Dungeons import dungeon_regions, split_region_starts
from RoomData import DoorKind
@@ -30,6 +30,12 @@ class GraphPiece:
# Dungeons shouldn't be generated until all entrances are appropriately accessible
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
entrance_regions = convert_regions(entrance_region_names, world, player)
excluded = {}
for region in entrance_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal and portal.destination:
excluded[region] = None
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
proposed_map = {}
doors_to_connect = {}
all_regions = set()
@@ -41,11 +47,11 @@ def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
all_regions.update(sector.regions)
bk_needed = bk_needed or determine_if_bk_needed(sector, split_dungeon, world, player)
bk_special = bk_special or check_for_special(sector)
paths = determine_required_paths(world, player, split_dungeon, all_regions, builder.name)[builder.name]
paths = determine_paths_for_dungeon(world, player, all_regions, builder.name)
dungeon, hangers, hooks = gen_dungeon_info(builder.name, builder.sectors, entrance_regions, all_regions,
proposed_map, doors_to_connect, bk_needed, bk_special, world, player)
return check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions,
bk_needed, paths, entrance_regions)
return check_valid(builder.name, dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions,
bk_needed, bk_special, paths, entrance_regions, world, player)
def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player):
@@ -69,11 +75,14 @@ def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world,
proposed_map = builder.valid_proposal
else:
proposed_map = generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon, world, player)
builder.valid_proposal = proposed_map
queue = collections.deque(proposed_map.items())
while len(queue) > 0:
a, b = queue.popleft()
connect_doors(a, b)
queue.remove((b, a))
if len(builder.sectors) == 0:
return Sector()
available_sectors = list(builder.sectors)
master_sector = available_sectors.pop()
for sub_sector in available_sectors:
@@ -87,6 +96,12 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
logger = logging.getLogger('')
name = builder.name
entrance_regions = convert_regions(entrance_region_names, world, player)
excluded = {}
for region in entrance_regions:
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region == region), None)
if portal and portal.destination:
excluded[region] = None
entrance_regions = [x for x in entrance_regions if x not in excluded.keys()]
doors_to_connect = {}
all_regions = set()
bk_needed = False
@@ -106,7 +121,7 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
attempt = 1
finished = False
# flag if standard and this is hyrule castle
paths = determine_required_paths(world, player, split_dungeon, all_regions, name)[name]
paths = determine_paths_for_dungeon(world, player, all_regions, name)
while not finished:
# what are my choices?
itr += 1
@@ -125,8 +140,8 @@ def generate_dungeon_find_proposal(builder, entrance_region_names, split_dungeon
dungeon, hangers, hooks = gen_dungeon_info(name, builder.sectors, entrance_regions, all_regions, proposed_map,
doors_to_connect, bk_needed, bk_special, world, player)
dungeon_cache[depth] = dungeon, hangers, hooks
valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions,
bk_needed, paths, entrance_regions)
valid = check_valid(name, dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions,
bk_needed, bk_special, paths, entrance_regions, world, player)
else:
dungeon, hangers, hooks = dungeon_cache[depth]
valid = True
@@ -181,7 +196,11 @@ def determine_if_bk_needed(sector, split_dungeon, world, player):
def check_for_special(sector):
return 'Hyrule Dungeon Cellblock' in sector.region_set()
for region in sector.regions:
for loc in region.locations:
if loc.forced_big_key():
return True
return False
def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, proposed_map, valid_doors, bk_needed, bk_special, world, player):
@@ -190,11 +209,12 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
start = ExplorationState(dungeon=name)
start.big_key_special = bk_special
group_flags, door_map = find_bk_groups(name, available_sectors, proposed_map, bk_special)
bk_flag = False if world.bigkeyshuffle[player] and not bk_special else bk_needed
def exception(d):
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
original_state = extend_reachable_state_improved(entrance_regions, start, proposed_map, all_regions,
valid_doors, bk_needed, world, player, exception)
valid_doors, bk_flag, world, player, exception)
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map, exception)
either_crystal = True # if all hooks from the origin are either, explore all bits with either
for hook, crystal in dungeon['Origin'].hooks.items():
@@ -385,21 +405,22 @@ def filter_choices(next_hanger, door, orig_hang, prev_choices, hook_candidates):
return next_hanger != door and orig_hang != next_hanger and door not in hook_candidates
def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions,
bk_needed, paths, entrance_regions):
def check_valid(name, dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions,
bk_needed, bk_special, paths, entrance_regions, world, player):
# evaluate if everything is still plausible
# only origin is left in the dungeon and not everything is connected
if len(dungeon.keys()) <= 1 and len(proposed_map.keys()) < len(doors_to_connect):
return False
# origin has no more hooks, but not all doors have been proposed
possible_bks = len(dungeon['Origin'].possible_bk_locations)
true_origin_hooks = [x for x in dungeon['Origin'].hooks.keys() if not x.bigKey or possible_bks > 0 or not bk_needed]
if len(true_origin_hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect):
return False
if len(true_origin_hooks) == 0 and bk_needed and possible_bks == 0 and len(proposed_map.keys()) == len(
doors_to_connect):
return False
if not world.bigkeyshuffle[player]:
possible_bks = len(dungeon['Origin'].possible_bk_locations)
true_origin_hooks = [x for x in dungeon['Origin'].hooks.keys() if not x.bigKey or possible_bks > 0 or not bk_needed]
if len(true_origin_hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect):
return False
if len(true_origin_hooks) == 0 and bk_needed and possible_bks == 0 and len(proposed_map.keys()) == len(
doors_to_connect):
return False
for key in hangers.keys():
if len(hooks[key]) > 0 and len(hangers[key]) == 0:
return False
@@ -425,7 +446,7 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
if len(outstanding_doors[key]) > 0 and len(hangers[key]) == 0 and len(hooks[opp_key]) == 0:
return False
all_visited = set()
bk_possible = not bk_needed
bk_possible = not bk_needed or (world.bigkeyshuffle[player] and not bk_special)
for piece in dungeon.values():
all_visited.update(piece.visited_regions)
if not bk_possible and len(piece.possible_bk_locations) > 0:
@@ -434,7 +455,8 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
return False
if not bk_possible:
return False
if not valid_paths(paths, entrance_regions, doors_to_connect, all_regions, proposed_map):
if not valid_paths(name, paths, entrance_regions, doors_to_connect, all_regions, proposed_map,
bk_needed, bk_special, world, player):
return False
new_hangers_found = True
accessible_hook_types = []
@@ -463,7 +485,8 @@ def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_reg
return len(all_hangers.difference(hanger_matching)) == 0
def valid_paths(paths, entrance_regions, valid_doors, all_regions, proposed_map):
def valid_paths(name, paths, entrance_regions, valid_doors, all_regions, proposed_map,
bk_needed, bk_special, world, player):
for path in paths:
if type(path) is tuple:
target = path[1]
@@ -475,76 +498,84 @@ def valid_paths(paths, entrance_regions, valid_doors, all_regions, proposed_map)
else:
target = path
start_regions = entrance_regions
if not valid_path(start_regions, target, valid_doors, proposed_map):
if not valid_path(name, start_regions, target, valid_doors, proposed_map, all_regions,
bk_needed, bk_special, world, player):
return False
return True
def valid_path(starting_regions, target, valid_doors, proposed_map):
queue = deque(starting_regions)
visited = set(starting_regions)
while len(queue) > 0:
region = queue.popleft()
if region.name == target:
def valid_path(name, starting_regions, target, valid_doors, proposed_map, all_regions,
bk_needed, bk_special, world, player):
target_regions = set()
if type(target) is not list:
for region in all_regions:
if target == region.name:
target_regions.add(region)
break
else:
for region in all_regions:
if region.name in target:
target_regions.add(region)
start = ExplorationState(dungeon=name)
start.big_key_special = bk_special
bk_flag = False if world.bigkeyshuffle[player] and not bk_special else bk_needed
def exception(d):
return name == 'Skull Woods 2' and d.name == 'Skull Pinball WS'
original_state = extend_reachable_state_improved(starting_regions, start, proposed_map, all_regions,
valid_doors, bk_flag, world, player, exception)
for exp_door in original_state.unattached_doors:
if not exp_door.door.blocked:
return True # outstanding connection possible
for target in target_regions:
if original_state.visited_at_all(target):
return True
for ext in region.exits:
connect = ext.connected_region
if connect is None and ext.name in valid_doors:
door = valid_doors[ext.name]
if not door.blocked:
if door in proposed_map:
new_region = proposed_map[door].entrance.parent_region
if new_region not in visited:
visited.add(new_region)
queue.append(new_region)
else:
return True # outstanding connection possible
elif connect is not None:
door = ext.door
if door is not None and not door.blocked and connect not in visited:
visited.add(connect)
queue.append(connect)
return False # couldn't find an outstanding door or the target
def determine_required_paths(world, player, split_dungeon=False, all_regions=None, name=None):
if all_regions is None:
all_regions = set()
paths = {
'Hyrule Castle': ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby'],
'Eastern Palace': ['Eastern Boss'],
'Desert Palace': ['Desert Main Lobby', 'Desert East Lobby', 'Desert West Lobby', 'Desert Boss'],
'Tower of Hera': ['Hera Boss'],
'Agahnims Tower': ['Tower Agahnim 1'],
'Palace of Darkness': ['PoD Boss'],
'Swamp Palace': ['Swamp Boss'],
'Skull Woods': ['Skull 1 Lobby', 'Skull 2 East Lobby', 'Skull 2 West Lobby', 'Skull Boss'],
'Thieves Town': ['Thieves Boss', ('Thieves Blind\'s Cell', 'Thieves Boss')],
'Ice Palace': ['Ice Boss'],
'Misery Mire': ['Mire Boss'],
'Turtle Rock': ['TR Main Lobby', 'TR Lazy Eyes', 'TR Big Chest Entrance', 'TR Eye Bridge', 'TR Boss'],
'Ganons Tower': ['GT Agahnim 2'],
'Skull Woods 1': ['Skull 1 Lobby'],
'Skull Woods 2': ['Skull 2 East Lobby', 'Skull 2 West Lobby'],
'Skull Woods 3': [],
'Desert Palace Main': ['Desert Main Lobby', 'Desert East Lobby', 'Desert West Lobby'],
'Desert Palace Back': []
}
if world.mode[player] == 'standard':
paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock')
# noinspection PyTypeChecker
paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
if world.doorShuffle[player] in ['basic']:
paths['Thieves Town'].append('Thieves Attic Window')
if split_dungeon:
if world.get_region('Desert Boss', player) in all_regions:
paths[name].append('Desert Boss')
if world.get_region('Skull Boss', player) in all_regions:
paths[name].append('Skull Boss')
def determine_required_paths(world, player):
paths = {}
for name, builder in world.dungeon_layouts[player].items():
all_regions = builder.master_sector.regions
paths[name] = determine_paths_for_dungeon(world, player, all_regions, name)
return paths
boss_path_checks = ['Eastern Boss', 'Desert Boss', 'Hera Boss', 'Tower Agahnim 1', 'PoD Boss', 'Swamp Boss',
'Skull Boss', 'Ice Boss', 'Mire Boss', 'TR Boss', 'GT Agahnim 2']
# pinball is allowed to orphan you
drop_path_checks = ['Skull Pot Circle', 'Skull Left Drop', 'Skull Back Drop', 'Sewers Rat Path']
def determine_paths_for_dungeon(world, player, all_regions, name):
all_r_names = set(x.name for x in all_regions)
paths = []
non_hole_portals = []
for portal in world.dungeon_portals[player]:
if portal.door.entrance.parent_region in all_regions:
non_hole_portals.append(portal.door.entrance.parent_region.name)
if portal.destination:
paths.append(portal.door.entrance.parent_region.name)
if world.mode[player] == 'standard' and name == 'Hyrule Castle':
paths.append('Hyrule Dungeon Cellblock')
paths.append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
paths.append('Thieves Attic Window')
elif 'Thieves Attic Window' in all_r_names:
paths.append('Thieves Attic Window')
for boss in boss_path_checks:
if boss in all_r_names:
paths.append(boss)
if 'Thieves Boss' in all_r_names:
paths.append('Thieves Boss')
paths.append(('Thieves Blind\'s Cell', 'Thieves Boss'))
for drop_check in drop_path_checks:
if drop_check in all_r_names:
paths.append((drop_check, non_hole_portals))
return paths
def winnow_hangers(hangers, hooks):
@@ -636,7 +667,7 @@ def create_graph_piece_from_state(door, o_state, b_state, proposed_map, exceptio
def filter_for_potential_bk_locations(locations):
return [x for x in locations if
'- Big Chest' not in x.name and '- Prize' not in x.name and x.name not in dungeon_events
and x.name not in key_only_locations.keys() and x.name not in ['Agahnim 1', 'Agahnim 2']]
and not x.forced_item and x.name not in ['Agahnim 1', 'Agahnim 2']]
type_map = {
@@ -738,6 +769,9 @@ def connect_simple_door(exit_door, region):
exit_door.dest = region
special_big_key_doors = ['Hyrule Dungeon Cellblock Door', "Thieves Blind's Cell Door"]
class ExplorationState(object):
def __init__(self, init_crystal=CrystalBarrier.Orange, dungeon=None):
@@ -819,7 +853,7 @@ class ExplorationState(object):
if region.type == RegionType.Dungeon:
for location in region.locations:
if key_checks and location not in self.found_locations:
if location.name in key_only_locations and 'Small Key' in location.item.name:
if location.forced_item and 'Small Key' in location.item.name:
self.key_locations += 1
if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']:
self.ttl_locations += 1
@@ -833,10 +867,6 @@ class ExplorationState(object):
if location.name in flooded_keys_reverse.keys() and self.location_found(
flooded_keys_reverse[location.name]):
self.perform_event(flooded_keys_reverse[location.name], key_region)
if key_checks and region.name == 'Hyrule Dungeon Cellblock' and not self.big_key_opened:
self.big_key_opened = True
self.avail_doors.extend(self.big_doors)
self.big_doors.clear()
def flooded_key_check(self, location):
if location.name not in flooded_keys.keys():
@@ -934,7 +964,7 @@ class ExplorationState(object):
if door in key_door_proposal and door not in self.opened_doors:
if not self.in_door_list(door, self.small_doors):
self.append_door_to_list(door, self.small_doors)
elif door.bigKey and not self.big_key_opened:
elif (door.bigKey or door.name in special_big_key_doors) and not self.big_key_opened:
if not self.in_door_list(door, self.big_doors):
self.append_door_to_list(door, self.big_doors)
elif door.req_event is not None and door.req_event not in self.events:
@@ -955,6 +985,12 @@ class ExplorationState(object):
def visited_at_all(self, region):
return region in self.visited_blue or region in self.visited_orange
def found_forced_bk(self):
for location in self.found_locations:
if location.forced_big_key():
return True
return False
def can_traverse(self, door, exception=None):
if door.blocked:
return exception(door) if exception else False
@@ -962,18 +998,10 @@ class ExplorationState(object):
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return True
def can_traverse_bk_check(self, door, isOrigin):
if door.blocked:
return False
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
return not isOrigin or not door.bigKey or self.count_locations_exclude_specials() > 0
# return not door.bigKey or len([x for x in self.found_locations if '- Prize' not in x.name]) > 0
def count_locations_exclude_specials(self):
cnt = 0
for loc in self.found_locations:
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in key_only_locations.keys():
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item:
cnt += 1
return cnt
@@ -1063,14 +1091,18 @@ def special_big_key_found(state, world, player):
def valid_region_to_explore_in_regions(region, all_regions, world, player):
if region is None:
return False
return (region.type == RegionType.Dungeon and region in all_regions) or region.name in world.inaccessible_regions[player]
return (region.type == RegionType.Dungeon and region in all_regions)\
or region.name in world.inaccessible_regions[player]\
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard')
# cross-utility methods
def valid_region_to_explore(region, name, world, player):
if region is None:
return False
return (region.type == RegionType.Dungeon and region.dungeon.name in name) or region.name in world.inaccessible_regions[player]
return (region.type == RegionType.Dungeon and region.dungeon.name in name)\
or region.name in world.inaccessible_regions[player]\
or (region.name == 'Hyrule Castle Ledge' and world.mode[player] == 'standard')
def get_doors(world, region, player):
@@ -1146,9 +1178,7 @@ class DungeonBuilder(object):
self.key_door_proposal = None
self.allowance = None
if name in dungeon_dead_end_allowance.keys():
self.allowance = dungeon_dead_end_allowance[name]
elif 'Stonewall' in name:
if 'Stonewall' in name:
self.allowance = 1
elif 'Prewall' in name:
orig_name = name[:-8]
@@ -1232,11 +1262,27 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
sector = find_sector(r_name, all_sectors)
reverse_d_map[sector] = key
complete_dungeons = {x: y for x, y in dungeon_map.items() if sum(len(sector.outstanding_doors) for sector in y.sectors) <= 0}
[dungeon_map.pop(key) for key in complete_dungeons.keys()]
# categorize sectors
identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections,
dungeon_entrances, split_dungeon_entrances)
for name, builder in dungeon_map.items():
calc_allowance_and_dead_ends(builder, connections_tuple)
calc_allowance_and_dead_ends(builder, connections_tuple, world.dungeon_portals[player])
if world.mode[player] == 'open' and world.shuffle[player] not in ['crossed', 'insanity']:
sanc = find_sector('Sanctuary', candidate_sectors)
if sanc: # only run if sanc if a candidate
lw_builders = []
for name, portal_list in dungeon_portals.items():
for portal_name in portal_list:
if world.get_portal(portal_name, player).light_world:
lw_builders.append(dungeon_map[name])
break
# portals only - not drops for mirror stuff
sanc_builder = random.choice(lw_builders)
assign_sector(sanc, sanc_builder, candidate_sectors, global_pole)
free_location_sectors = {}
crystal_switches = {}
@@ -1274,6 +1320,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builder_info)
# the rest
assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info)
dungeon_map.update(complete_dungeons)
finished = True
except NeutralizingException:
pass
@@ -1327,11 +1374,12 @@ def identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map,
break
def calc_allowance_and_dead_ends(builder, connections_tuple):
def calc_allowance_and_dead_ends(builder, connections_tuple, portals):
entrances_map, potentials, connections = connections_tuple
needed_connections = [x for x in builder.all_entrances if x not in entrances_map[builder.name]]
starting_allowance = 0
used_sectors = set()
destination_entrances = [x.door.entrance.parent_region.name for x in portals if x.destination]
for entrance in entrances_map[builder.name]:
sector = find_sector(entrance, builder.sectors)
outflow_target = 0 if entrance not in drop_entrances_allowance else 1
@@ -1367,21 +1415,19 @@ def calc_allowance_and_dead_ends(builder, connections_tuple):
def define_sector_features(sectors):
for sector in sectors:
if 'Hyrule Dungeon Cellblock' in sector.region_set():
sector.bk_provided = True
if 'Thieves Blind\'s Cell' in sector.region_set():
sector.bk_required = True
for region in sector.regions:
for loc in region.locations:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2', 'Hyrule Castle - Big Key Drop']:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
pass
elif loc.event and 'Small Key' in loc.item.name:
elif loc.forced_item and 'Small Key' in loc.item.name:
sector.key_only_locations += 1
elif loc.name not in dungeon_events:
elif loc.forced_item and loc.forced_item.bigkey:
sector.bk_provided = True
elif loc.name not in dungeon_events and not loc.forced_item:
sector.chest_locations += 1
if '- Big Chest' in loc.name:
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
"Thieves' Town - Blind's Cell"]:
sector.bk_required = True
sector.big_chest_present = True
for ext in region.exits:
door = ext.door
if door is not None:
@@ -1775,16 +1821,6 @@ def loop_present(hook, opp, h_mag, other_mag):
return h_mag[opp] >= h_mag[hook.value] - other_mag[opp]
def is_entrance_sector(builder, sector):
if builder is None:
return False
for entrance in builder.all_entrances:
r_set = sector.region_set()
if entrance in r_set:
return True
return False
def is_satisfied(door_dict_list):
for door_dict in door_dict_list:
for door_list in door_dict.values():
@@ -2580,6 +2616,8 @@ def categorize_groupings(sectors):
def valid_assignment(builder, sector_list, builder_info):
if not valid_entrance(builder, sector_list, builder_info):
return False
if not valid_c_switch(builder, sector_list):
return False
if not valid_polarized_assignment(builder, sector_list):
@@ -2588,6 +2626,34 @@ def valid_assignment(builder, sector_list, builder_info):
return resolved
def valid_entrance(builder, sector_list, builder_info):
is_dead_end = False
if len(builder.sectors) == 0:
is_dead_end = True
else:
entrances, splits, world, player = builder_info
if builder.name not in entrances.keys():
name_parts = builder.name.rsplit(' ', 1)
entrance_list = splits[name_parts[0]][name_parts[1]]
entrances = []
for sector in builder.sectors:
if sector.is_entrance_sector():
sector.region_set()
entrances.append(sector)
all_dead = True
for sector in entrances:
for region in entrance_list:
if region in sector.region_set():
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == region))
if not portal.deadEnd:
all_dead = False
break
if not all_dead:
break
is_dead_end = all_dead
return len(sector_list) == 0 if is_dead_end else True
def valid_c_switch(builder, sector_list):
if builder.c_switch_present:
return True
@@ -2604,7 +2670,11 @@ def valid_c_switch(builder, sector_list):
def valid_connected_assignment(builder, sector_list):
full_list = sector_list + builder.sectors
if len(full_list) == 1 and sum_magnitude(full_list) == [0, 0, 0]:
return True
for sector in full_list:
if sector.is_entrance_sector():
continue
others = [x for x in full_list if x != sector]
other_mag = sum_magnitude(others)
sector_mag = sector.magnitude()
@@ -2678,17 +2748,38 @@ def split_dungeon_builder(builder, split_list, builder_info):
builder.split_dungeon_map[name].valid_proposal = proposal
return builder.split_dungeon_map # we made this earlier in gen, just use it
attempts, comb_w_replace = 0, None
attempts, comb_w_replace, merge_attempt = 0, None, False
while attempts < 5: # does not solve coin flips 3% of the time
try:
candidate_sectors = dict.fromkeys(builder.sectors)
global_pole = GlobalPolarity(candidate_sectors)
dungeon_map = {}
dungeon_map, sub_builder, merge_keys = {}, None, []
if merge_attempt:
candidates = []
for name, split_entrances in split_list.items():
if len(split_entrances) > 1:
candidates.append(name)
continue
elif len(split_entrances) <= 0:
continue
x, y, world, player = builder_info
r_name = split_entrances[0]
p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name)
if not p.deadEnd:
candidates.append(name)
merge_keys = random.sample(candidates, 2) if len(candidates) >= 2 else []
for name, split_entrances in split_list.items():
key = builder.name + ' ' + name
dungeon_map[key] = sub_builder = DungeonBuilder(key)
sub_builder.all_entrances = split_entrances
if merge_keys and name in merge_keys:
other_key = builder.name + ' ' + [x for x in merge_keys if x != name][0]
if other_key in dungeon_map:
key = other_key
sub_builder = dungeon_map[other_key]
sub_builder.all_entrances.extend(split_entrances)
if key not in dungeon_map:
dungeon_map[key] = sub_builder = DungeonBuilder(key)
sub_builder.all_entrances = list(split_entrances)
for r_name in split_entrances:
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
comb_w_replace = len(dungeon_map) ** len(candidate_sectors)
@@ -2698,6 +2789,9 @@ def split_dungeon_builder(builder, split_list, builder_info):
attempts += 5 # all the combinations were tried already, no use repeating
else:
attempts += 1
if attempts >= 5 and not merge_attempt:
merge_attempt, attempts = True, 0
raise GenerationException('Unable to resolve in 5 attempts')
@@ -2714,8 +2808,8 @@ def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info):
for i, choice in enumerate(choices):
chosen_sectors[choice].append(main_sector_list[i])
all_valid = True
for name, sector_list in chosen_sectors.items():
if not valid_assignment(dungeon_map[name], sector_list, builder_info):
for name, builder in dungeon_map.items():
if not valid_assignment(builder, chosen_sectors[name], builder_info):
all_valid = False
break
if all_valid:
@@ -2831,6 +2925,8 @@ def check_for_forced_crystal_single(builder, candidate_sectors):
for sector in builder.sectors:
for door in sector.outstanding_doors:
builder_doors[hook_from_door(door)][door] = sector
if len(builder_doors) == 0:
return False
candidate_doors = defaultdict(dict)
for sector in candidate_sectors:
for door in sector.outstanding_doors:
@@ -3124,15 +3220,27 @@ def check_for_valid_layout(builder, sector_list, builder_info):
temp_builder = DungeonBuilder(builder.name)
for s in sector_list + builder.sectors:
assign_sector_helper(s, temp_builder)
split_list = split_region_starts[builder.name]
split_list = split_dungeon_entrances[builder.name]
builder.split_dungeon_map = split_dungeon_builder(temp_builder, split_list, builder_info)
builder.valid_proposal = {}
possible_regions = set()
for portal in world.dungeon_portals[player]:
if not portal.destination and portal.name in dungeon_portals[builder.name]:
possible_regions.add(portal.door.entrance.parent_region.name)
if builder.name in dungeon_drops.keys():
possible_regions.update(dungeon_drops[builder.name])
for name, split_build in builder.split_dungeon_map.items():
name_bits = name.split(" ")
orig_name = " ".join(name_bits[:-1])
entrance_regions = split_dungeon_entrances[orig_name][name_bits[-1]]
# todo: this is hardcoded information for random entrances
entrance_regions = [x for x in entrance_regions if x not in split_check_entrance_invalid]
for sector in split_build.sectors:
match_set = set(sector.region_set()).intersection(possible_regions)
if len(match_set) > 0:
for r_name in match_set:
if r_name not in entrance_regions:
entrance_regions.append(r_name)
# entrance_regions = [x for x in entrance_regions if x not in split_check_entrance_invalid]
proposal = generate_dungeon_find_proposal(split_build, entrance_regions, True, world, player)
# record split proposals
builder.valid_proposal[name] = proposal
@@ -3150,7 +3258,7 @@ def check_for_valid_layout(builder, sector_list, builder_info):
def resolve_equations(builder, sector_list):
unreached_doors = defaultdict(list)
equations = copy_door_equations(builder, sector_list)
equations = {x: y for x, y in copy_door_equations(builder, sector_list).items() if len(y) > 0}
current_access = {}
sector_split = {} # those sectors that belong to a certain sector
if builder.name in split_region_starts.keys():
@@ -3252,7 +3360,7 @@ def find_priority_equation(equations, access_id, current_access):
if len(filtered_candidates) == 1:
return filtered_candidates[0]
neutral_candidates = [x for x in filtered_candidates if x[0].neutral_profit() or x[0].neutral()]
neutral_candidates = [x for x in filtered_candidates if (x[0].neutral_profit() or x[0].neutral()) and x[0].profit(current_access) == local_profit_map[x[2]]]
if len(neutral_candidates) == 0:
neutral_candidates = filtered_candidates
if len(neutral_candidates) == 1:
@@ -3531,7 +3639,7 @@ def copy_door_equations(builder, sector_list):
def calc_sector_equations(sector, builder):
equations = []
is_entrance = is_entrance_sector(builder, sector) and not sector.destination_entrance
is_entrance = sector.is_entrance_sector() and not sector.destination_entrance
if is_entrance:
flagged_equations = []
for door in sector.outstanding_doors:
@@ -3693,6 +3801,22 @@ default_dungeon_entrances = {
'Ganons Tower': ['GT Lobby']
}
drop_entrances = {
'Hyrule Castle': ['Sewers Rat Path'],
'Eastern Palace': [],
'Desert Palace': [],
'Tower of Hera': [],
'Agahnims Tower': [],
'Palace of Darkness': [],
'Swamp Palace': [],
'Skull Woods': ['Skull Pinball', 'Skull Left Drop', 'Skull Pot Circle', 'Skull Back Drop'],
'Thieves Town': [],
'Ice Palace': [],
'Misery Mire': [],
'Turtle Rock': [],
'Ganons Tower': []
}
# todo: calculate these for ER - the multi entrance dungeons anyway
dungeon_dead_end_allowance = {
@@ -3724,14 +3848,28 @@ dead_entrances = [
'TR Big Chest Entrance'
]
destination_entrances = [
'Sanctuary', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Sewers Rat Path', 'Desert East Lobby',
'Desert West Lobby', 'Skull Pinball', 'Skull Pot Circle', 'Skull Left Drop', 'Skull 2 West Lobby',
'Skull Back Drop', 'TR Big Chest Entrance', 'TR Eye Bridge', 'TR Lazy Eyes'
]
split_check_entrance_invalid = [
'Desert East Lobby', 'Skull 2 West Lobby'
]
dungeon_portals = {
'Hyrule Castle': ['Hyrule Castle South', 'Hyrule Castle West', 'Hyrule Castle East', 'Sanctuary'],
'Eastern Palace': ['Eastern'],
'Desert Palace': ['Desert Back', 'Desert South', 'Desert West', 'Desert East'],
'Tower of Hera': ['Hera'],
'Agahnims Tower': ['Agahnims Tower'],
'Palace of Darkness': ['Palace of Darkness'],
'Swamp Palace': ['Swamp'],
'Skull Woods': ['Skull 1', 'Skull 2 East', 'Skull 2 West', 'Skull 3'],
'Thieves Town': ['Thieves Town'],
'Ice Palace': ['Ice'],
'Misery Mire': ['Mire'],
'Turtle Rock': ['Turtle Rock Main', 'Turtle Rock Lazy Eyes', 'Turtle Rock Chest', 'Turtle Rock Eye Bridge'],
'Ganons Tower': ['Ganons Tower']
}
dungeon_drops = {
'Hyrule Castle': ['Sewers Rat Path'],
'Skull Woods': ['Skull Pot Circle', 'Skull Pinball', 'Skull Left Drop', 'Skull Back Drop'],
}

View File

@@ -7,27 +7,28 @@ from Items import ItemFactory
def create_dungeons(world, player):
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro[player] else small_keys, dungeon_items, player)
def make_dungeon(name, id, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro[player] else small_keys,
dungeon_items, player, id)
dungeon.boss = BossFactory(default_boss, player)
for region in dungeon.regions:
world.get_region(region, player).dungeon = dungeon
dungeon.world = world
return dungeon
ES = make_dungeon('Hyrule Castle', None, hyrule_castle_regions, None, [ItemFactory('Small Key (Escape)', player)], [ItemFactory('Map (Escape)', player)])
EP = make_dungeon('Eastern Palace', 'Armos Knights', eastern_regions, ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player))
DP = make_dungeon('Desert Palace', 'Lanmolas', desert_regions, ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player))
ToH = make_dungeon('Tower of Hera', 'Moldorm', hera_regions, ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player))
PoD = make_dungeon('Palace of Darkness', 'Helmasaur King', pod_regions, ItemFactory('Big Key (Palace of Darkness)', player), ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player))
TT = make_dungeon('Thieves Town', 'Blind', thieves_regions, ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
SW = make_dungeon('Skull Woods', 'Mothula', skull_regions, ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 3, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
SP = make_dungeon('Swamp Palace', 'Arrghus', swamp_regions, ItemFactory('Big Key (Swamp Palace)', player), [ItemFactory('Small Key (Swamp Palace)', player)], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
IP = make_dungeon('Ice Palace', 'Kholdstare', ice_regions, ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
MM = make_dungeon('Misery Mire', 'Vitreous', mire_regions, ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
TR = make_dungeon('Turtle Rock', 'Trinexx', tr_regions, ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
AT = make_dungeon('Agahnims Tower', 'Agahnim', tower_regions, None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
GT = make_dungeon('Ganons Tower', 'Agahnim2', gt_regions, ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
ES = make_dungeon('Hyrule Castle', 1, None, hyrule_castle_regions, None, [ItemFactory('Small Key (Escape)', player)], [ItemFactory('Map (Escape)', player)])
EP = make_dungeon('Eastern Palace', 2, 'Armos Knights', eastern_regions, ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player))
DP = make_dungeon('Desert Palace', 3, 'Lanmolas', desert_regions, ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player))
ToH = make_dungeon('Tower of Hera', 10, 'Moldorm', hera_regions, ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player))
PoD = make_dungeon('Palace of Darkness', 6, 'Helmasaur King', pod_regions, ItemFactory('Big Key (Palace of Darkness)', player), ItemFactory(['Small Key (Palace of Darkness)'] * 6, player), ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player))
TT = make_dungeon('Thieves Town', 11, 'Blind', thieves_regions, ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)], ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
SW = make_dungeon('Skull Woods', 8, 'Mothula', skull_regions, ItemFactory('Big Key (Skull Woods)', player), ItemFactory(['Small Key (Skull Woods)'] * 3, player), ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
SP = make_dungeon('Swamp Palace', 5, 'Arrghus', swamp_regions, ItemFactory('Big Key (Swamp Palace)', player), [ItemFactory('Small Key (Swamp Palace)', player)], ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
IP = make_dungeon('Ice Palace', 9, 'Kholdstare', ice_regions, ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
MM = make_dungeon('Misery Mire', 7, 'Vitreous', mire_regions, ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
TR = make_dungeon('Turtle Rock', 12, 'Trinexx', tr_regions, ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
AT = make_dungeon('Agahnims Tower', 4, 'Agahnim', tower_regions, None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
GT = make_dungeon('Ganons Tower', 13, 'Agahnim2', gt_regions, ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
GT.bosses['bottom'] = BossFactory('Armos Knights', player)
GT.bosses['middle'] = BossFactory('Lanmolas', player)
@@ -166,9 +167,9 @@ hyrule_castle_regions = [
'Hyrule Dungeon Map Room', 'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk',
'Hyrule Dungeon South Abyss', 'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom',
'Hyrule Dungeon Armory Main', 'Hyrule Dungeon Armory Boomerang', 'Hyrule Dungeon Armory North Branch',
'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Sewers Behind Tapestry', 'Sewers Rope Room',
'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Rat Path', 'Sewers Secret Room Blocked Path',
'Sewers Secret Room', 'Sewers Yet More Rats', 'Sewers Pull Switch', 'Sanctuary'
'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Hyrule Dungeon Cell', 'Sewers Behind Tapestry',
'Sewers Rope Room', 'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Rat Path',
'Sewers Secret Room Blocked Path', 'Sewers Secret Room', 'Sewers Yet More Rats', 'Sewers Pull Switch', 'Sanctuary'
]
eastern_regions = [
@@ -238,9 +239,10 @@ thieves_regions = [
'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom',
'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway',
'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch',
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right', 'Thieves Attic Window',
'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak', 'Thieves Blind\'s Cell',
'Thieves Conveyor Bridge', 'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap'
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right',
'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak',
"Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge', 'Thieves Conveyor Block',
'Thieves Big Chest Room', 'Thieves Trap'
]
ice_regions = [
@@ -332,7 +334,7 @@ region_starts = {
}
standard_starts = {
'Hyrule Castle': ['Hyrule Castle Lobby']
'Hyrule Castle': ['Hyrule Castle South']
}
split_region_starts = {

View File

@@ -316,7 +316,7 @@ def link_entrances(world, player):
caves.remove(old_man_house[0])
except ValueError:
pass
else: #if the cave wasn't placed we get here
else: # if the cave wasn't placed we get here
connect_caves(world, lw_entrances, [], old_man_house, player)
if world.mode[player] == 'standard':
# rest of hyrule castle must be in light world
@@ -325,7 +325,6 @@ def link_entrances(world, player):
elif world.doorShuffle[player] != 'vanilla':
connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')], player)
# place old man, has limited options
# exit has to come from specific set of doors, the entrance is free to move about
old_man_entrances = [door for door in old_man_entrances if door in lw_entrances]
@@ -711,7 +710,7 @@ def link_entrances(world, player):
# handle remaining caves
while caves:
# connect highest exit count caves first, prevent issue where we have 2 or 3 exits accross worlds left to fill
# connect highest exit count caves first, prevent issue where we have 2 or 3 exits across worlds left to fill
cave_candidate = (None, 0)
for i, cave in enumerate(caves):
if isinstance(cave, str):
@@ -1062,7 +1061,7 @@ def link_entrances(world, player):
raise NotImplementedError('Shuffling not supported yet')
# check for swamp palace fix
if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Lobby':
if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Placeholder':
world.swamp_patch_required[player] = True
# check for potion shop location
@@ -1074,7 +1073,7 @@ def link_entrances(world, player):
world.ganon_at_pyramid[player] = False
# check for Ganon's Tower location
if world.get_entrance('Ganons Tower', player).connected_region.name != 'GT Lobby':
if world.get_entrance('Ganons Tower', player).connected_region.name != 'Ganons Tower Placeholder':
world.ganonstower_vanilla[player] = False
def link_inverted_entrances(world, player):
@@ -3165,7 +3164,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
('Two Brothers House Exit (East)', 'Light World'),
('Two Brothers House Exit (West)', 'Maze Race Ledge'),
('Sanctuary', 'Sanctuary'),
('Sanctuary', 'Sanctuary Placeholder'),
('Sanctuary Grave', 'Sewer Drop'),
('Sanctuary Exit', 'Light World'),
@@ -3400,64 +3399,64 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
('Inverted Pyramid Entrance', 'Bottom of Pyramid')]
# non shuffled dungeons
default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Main Lobby'),
('Desert Palace Entrance (West)', 'Desert West Lobby'),
('Desert Palace Entrance (North)', 'Desert Back Lobby'),
('Desert Palace Entrance (East)', 'Desert East Lobby'),
default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert South Placeholder'),
('Desert Palace Entrance (West)', 'Desert West Placeholder'),
('Desert Palace Entrance (North)', 'Desert Back Placeholder'),
('Desert Palace Entrance (East)', 'Desert East Placeholder'),
('Desert Palace Exit (South)', 'Desert Palace Stairs'),
('Desert Palace Exit (West)', 'Desert Ledge'),
('Desert Palace Exit (East)', 'Desert Palace Lone Stairs'),
('Desert Palace Exit (North)', 'Desert Palace Entrance (North) Spot'),
('Eastern Palace', 'Eastern Lobby'),
('Eastern Palace', 'Eastern Placeholder'),
('Eastern Palace Exit', 'Light World'),
('Tower of Hera', 'Hera Lobby'),
('Tower of Hera', 'Hera Placeholder'),
('Tower of Hera Exit', 'Death Mountain (Top)'),
('Hyrule Castle Entrance (South)', 'Hyrule Castle Lobby'),
('Hyrule Castle Entrance (West)', 'Hyrule Castle West Lobby'),
('Hyrule Castle Entrance (East)', 'Hyrule Castle East Lobby'),
('Hyrule Castle Entrance (South)', 'Hyrule Castle South Placeholder'),
('Hyrule Castle Entrance (West)', 'Hyrule Castle West Placeholder'),
('Hyrule Castle Entrance (East)', 'Hyrule Castle East Placeholder'),
('Hyrule Castle Exit (South)', 'Hyrule Castle Courtyard'),
('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'),
('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'),
('Agahnims Tower', 'Tower Lobby'),
('Agahnims Tower', 'Agahnims Tower Placeholder'),
('Agahnims Tower Exit', 'Hyrule Castle Ledge'),
('Thieves Town', 'Thieves Lobby'),
('Thieves Town', 'Thieves Town Placeholder'),
('Thieves Town Exit', 'West Dark World'),
('Skull Woods First Section Hole (East)', 'Skull Pinball'),
('Skull Woods First Section Hole (West)', 'Skull Left Drop'),
('Skull Woods First Section Hole (North)', 'Skull Pot Circle'),
('Skull Woods First Section Door', 'Skull 1 Lobby'),
('Skull Woods First Section Door', 'Skull 1 Placeholder'),
('Skull Woods First Section Exit', 'Skull Woods Forest'),
('Skull Woods Second Section Hole', 'Skull Back Drop'),
('Skull Woods Second Section Door (East)', 'Skull 2 East Lobby'),
('Skull Woods Second Section Door (West)', 'Skull 2 West Lobby'),
('Skull Woods Second Section Door (East)', 'Skull 2 East Placeholder'),
('Skull Woods Second Section Door (West)', 'Skull 2 West Placeholder'),
('Skull Woods Second Section Exit (East)', 'Skull Woods Forest'),
('Skull Woods Second Section Exit (West)', 'Skull Woods Forest (West)'),
('Skull Woods Final Section', 'Skull 3 Lobby'),
('Skull Woods Final Section', 'Skull 3 Placeholder'),
('Skull Woods Final Section Exit', 'Skull Woods Forest (West)'),
('Ice Palace', 'Ice Lobby'),
('Ice Palace', 'Ice Placeholder'),
('Ice Palace Exit', 'Dark Lake Hylia Central Island'),
('Misery Mire', 'Mire Lobby'),
('Misery Mire', 'Mire Placeholder'),
('Misery Mire Exit', 'Dark Desert'),
('Palace of Darkness', 'PoD Lobby'),
('Palace of Darkness', 'Palace of Darkness Placeholder'),
('Palace of Darkness Exit', 'East Dark World'),
('Swamp Palace', 'Swamp Lobby'), # requires additional patch for flooding moat if moved
('Swamp Palace', 'Swamp Placeholder'), # requires additional patch for flooding moat if moved
('Swamp Palace Exit', 'South Dark World'),
('Turtle Rock', 'TR Main Lobby'),
('Turtle Rock', 'Turtle Rock Main Placeholder'),
('Turtle Rock Exit (Front)', 'Dark Death Mountain (Top)'),
('Turtle Rock Ledge Exit (West)', 'Dark Death Mountain Ledge'),
('Turtle Rock Ledge Exit (East)', 'Dark Death Mountain Ledge'),
('Dark Death Mountain Ledge (West)', 'TR Lazy Eyes'),
('Dark Death Mountain Ledge (East)', 'TR Big Chest Entrance'),
('Dark Death Mountain Ledge (West)', 'Turtle Rock Lazy Eyes Placeholder'),
('Dark Death Mountain Ledge (East)', 'Turtle Rock Chest Placeholder'),
('Turtle Rock Isolated Ledge Exit', 'Dark Death Mountain Isolated Ledge'),
('Turtle Rock Isolated Ledge Entrance', 'TR Eye Bridge'),
('Turtle Rock Isolated Ledge Entrance', 'Turtle Rock Eye Bridge Placeholder'),
('Ganons Tower', 'GT Lobby'),
('Ganons Tower', 'Ganons Tower Placeholder'),
('Ganons Tower Exit', 'Dark Death Mountain (Top)')
]
]
inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Desert Main Lobby'),
('Desert Palace Entrance (West)', 'Desert West Lobby'),

View File

@@ -6,7 +6,7 @@ from BaseClasses import Region, RegionType, Shop, ShopType, Location
from Bosses import place_bosses
from Dungeons import get_dungeon_item_pool
from EntranceShuffle import connect_entrance
from Fill import FillError, fill_restrictive
from Fill import FillError, fill_restrictive, fast_fill
from Items import ItemFactory
import source.classes.constants as CONST
@@ -355,9 +355,12 @@ def generate_itempool(world, player):
if world.retro[player]:
set_up_take_anys(world, player)
if world.keydropshuffle[player]:
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32
create_dynamic_shop_locations(world, player)
take_any_locations = [
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)',
@@ -735,3 +738,21 @@ def test():
if __name__ == '__main__':
test()
def fill_specific_items(world):
keypool = [item for item in world.itempool if item.smallkey]
cage = world.get_location('Tower of Hera - Basement Cage', 1)
c_dungeon = cage.parent_region.dungeon
key_item = next(x for x in keypool if c_dungeon.name in x.name or (c_dungeon.name == 'Hyrule Castle' and 'Escape' in x.name))
world.itempool.remove(key_item)
all_state = world.get_all_state(True)
fill_restrictive(world, all_state, [cage], [key_item])
# somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria')
# shooter = world.get_location('Palace of Darkness - Shooter Room', 1)
# world.itempool.remove(somaria)
# all_state = world.get_all_state(True)
# fill_restrictive(world, all_state, [shooter], [somaria])

View File

@@ -3,9 +3,9 @@ import logging
from collections import defaultdict, deque
from BaseClasses import DoorType
from Regions import dungeon_events, key_only_locations
from Regions import dungeon_events
from Dungeons import dungeon_keys, dungeon_bigs
from DungeonGenerator import ExplorationState
from DungeonGenerator import ExplorationState, special_big_key_doors
class KeyLayout(object):
@@ -190,7 +190,7 @@ def build_key_layout(builder, start_regions, proposal, world, player):
key_layout.flat_prop = flatten_pair_list(key_layout.proposal)
key_layout.max_drops = count_key_drops(key_layout.sector)
key_layout.max_chests = calc_max_chests(builder, key_layout, world, player)
key_layout.big_key_special = 'Hyrule Dungeon Cellblock' in key_layout.sector.region_set()
key_layout.big_key_special = check_bk_special(key_layout.sector.region_set(), world, player)
key_layout.all_locations = find_all_locations(key_layout.sector)
return key_layout
@@ -199,7 +199,7 @@ def count_key_drops(sector):
cnt = 0
for region in sector.regions:
for loc in region.locations:
if loc.event and 'Small Key' in loc.item.name:
if loc.forced_item and 'Small Key' in loc.item.name:
cnt += 1
return cnt
@@ -243,18 +243,17 @@ def analyze_dungeon(key_layout, world, player):
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
avail_bigs = exist_relevant_big_doors(key_counter, key_layout) or exist_big_chest(key_counter)
non_big_locs = count_locations_big_optional(key_counter.free_locations)
if not key_counter.big_key_opened:
big_avail = key_counter.big_key_opened or (key_layout.big_key_special and any(x for x in key_counter.other_locations.keys() if x.forced_item and x.forced_item.bigkey))
if not big_avail:
if chest_keys == non_big_locs and chest_keys > 0 and available <= possible_smalls and not avail_bigs:
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
# try to relax the rules here? - smallest requirement that doesn't force a softlock
child_queue = deque()
for child in key_counter.child_doors.keys():
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
if not child.bigKey or not key_layout.big_key_special or big_avail:
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world, player)
empty_flag = empty_counter(odd_counter)
child_queue.append((child, odd_counter, empty_flag))
if child in doors_completed and child in key_logic.door_rules.keys():
rule = key_logic.door_rules[child]
while len(child_queue) > 0:
child, odd_counter, empty_flag = child_queue.popleft()
if not child.bigKey and child not in doors_completed:
@@ -650,17 +649,17 @@ def find_worst_counter(door, odd_counter, key_counter, key_layout, skip_bk): #
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk, reserve=1):
small_doors = []
big_doors = []
if key_layout.big_key_special:
big_key_available = any(x for x in key_counter.other_locations.keys() if x.forced_item and x.forced_item.bigkey)
else:
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 0
for other in key_counter.child_doors:
if other not in ignored_doors and other.dest not in ignored_doors:
if other.bigKey:
if not skip_bk and (not key_layout.big_key_special or key_counter.big_key_opened):
if not skip_bk and (not key_layout.big_key_special or big_key_available):
big_doors.append(other)
elif other.dest not in small_doors:
small_doors.append(other)
if key_layout.big_key_special:
big_key_available = key_counter.big_key_opened
else:
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 0
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
return None
return small_doors + big_doors
@@ -832,6 +831,13 @@ def available_chest_small_keys_logic(key_counter, world, player, sm_restricted):
return key_counter.max_chests
def big_key_drop_available(key_counter):
for loc in key_counter.other_locations:
if loc.forced_big_key():
return True
return False
def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_layout, world, player):
if key_counter.big_key_opened:
return
@@ -878,7 +884,7 @@ def find_worst_counter_wo_bk(small_key_num, accessible_set, door, odd_ctr, key_c
def open_a_door(door, child_state, flat_proposal):
if door.bigKey:
if door.bigKey or door.name in special_big_key_doors:
child_state.big_key_opened = True
child_state.avail_doors.extend(child_state.big_doors)
child_state.opened_doors.extend(set([d.door for d in child_state.big_doors]))
@@ -985,8 +991,7 @@ def count_locations_exclude_big_chest(state):
cnt = 0
for loc in state.found_locations:
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events:
if not loc.forced_item and loc.name not in ['Agahnim 1', 'Agahnim 2', "Hyrule Castle - Zelda's Chest",
"Thieves' Town - Blind's Cell"]:
if not loc.forced_item and loc.name not in ['Agahnim 1', 'Agahnim 2']:
cnt += 1
return cnt
@@ -1138,16 +1143,20 @@ def check_rules_deep(original_counter, key_layout, world, player):
bail = 0
last_counter = counter
chest_keys = available_chest_small_keys_logic(counter, world, player, key_logic.sm_restricted)
big_avail = counter.big_key_opened
big_maybe_not_found = not counter.big_key_opened
bk_drop = big_key_drop_available(counter)
big_avail = counter.big_key_opened or bk_drop
big_maybe_not_found = not counter.big_key_opened and not bk_drop # better named as big_missing?
if not key_layout.big_key_special and not big_avail:
for location in counter.free_locations:
if location not in key_logic.bk_restricted:
big_avail = True
break
if world.bigkeyshuffle[player]:
big_avail = True
else:
for location in counter.free_locations:
if location not in key_logic.bk_restricted:
big_avail = True
break
outstanding_big_locs = {x for x in big_locations if x not in counter.free_locations}
if big_maybe_not_found:
if len(outstanding_big_locs) == 0:
if len(outstanding_big_locs) == 0 and not key_layout.big_key_special:
big_maybe_not_found = False
big_uses_chest = big_avail and not key_layout.big_key_special
collected_alt = len(counter.key_only_locations) + chest_keys
@@ -1155,7 +1164,7 @@ def check_rules_deep(original_counter, key_layout, world, player):
chest_keys -= 1
collected = len(counter.key_only_locations) + chest_keys
can_progress = len(counter.child_doors) == 0
smalls_opened = False
smalls_opened, big_opened = False, False
small_rules = []
for door in counter.child_doors.keys():
can_open = False
@@ -1229,6 +1238,15 @@ def set_paired_rules(key_logic, world, player):
rule.opposite = key_logic.door_rules[door.dest.name]
def check_bk_special(regions, world, player):
for r_name in regions:
region = world.get_region(r_name, player)
for loc in region.locations:
if loc.forced_big_key():
return True
return False
# Soft lock stuff
def validate_key_layout(key_layout, world, player):
# retro is all good - except for hyrule castle in standard mode
@@ -1237,7 +1255,7 @@ def validate_key_layout(key_layout, world, player):
flat_proposal = key_layout.flat_prop
state = ExplorationState(dungeon=key_layout.sector.name)
state.key_locations = key_layout.max_chests
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
state.big_key_special = check_bk_special(key_layout.sector.regions, 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)
@@ -1259,7 +1277,10 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
return False
# todo: allow more key shuffles - refine placement rules
# if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
found_forced_bk = state.found_forced_bk()
smalls_done = not smalls_avail or not enough_small_locations(state, available_small_locations)
bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk)
if smalls_done and bk_done:
return False
else:
if smalls_avail and available_small_locations > 0:
@@ -1278,10 +1299,11 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
valid = checked_states[code]
if not valid:
return False
if not state.big_key_opened and available_big_locations >= num_bigs > 0:
if not state.big_key_opened and (available_big_locations >= num_bigs > 0 or (found_forced_bk and num_bigs > 0)):
state_copy = state.copy()
open_a_door(state.big_doors[0].door, state_copy, flat_proposal)
state_copy.used_locations += 1
if not found_forced_bk:
state_copy.used_locations += 1
code = state_id(state_copy, flat_proposal)
if code not in checked_states.keys():
valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal,
@@ -1347,7 +1369,12 @@ def create_key_counters(key_layout, world, player):
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
else:
state.key_locations = world.dungeon_layouts[player][key_layout.sector.name].key_doors_num
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
state.big_key_special, special_region = False, None
for region in key_layout.sector.regions:
for location in region.locations:
if location.forced_big_key():
state.big_key_special = True
special_region = region
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)
@@ -1359,10 +1386,10 @@ def create_key_counters(key_layout, world, player):
next_key_counter, parent_state = queue.popleft()
for door in next_key_counter.child_doors:
child_state = parent_state.copy()
if door.bigKey:
if door.bigKey or door.name in special_big_key_doors:
key_layout.key_logic.bk_doors.add(door)
# open the door, if possible
if not door.bigKey or not child_state.big_key_special or child_state.big_key_opened:
if not door.bigKey or not child_state.big_key_special or child_state.visited(special_region):
open_a_door(door, child_state, flat_proposal)
expand_key_state(child_state, flat_proposal, world, player)
code = state_id(child_state, key_layout.flat_prop)
@@ -1390,10 +1417,7 @@ def create_key_counter(state, key_layout, world, player):
key_counter.other_locations[loc] = None
key_counter.open_doors.update(dict.fromkeys(state.opened_doors))
key_counter.used_keys = count_unique_sm_doors(state.opened_doors)
if state.big_key_special:
key_counter.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player))
else:
key_counter.big_key_opened = state.big_key_opened
key_counter.big_key_opened = state.big_key_opened
return key_counter
@@ -1412,7 +1436,7 @@ def imp_locations_factory(world, player):
def important_location(loc, world, player):
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_item is not None and loc.item.bigkey)
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_big_key())
def create_odd_key_counter(door, parent_counter, key_layout, world, player):
@@ -1679,13 +1703,13 @@ def validate_key_placement(key_layout, world, player):
max_counter = find_max_counter(key_layout)
keys_outside = 0
big_key_outside = False
dungeon = world.get_dungeon(key_layout.sector.name, player)
smallkey_name = 'Small Key (%s)' % (key_layout.sector.name if key_layout.sector.name != 'Hyrule Castle' else 'Escape')
smallkey_name = dungeon_keys[key_layout.sector.name]
bigkey_name = dungeon_bigs[key_layout.sector.name]
if world.keyshuffle[player]:
keys_outside = key_layout.max_chests - sum(1 for i in max_counter.free_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player)
if world.bigkeyshuffle[player]:
max_counter = find_max_counter(key_layout)
big_key_outside = dungeon.big_key not in (l.item for l in max_counter.free_locations)
big_key_outside = bigkey_name not in (l.item.name for l in max_counter.free_locations if l.item)
for code, counter in key_layout.key_counters.items():
if len(counter.child_doors) == 0:
@@ -1693,7 +1717,7 @@ def validate_key_placement(key_layout, world, player):
if key_layout.big_key_special:
big_found = any(i.forced_item is not None and i.item.bigkey for i in counter.other_locations) or big_key_outside
else:
big_found = any(i.item is not None and i.item == dungeon.big_key for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
big_found = any(i.item is not None and i.item.name == bigkey_name for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
if counter.big_key_opened and not big_found:
continue # Can't get to this state
found_locations = set(i for i in counter.free_locations if big_found or "- Big Chest" not in i.name)
@@ -1703,7 +1727,7 @@ def validate_key_placement(key_layout, world, player):
found_keys > counter.used_keys and any(not d.bigKey for d in counter.child_doors)
if not can_progress:
missing_locations = set(max_counter.free_locations.keys()).difference(found_locations)
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name]
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item.name != bigkey_name) or "- Boss" in l.name]
# missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations?
if len(missing_items) > 0: # world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0):
logging.getLogger('').error("Keylock - can't open locations: ")

27
Main.py
View File

@@ -11,20 +11,20 @@ import zlib
from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance
from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions, adjust_locations
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
from EntranceShuffle import link_entrances, link_inverted_entrances
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
from Doors import create_doors
from DoorShuffle import link_doors
from DoorShuffle import link_doors, connect_portal_copy
from RoomData import create_rooms
from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
from ItemList import generate_itempool, difficulties, fill_prizes
from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items
from Utils import output_path, parse_player_names
__version__ = '0.1.1-dev'
__version__ = '0.2.0.8-u'
class EnemizerError(RuntimeError):
pass
@@ -66,6 +66,8 @@ def main(args, seed=None, fish=None):
world.experimental = args.experimental.copy()
world.dungeon_counters = args.dungeon_counters.copy()
world.fish = fish
world.keydropshuffle = args.keydropshuffle.copy()
world.mixed_travel = args.mixed_travel.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
@@ -105,6 +107,7 @@ def main(args, seed=None, fish=None):
create_doors(world, player)
create_rooms(world, player)
create_dungeons(world, player)
adjust_locations(world, player)
logger.info(world.fish.translate("cli","cli","shuffling.world"))
@@ -136,6 +139,9 @@ def main(args, seed=None, fish=None):
fill_prizes(world)
# used for debugging
# fill_specific_items(world)
logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
shuffled_locations = None
@@ -371,6 +377,8 @@ def copy_world(world):
ret.beemizer = world.beemizer.copy()
ret.intensity = world.intensity.copy()
ret.experimental = world.experimental.copy()
ret.keydropshuffle = world.keydropshuffle.copy()
ret.mixed_travel = world.mixed_travel.copy()
for player in range(1, world.players + 1):
if world.mode[player] != 'inverted':
@@ -441,6 +449,11 @@ def copy_world(world):
ret.inaccessible_regions = world.inaccessible_regions
ret.dungeon_layouts = world.dungeon_layouts
ret.key_logic = world.key_logic
ret.dungeon_portals = world.dungeon_portals
for player, portals in world.dungeon_portals.items():
for portal in portals:
connect_portal_copy(portal, ret, player)
ret.sanc_portal = world.sanc_portal
for player in range(1, world.players + 1):
set_rules(ret, player)
@@ -502,11 +515,11 @@ def create_playthrough(world):
state_cache.append(state.copy())
logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
logging.getLogger('').debug(world.fish.translate("cli", "cli", "building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
if not sphere:
logging.getLogger('').error(world.fish.translate("cli","cli","cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
logging.getLogger('').error(world.fish.translate("cli", "cli", "cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
raise RuntimeError(world.fish.translate("cli", "cli", "cannot.reach.progression"))
else:
old_world.spoiler.unreachables = sphere_candidates.copy()
break

View File

@@ -126,23 +126,34 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
'Desert Palace - Map Chest': (0x74, 0x10),
'Desert Palace - Compass Chest': (0x85, 0x10),
'Desert Palace - Big Key Chest': (0x75, 0x10),
'Desert Palace - Desert Tiles 1 Pot Key': (0x63, 0x400),
'Desert Palace - Beamos Hall Pot Key': (0x53, 0x400),
'Desert Palace - Desert Tiles 2 Pot Key': (0x43, 0x400),
'Desert Palace - Boss': (0x33, 0x800),
'Eastern Palace - Compass Chest': (0xa8, 0x10),
'Eastern Palace - Big Chest': (0xa9, 0x10),
'Eastern Palace - Dark Square Pot Key': (0xba, 0x400),
'Eastern Palace - Dark Eyegore Key Drop': (0x99, 0x400),
'Eastern Palace - Cannonball Chest': (0xb9, 0x10),
'Eastern Palace - Big Key Chest': (0xb8, 0x10),
'Eastern Palace - Map Chest': (0xaa, 0x10),
'Eastern Palace - Boss': (0xc8, 0x800),
'Hyrule Castle - Boomerang Chest': (0x71, 0x10),
'Hyrule Castle - Boomerang Guard Key Drop': (0x71, 0x400),
'Hyrule Castle - Map Chest': (0x72, 0x10),
'Hyrule Castle - Map Guard Key Drop': (0x72, 0x400),
"Hyrule Castle - Zelda's Chest": (0x80, 0x10),
'Hyrule Castle - Big Key Drop': (0x80, 0x400),
'Sewers - Dark Cross': (0x32, 0x10),
'Hyrule Castle - Key Rat Key Drop': (0x21, 0x400),
'Sewers - Secret Room - Left': (0x11, 0x10),
'Sewers - Secret Room - Middle': (0x11, 0x20),
'Sewers - Secret Room - Right': (0x11, 0x40),
'Sanctuary': (0x12, 0x10),
'Castle Tower - Room 03': (0xe0, 0x10),
'Castle Tower - Dark Maze': (0xd0, 0x10),
'Castle Tower - Dark Archer Key Drop': (0xc0, 0x400),
'Castle Tower - Circle of Pots Key Drop': (0xb0, 0x400),
'Spectacle Rock Cave': (0xea, 0x400),
'Paradox Cave Lower - Far Left': (0xef, 0x10),
'Paradox Cave Lower - Left': (0xef, 0x20),
@@ -181,18 +192,25 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
'Mimic Cave': (0x10c, 0x10),
'Swamp Palace - Entrance': (0x28, 0x10),
'Swamp Palace - Map Chest': (0x37, 0x10),
'Swamp Palace - Pot Row Pot Key': (0x38, 0x400),
'Swamp Palace - Trench 1 Pot Key': (0x37, 0x400),
'Swamp Palace - Hookshot Pot Key': (0x36, 0x400),
'Swamp Palace - Big Chest': (0x36, 0x10),
'Swamp Palace - Compass Chest': (0x46, 0x10),
'Swamp Palace - Trench 2 Pot Key': (0x35, 0x400),
'Swamp Palace - Big Key Chest': (0x35, 0x10),
'Swamp Palace - West Chest': (0x34, 0x10),
'Swamp Palace - Flooded Room - Left': (0x76, 0x10),
'Swamp Palace - Flooded Room - Right': (0x76, 0x20),
'Swamp Palace - Waterfall Room': (0x66, 0x10),
'Swamp Palace - Waterway Pot Key': (0x16, 0x400),
'Swamp Palace - Boss': (0x6, 0x800),
"Thieves' Town - Big Key Chest": (0xdb, 0x20),
"Thieves' Town - Map Chest": (0xdb, 0x10),
"Thieves' Town - Compass Chest": (0xdc, 0x10),
"Thieves' Town - Ambush Chest": (0xcb, 0x10),
"Thieves' Town - Hallway Pot Key": (0xbc, 0x400),
"Thieves' Town - Spike Switch Pot Key": (0xab, 0x400),
"Thieves' Town - Attic": (0x65, 0x10),
"Thieves' Town - Big Chest": (0x44, 0x10),
"Thieves' Town - Blind's Cell": (0x45, 0x10),
@@ -203,28 +221,39 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
'Skull Woods - Pot Prison': (0x57, 0x20),
'Skull Woods - Pinball Room': (0x68, 0x10),
'Skull Woods - Big Key Chest': (0x57, 0x10),
'Skull Woods - West Lobby Pot Key': (0x56, 0x400),
'Skull Woods - Bridge Room': (0x59, 0x10),
'Skull Woods - Spike Corner Key Drop': (0x39, 0x400),
'Skull Woods - Boss': (0x29, 0x800),
'Ice Palace - Jelly Key Drop': (0x0e, 0x400),
'Ice Palace - Compass Chest': (0x2e, 0x10),
'Ice Palace - Conveyor Key Drop': (0x3e, 0x400),
'Ice Palace - Freezor Chest': (0x7e, 0x10),
'Ice Palace - Big Chest': (0x9e, 0x10),
'Ice Palace - Iced T Room': (0xae, 0x10),
'Ice Palace - Many Pots Pot Key': (0x9f, 0x400),
'Ice Palace - Spike Room': (0x5f, 0x10),
'Ice Palace - Big Key Chest': (0x1f, 0x10),
'Ice Palace - Hammer Block Key Drop': (0x3f, 0x400),
'Ice Palace - Map Chest': (0x3f, 0x10),
'Ice Palace - Boss': (0xde, 0x800),
'Misery Mire - Big Chest': (0xc3, 0x10),
'Misery Mire - Map Chest': (0xc3, 0x20),
'Misery Mire - Main Lobby': (0xc2, 0x10),
'Misery Mire - Bridge Chest': (0xa2, 0x10),
'Misery Mire - Spikes Pot Key': (0xb3, 0x400),
'Misery Mire - Spike Chest': (0xb3, 0x10),
'Misery Mire - Fishbone Pot Key': (0xa1, 0x400),
'Misery Mire - Conveyor Crystal Key Drop': (0xc1, 0x400),
'Misery Mire - Compass Chest': (0xc1, 0x10),
'Misery Mire - Big Key Chest': (0xd1, 0x10),
'Misery Mire - Boss': (0x90, 0x800),
'Turtle Rock - Compass Chest': (0xd6, 0x10),
'Turtle Rock - Roller Room - Left': (0xb7, 0x10),
'Turtle Rock - Roller Room - Right': (0xb7, 0x20),
'Turtle Rock - Pokey 1 Key Drop': (0xb6, 0x400),
'Turtle Rock - Chain Chomps': (0xb6, 0x10),
'Turtle Rock - Pokey 2 Key Drop': (0x13, 0x400),
'Turtle Rock - Big Key Chest': (0x14, 0x10),
'Turtle Rock - Big Chest': (0x24, 0x10),
'Turtle Rock - Crystaroller Room': (0x4, 0x10),
@@ -247,6 +276,7 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
'Palace of Darkness - Big Chest': (0x1a, 0x10),
'Palace of Darkness - Harmless Hellway': (0x1a, 0x40),
'Palace of Darkness - Boss': (0x5a, 0x800),
'Ganons Tower - Conveyor Cross Pot Key': (0x8b, 0x400),
"Ganons Tower - Bob's Torch": (0x8c, 0x400),
'Ganons Tower - Hope Room - Left': (0x8c, 0x20),
'Ganons Tower - Hope Room - Right': (0x8c, 0x40),
@@ -255,11 +285,13 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
'Ganons Tower - Compass Room - Top Right': (0x9d, 0x20),
'Ganons Tower - Compass Room - Bottom Left': (0x9d, 0x40),
'Ganons Tower - Compass Room - Bottom Right': (0x9d, 0x80),
'Ganons Tower - Conveyor Star Pits Pot Key': (0x7b, 0x400),
'Ganons Tower - DMs Room - Top Left': (0x7b, 0x10),
'Ganons Tower - DMs Room - Top Right': (0x7b, 0x20),
'Ganons Tower - DMs Room - Bottom Left': (0x7b, 0x40),
'Ganons Tower - DMs Room - Bottom Right': (0x7b, 0x80),
'Ganons Tower - Map Chest': (0x8b, 0x10),
'Ganons Tower - Double Switch Pot Key': (0x9b, 0x400),
'Ganons Tower - Firesnake Room': (0x7d, 0x10),
'Ganons Tower - Randomizer Room - Top Left': (0x7c, 0x10),
'Ganons Tower - Randomizer Room - Top Right': (0x7c, 0x20),
@@ -272,6 +304,7 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
'Ganons Tower - Big Key Chest': (0x1c, 0x10),
'Ganons Tower - Mini Helmasaur Room - Left': (0x3d, 0x10),
'Ganons Tower - Mini Helmasaur Room - Right': (0x3d, 0x20),
'Ganons Tower - Mini Helmasaur Key Drop': (0x3d, 0x400),
'Ganons Tower - Pre-Moldorm Chest': (0x3d, 0x40),
'Ganons Tower - Validation Chest': (0x4d, 0x10)}
location_table_npc = {'Mushroom': 0x1000,
@@ -630,7 +663,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
ctx.player_names = {p: n for p, n in args[1]}
msgs = []
if ctx.locations_checked:
msgs.append(['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]])
msgs.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
if ctx.locations_scouted:
msgs.append(['LocationScouts', list(ctx.locations_scouted)])
if msgs:
@@ -643,7 +676,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
elif start_index != len(ctx.items_received):
sync_msg = [['Sync']]
if ctx.locations_checked:
sync_msg.append(['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]])
sync_msg.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
await send_msgs(ctx.socket, sync_msg)
if start_index == len(ctx.items_received):
for item in items:
@@ -655,7 +688,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
if location not in ctx.locations_info:
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
item_name = replacements.get(item, get_item_name_from_id(item))
logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.location_table.keys())[location - 1]}")
logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.lookup_id_to_name.keys())[location - 1]}")
ctx.locations_info[location] = (item, player)
ctx.watcher_event.set()
@@ -736,7 +769,7 @@ async def console_loop(ctx : Context):
get_location_name_from_address(item.location), index, len(ctx.items_received)))
if command[0] == '/missing':
for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]:
for location in [k for k, v in Regions.lookup_name_to_id.items() if type(v[0]) is int]:
if location not in ctx.locations_checked:
logging.info('Missing: ' + location)
if command[0] == '/getitem' and len(command) > 1:
@@ -751,23 +784,26 @@ async def console_loop(ctx : Context):
await snes_flush_writes(ctx)
def get_item_name_from_id(code):
items = [k for k, i in Items.item_table.items() if type(i[3]) is int and i[3] == code]
return items[0] if items else 'Unknown item'
return items[0] if items else f'Unknown item (ID:{code})'
def get_location_name_from_address(address):
if type(address) is str:
return address
locs = [k for k, l in Regions.location_table.items() if type(l[0]) is int and l[0] == address]
return locs[0] if locs else 'Unknown location'
return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
async def track_locations(ctx : Context, roomid, roomdata):
new_locations = []
def new_check(location):
ctx.locations_checked.add(location)
logging.info("New check: %s (%d/216)" % (location, len(ctx.locations_checked)))
new_locations.append(Regions.location_table[location][0])
new_locations.append(Regions.lookup_name_to_id[location])
for location, (loc_roomid, loc_mask) in location_table_uw.items():
if location not in ctx.locations_checked and loc_roomid == roomid and (roomdata << 4) & loc_mask != 0:
@@ -882,7 +918,7 @@ async def game_watcher(ctx : Context):
if scout_location > 0 and scout_location not in ctx.locations_scouted:
ctx.locations_scouted.add(scout_location)
logging.info(f'Scouting item at {list(Regions.location_table.keys())[scout_location - 1]}')
logging.info(f'Scouting item at {list(Regions.lookup_id_to_name.keys())[scout_location - 1]}')
await send_msgs(ctx.socket, [['LocationScouts', [scout_location]]])
await track_locations(ctx, roomid, roomdata)

View File

@@ -154,7 +154,8 @@ def send_new_items(ctx : Context):
client.send_index = len(items)
def forfeit_player(ctx : Context, team, slot):
all_locations = [values[0] for values in Regions.location_table.values() if type(values[0]) is int]
all_locations = {values[0] for values in Regions.location_table.values() if type(values[0]) is int}
all_locations.update({values[1] for values in Regions.key_drop_data.values()})
notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
register_location_checks(ctx, team, slot, all_locations)
@@ -248,11 +249,11 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
return
locs = []
for location in args:
if type(location) is not int or 0 >= location > len(Regions.location_table):
if type(location) is not int or 0 >= location > len(Regions.lookup_id_to_name.keys()):
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
return
loc_name = list(Regions.location_table.keys())[location - 1]
target_item, target_player = ctx.locations[(Regions.location_table[loc_name][0], client.slot)]
loc_name = list(Regions.lookup_id_to_name.keys())[location - 1]
target_item, target_player = ctx.locations[(Regions.lookup_name_to_id[loc_name], client.slot)]
replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D}
item_type = [i[2] for i in Items.item_table.values() if type(i[3]) is int and i[3] == target_item]

View File

@@ -22,7 +22,7 @@ Alternatively, run ```Gui.py``` for a simple graphical user interface. (WIP)
Only extra settings are found here. All entrance randomizer settings are supported. See their [readme](https://github.com/KevinCathcart/ALttPEntranceRandomizer/blob/master/README.md)
## Door Shuffle
## Door Shuffle (--doorShuffle)
### Basic
@@ -36,15 +36,41 @@ Doors are shuffled between dungeons as well.
Doors are not shuffled.
## Intensity
## Intensity (--intensity number)
#### Level 1
Normal door and spiral staircases are shuffled
#### Level 2
Same as Level 1 plus open edges and straight staircases are shuffled.
#### Level 3 (Coming soon)
#### Level 3
Same as Level 2 plus Dungeon Lobbies are shuffled
## KeyDropShuffle (--keydropshuffle)
Adds 33 new locations to the randomization pool. The 32 small keys found under pots and dropped by enemies and the Big
Key drop location are added to the pool. The keys normally found there are added to the item pool. Retro adds
32 generic keys to the pool instead.
## Crossed Dungeon Specific Settings
### Mixed Travel (--mixed_travel value)
Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
otherwise unconnected logically can be reach using these glitches. To prevent the player from unintentionally
#### Prevent
Rails are added the 3 spots to prevent this tricks. This setting is recommend for those learning crossed dungeon mode to
learn what is dangerous and what is not. No logic seeds ignore this setting.
#### Allow
The rooms are left alone and it is up to the discretion of the player whether to use these tricks or not.
#### Force
The two disjointed sections are forced to be in the same dungeon but never logically required to complete that game.
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)

View File

@@ -1,38 +1,58 @@
# New Features
* Crossed Dungeon generation improvements
* Standard mode generation improvements
* Spoiler lists bosses (multiworld compatible)
* Bombs escape not valid for Crossed Dungeon
* Graph algorithm speed improvement for placements and playthrough
* TT Attic Hint tile should have a crystal switch accessible now
* Updated to v.31.0.5
* Lobby shuffle added as Intensity level 3
* Can now be found in the spoiler
* Known issues:
* If a dungeon is vanilla in ER and Sanc is in that dungeon and the dungeon has an entrance that needs to let link out: is broken.
* e.g. PoD, GT, TR
* Some TR lobbies that need a bomb aren't pre-opened.
* Palettes aren't perfect - may add Sanctuary and Sewer palette back. May add a way to turn off palette "fixing"
* Some ugly colors
* Invisible floors can be see in many palettes
* Animated tiles aren't loaded correctly in lobbies
* If a wallmaster grabs you and the lobby is dark, the lamp doesn't turn on
* --keydropshuffle added (coming to the GUI soon). This add 33 new locations to the game where keys are found under pots
and where enemies drop keys. This includes 32 small key location and the ball and chain guard who normally drop the HC
Big Key.
* Overall location count updated
* Setting mentioned in spoiler
* GT Big Key count / total location count needs to be updated
* --mixed_travel setting added
* Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
otherwise unconnected logically can be reach using these glitches. To prevent the player from unintentionally
* prevent: Rails are added the 3 spots to prevent this tricks. This setting is recommend for those learning
crossed dungeon mode to learn what is dangerous and what is not. No logic seeds ignore this setting.
* allow: The rooms are left alone and it is up to the discretion of the player whether to use these tricks or not.
* force: The two disjointed sections are forced to be in the same dungeon but never logically required to complete that game.
### Experimental features
* Moved BK information and total chest keys per dungeon to keysanity menu. The info there requires compass for all info.
* Map still required for on-hud key counter.
* Added total counter to keysanity the compass/map screen when you have the compass for the dungeon.
* Open "Edge" transitions can now be linked with normal doors
* "Straight" staircases (the ones similar to normal doors) can be linked with both normal doors and edges
* Redesign of Keysanity Menu for Crossed Dungeon - soon to move out of experimental
#### Couple of temporary debug features added:
#### Temporary debug features
* Total item count displays where TFH's goal usually does
* A red square appears in the upper right corner of the hud if the castle gate is closed
* Removed the red square in the upper right corner of the hud if the castle gate is closed
# Bug Fixes
* Fix for Animated Tiles in crossed dungeon
* Stonewall hardlock no longer reachable from certain drops (Sewer Drop, some Skull Woods drops) that were previously possible
* No logic uses less key door logic
* Spoiler log encoding
* Enemizer settings made consistent with website
* Swamp flooded ladders in the basement now requires Flippers
* PoD EG Glitch gets killed on transitions (Only when DR is on)
* Problem with standard logic fixed wanting you to pass through the tapestry backwards to rescue Zelda
* Fixed SRAM corruption issues
* Problem with the dungeons requiring you to take Blind through her attic fixed. (Maiden no longer despawns)
* Hyrule Castle will not be your DW access in various Entrance Shuffles: simple, restricted, dungeonssimple, dungeonsfull
(Also prevents getting stuck in TR opening)
* Beatable only (accessibility: none) no longer fails when there are unplaced items
* 2.0.8-u
* Player sprite disappears after picking up a key drop in keydropshuffle
* Sewers and Hyrule Castle compass problems
* Double count of the Hera Basement Cage item (both overall and compass)
* Unnecessary/inconsistent rug cutoff
* TR Crystal Maze thought you get through backwards without Somaria
* Ensure Thieves Attic Window area can always be reached
* Fixed where HC big key was not counted
* Prior fixes
* Fixed a situation where logic did not account properly for Big Key doors in standard Hyrule Castle
* Fixed a problem ER shuffle generation that did not account for lobbies moving around
* Fixed a problem with camera unlock (GT Mimics and Mire Minibridge)
* Fixed a problem with bad-pseudo layer at PoD map Balcony (unable to hit switch with Bomb)
* Fixed a problem with the Ganon hint when hints are turned off
# Known Issues
* Multiworld = /missing command not working
* Potenial keylocks in multi-entrance dungeons
* Incorrect vanilla keylogic for Mire
* ER - Potential for Skull Woods West to be completely inaccessible in non-beatable logic

View File

@@ -198,18 +198,45 @@ def create_regions(world, player):
create_dw_region(player, 'Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']),
]
def create_dungeon_regions(world, player):
std_flag = world.mode[player] == 'standard'
inv_flag = world.mode[player] == 'inverted'
world.regions += [
create_dungeon_region(player, 'Hyrule Castle Lobby', 'Hyrule Castle', None, ['Hyrule Castle Lobby W', 'Hyrule Castle Lobby E',
'Hyrule Castle Lobby WN', 'Hyrule Castle Lobby North Stairs', 'Hyrule Castle Exit (South)']),
create_dungeon_region(player, 'Hyrule Castle West Lobby', 'Hyrule Castle', None, ['Hyrule Castle West Lobby E', 'Hyrule Castle West Lobby N',
'Hyrule Castle West Lobby EN', 'Hyrule Castle Exit (West)']),
create_dungeon_region(player, 'Hyrule Castle East Lobby', 'Hyrule Castle', None, ['Hyrule Castle East Lobby W', 'Hyrule Castle East Lobby N',
'Hyrule Castle East Lobby NW', 'Hyrule Castle Exit (East)']),
create_dungeon_region(player, 'Hyrule Castle East Hall', 'Hyrule Castle', None, ['Hyrule Castle East Hall W', 'Hyrule Castle East Hall S',
'Hyrule Castle East Hall SW']),
create_dungeon_region(player, 'Sanctuary Placeholder', 'Twilight Zone', None, ['Sanctuary Exit']),
create_dungeon_region(player, 'Hyrule Castle West Placeholder', 'Twilight Zone', None, ['Hyrule Castle Exit (West)']),
create_dungeon_region(player, 'Hyrule Castle South Placeholder', 'Twilight Zone', None, ['Hyrule Castle Exit (South)']),
create_dungeon_region(player, 'Hyrule Castle East Placeholder', 'Twilight Zone', None, ['Hyrule Castle Exit (East)']),
create_dungeon_region(player, 'Eastern Placeholder', 'Twilight Zone', None, ['Eastern Palace Exit']),
create_dungeon_region(player, 'Desert West Placeholder', 'Twilight Zone', None, ['Desert Palace Exit (West)']),
create_dungeon_region(player, 'Desert South Placeholder', 'Twilight Zone', None, ['Desert Palace Exit (South)']),
create_dungeon_region(player, 'Desert East Placeholder', 'Twilight Zone', None, ['Desert Palace Exit (East)']),
create_dungeon_region(player, 'Desert Back Placeholder', 'Twilight Zone', None, ['Desert Palace Exit (North)']),
create_dungeon_region(player, 'Hera Placeholder', 'Twilight Zone', None, ['Tower of Hera Exit']),
create_dungeon_region(player, 'Agahnims Tower Placeholder', 'Twilight Zone', None, [inv_flag and 'Inverted Agahnims Tower Exit' or 'Agahnims Tower Exit']),
create_dungeon_region(player, 'Palace of Darkness Placeholder', 'Twilight Zone', None, ['Palace of Darkness Exit']),
create_dungeon_region(player, 'Swamp Placeholder', 'Twilight Zone', None, ['Swamp Palace Exit']),
create_dungeon_region(player, 'Skull 1 Placeholder', 'Twilight Zone', None, ['Skull Woods First Section Exit']),
create_dungeon_region(player, 'Skull 2 East Placeholder', 'Twilight Zone', None, ['Skull Woods Second Section Exit (East)']),
create_dungeon_region(player, 'Skull 2 West Placeholder', 'Twilight Zone', None, ['Skull Woods Second Section Exit (West)']),
create_dungeon_region(player, 'Skull 3 Placeholder', 'Twilight Zone', None, ['Skull Woods Final Section Exit']),
create_dungeon_region(player, 'Thieves Town Placeholder', 'Twilight Zone', None, ['Thieves Town Exit']),
create_dungeon_region(player, 'Ice Placeholder', 'Twilight Zone', None, ['Ice Palace Exit']),
create_dungeon_region(player, 'Mire Placeholder', 'Twilight Zone', None, ['Misery Mire Exit']),
create_dungeon_region(player, 'Turtle Rock Main Placeholder', 'Twilight Zone', None, ['Turtle Rock Exit (Front)']),
create_dungeon_region(player, 'Turtle Rock Lazy Eyes Placeholder', 'Twilight Zone', None, ['Turtle Rock Ledge Exit (West)']),
create_dungeon_region(player, 'Turtle Rock Chest Placeholder', 'Twilight Zone', None, ['Turtle Rock Ledge Exit (East)']),
create_dungeon_region(player, 'Turtle Rock Eye Bridge Placeholder', 'Twilight Zone', None, ['Turtle Rock Isolated Ledge Exit']),
create_dungeon_region(player, 'Ganons Tower Placeholder', 'Twilight Zone', None, [inv_flag and 'Inverted Ganons Tower Exit' or 'Ganons Tower Exit']),
create_dungeon_region(player, 'Hyrule Castle Lobby', 'Hyrule Castle', None,
['Hyrule Castle Lobby W', 'Hyrule Castle Lobby E', 'Hyrule Castle Lobby WN', 'Hyrule Castle Lobby North Stairs', 'Hyrule Castle Lobby S']),
create_dungeon_region(player, 'Hyrule Castle West Lobby', 'Hyrule Castle', None,
['Hyrule Castle West Lobby E', 'Hyrule Castle West Lobby N', 'Hyrule Castle West Lobby EN', 'Hyrule Castle West Lobby S']),
create_dungeon_region(player, 'Hyrule Castle East Lobby', 'Hyrule Castle', None,
['Hyrule Castle East Lobby W', 'Hyrule Castle East Lobby N', 'Hyrule Castle East Lobby NW', 'Hyrule Castle East Lobby S']),
create_dungeon_region(player, 'Hyrule Castle East Hall', 'Hyrule Castle', None,
['Hyrule Castle East Hall W', 'Hyrule Castle East Hall S', 'Hyrule Castle East Hall SW']),
create_dungeon_region(player, 'Hyrule Castle West Hall', 'Hyrule Castle', None, ['Hyrule Castle West Hall E', 'Hyrule Castle West Hall S']),
create_dungeon_region(player, 'Hyrule Castle Back Hall', 'Hyrule Castle', None, ['Hyrule Castle Back Hall E', 'Hyrule Castle Back Hall W', 'Hyrule Castle Back Hall Down Stairs']),
create_dungeon_region(player, 'Hyrule Castle Throne Room', 'Hyrule Castle', None, ['Hyrule Castle Throne Room Tapestry', 'Hyrule Castle Throne Room South Stairs']),
@@ -225,10 +252,11 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Hyrule Dungeon Armory Boomerang', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Boomerang Guard Key Drop'], ['Hyrule Dungeon Armory Boomerang WS']),
create_dungeon_region(player, 'Hyrule Dungeon Armory North Branch', 'Hyrule Castle', None, ['Hyrule Dungeon Armory Interior Key Door S', 'Hyrule Dungeon Armory Down Stairs']),
create_dungeon_region(player, 'Hyrule Dungeon Staircase', 'Hyrule Castle', None, ['Hyrule Dungeon Staircase Up Stairs', 'Hyrule Dungeon Staircase Down Stairs']),
create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'Hyrule Castle',
['Hyrule Castle - Big Key Drop', 'Hyrule Castle - Zelda\'s Chest'] if not std_flag else
['Hyrule Castle - Big Key Drop', 'Hyrule Castle - Zelda\'s Chest', 'Zelda Pickup'],
['Hyrule Dungeon Cellblock Up Stairs']),
create_dungeon_region(player, 'Hyrule Dungeon Cellblock', 'Hyrule Castle', ['Hyrule Castle - Big Key Drop'], ['Hyrule Dungeon Cellblock Up Stairs', 'Hyrule Dungeon Cellblock Door']),
create_dungeon_region(player, 'Hyrule Dungeon Cell', 'Hyrule Castle',
["Hyrule Castle - Zelda's Chest"] if not std_flag else
["Hyrule Castle - Zelda's Chest", 'Zelda Pickup'],
['Hyrule Dungeon Cell Exit']),
create_dungeon_region(player, 'Sewers Behind Tapestry', 'Hyrule Castle', None, ['Sewers Behind Tapestry S', 'Sewers Behind Tapestry Down Stairs']),
@@ -244,10 +272,10 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Sewers Pull Switch', 'Hyrule Castle', None, ['Sewers Pull Switch N', 'Sewers Pull Switch S']),
create_dungeon_region(player, 'Sanctuary', 'Hyrule Castle',
['Sanctuary'] if not std_flag else ['Sanctuary', 'Zelda Drop Off'],
['Sanctuary Exit', 'Sanctuary N']),
['Sanctuary S', 'Sanctuary N']),
# Eastern Palace
create_dungeon_region(player, 'Eastern Lobby', 'Eastern Palace', None, ['Eastern Lobby N', 'Eastern Palace Exit', 'Eastern Lobby NW', 'Eastern Lobby NE']),
create_dungeon_region(player, 'Eastern Lobby', 'Eastern Palace', None, ['Eastern Lobby N', 'Eastern Lobby S', 'Eastern Lobby NW', 'Eastern Lobby NE']),
create_dungeon_region(player, 'Eastern Lobby Bridge', 'Eastern Palace', None, ['Eastern Lobby Bridge S', 'Eastern Lobby Bridge N']),
create_dungeon_region(player, 'Eastern Lobby Left Ledge', 'Eastern Palace', None, ['Eastern Lobby Left Ledge SW']),
create_dungeon_region(player, 'Eastern Lobby Right Ledge', 'Eastern Palace', None, ['Eastern Lobby Right Ledge SE']),
@@ -279,11 +307,11 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Eastern Boss', 'Eastern Palace', ['Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Boss SE']),
# Desert Palace
create_dungeon_region(player, 'Desert Main Lobby', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Main Lobby N Edge', 'Desert Main Lobby Left Path', 'Desert Main Lobby Right Path']),
create_dungeon_region(player, 'Desert Main Lobby', 'Desert Palace', None, ['Desert Main Lobby S', 'Desert Main Lobby N Edge', 'Desert Main Lobby Left Path', 'Desert Main Lobby Right Path']),
create_dungeon_region(player, 'Desert Left Alcove', 'Desert Palace', None, ['Desert Main Lobby NW Edge', 'Desert Left Alcove Path']),
create_dungeon_region(player, 'Desert Right Alcove', 'Desert Palace', None, ['Desert Main Lobby NE Edge', 'Desert Main Lobby E Edge', 'Desert Right Alcove Path']),
create_dungeon_region(player, 'Desert Dead End', 'Desert Palace', None, ['Desert Dead End Edge']),
create_dungeon_region(player, 'Desert East Lobby', 'Desert Palace', None, ['Desert East Lobby WS', 'Desert Palace Exit (East)']),
create_dungeon_region(player, 'Desert East Lobby', 'Desert Palace', None, ['Desert East Lobby WS', 'Desert East Lobby S']),
create_dungeon_region(player, 'Desert East Wing', 'Desert Palace', None, ['Desert East Wing ES', 'Desert East Wing Key Door EN', 'Desert East Wing W Edge', 'Desert East Wing N Edge']),
create_dungeon_region(player, 'Desert Compass Room', 'Desert Palace', ['Desert Palace - Compass Chest'], ['Desert Compass Key Door WN', 'Desert Compass NW']),
create_dungeon_region(player, 'Desert Cannonball', 'Desert Palace', ['Desert Palace - Big Key Chest'], ['Desert Cannonball S']),
@@ -296,9 +324,9 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Desert Circle of Pots', 'Desert Palace', None, ['Desert Circle of Pots ES', 'Desert Circle of Pots NW']),
create_dungeon_region(player, 'Desert Big Chest Room', 'Desert Palace', ['Desert Palace - Big Chest'], ['Desert Big Chest SW']),
create_dungeon_region(player, 'Desert West Wing', 'Desert Palace', None, ['Desert West Wing N Edge', 'Desert West Wing WS']),
create_dungeon_region(player, 'Desert West Lobby', 'Desert Palace', None, ['Desert West Lobby ES', 'Desert Palace Exit (West)', 'Desert West Lobby NW']),
create_dungeon_region(player, 'Desert West Lobby', 'Desert Palace', None, ['Desert West Lobby ES', 'Desert West S', 'Desert West Lobby NW']),
create_dungeon_region(player, 'Desert Fairy Fountain', 'Desert Palace', None, ['Desert Fairy Fountain SW']),
create_dungeon_region(player, 'Desert Back Lobby', 'Desert Palace', None, ['Desert Palace Exit (North)', 'Desert Back Lobby NW']),
create_dungeon_region(player, 'Desert Back Lobby', 'Desert Palace', None, ['Desert Back Lobby S', 'Desert Back Lobby NW']),
create_dungeon_region(player, 'Desert Tiles 1', 'Desert Palace', ['Desert Palace - Desert Tiles 1 Pot Key'], ['Desert Tiles 1 SW', 'Desert Tiles 1 Up Stairs']),
create_dungeon_region(player, 'Desert Bridge', 'Desert Palace', None, ['Desert Bridge Down Stairs', 'Desert Bridge SW']),
create_dungeon_region(player, 'Desert Four Statues', 'Desert Palace', None, ['Desert Four Statues NW', 'Desert Four Statues ES']),
@@ -308,7 +336,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Desert Boss', 'Desert Palace', ['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Boss SW']),
# Hera
create_dungeon_region(player, 'Hera Lobby', 'Tower of Hera', ['Tower of Hera - Map Chest'], ['Hera Lobby Down Stairs', 'Hera Lobby Key Stairs', 'Hera Lobby Up Stairs', 'Tower of Hera Exit']),
create_dungeon_region(player, 'Hera Lobby', 'Tower of Hera', ['Tower of Hera - Map Chest'], ['Hera Lobby Down Stairs', 'Hera Lobby Key Stairs', 'Hera Lobby Up Stairs', 'Hera Lobby S']),
create_dungeon_region(player, 'Hera Basement Cage', 'Tower of Hera', ['Tower of Hera - Basement Cage'], ['Hera Basement Cage Up Stairs']),
create_dungeon_region(player, 'Hera Tile Room', 'Tower of Hera', None, ['Hera Tile Room Up Stairs', 'Hera Tile Room EN']),
create_dungeon_region(player, 'Hera Tridorm', 'Tower of Hera', None, ['Hera Tridorm WN', 'Hera Tridorm SE']),
@@ -323,7 +351,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Hera Boss', 'Tower of Hera', ['Tower of Hera - Boss', 'Tower of Hera - Prize'], ['Hera Boss Down Stairs', 'Hera Boss Outer Hole', 'Hera Boss Inner Hole']),
# AgaTower
create_dungeon_region(player, 'Tower Lobby', 'Castle Tower', None, ['Tower Lobby NW', inv_flag and 'Inverted Agahnims Tower Exit' or 'Agahnims Tower Exit']),
create_dungeon_region(player, 'Tower Lobby', 'Castle Tower', None, ['Tower Lobby NW', 'Tower Lobby S']),
create_dungeon_region(player, 'Tower Gold Knights', 'Castle Tower', None, ['Tower Gold Knights SW', 'Tower Gold Knights EN']),
create_dungeon_region(player, 'Tower Room 03', 'Castle Tower', ['Castle Tower - Room 03'], ['Tower Room 03 WN', 'Tower Room 03 Up Stairs']),
create_dungeon_region(player, 'Tower Lone Statue', 'Castle Tower', None, ['Tower Lone Statue Down Stairs', 'Tower Lone Statue WN']),
@@ -343,7 +371,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Tower Agahnim 1', 'Castle Tower', ['Agahnim 1'], ['Tower Agahnim 1 SW']),
# pod
create_dungeon_region(player, 'PoD Lobby', 'Palace of Darkness', None, ['PoD Lobby N', 'PoD Lobby NW', 'PoD Lobby NE', 'Palace of Darkness Exit']),
create_dungeon_region(player, 'PoD Lobby', 'Palace of Darkness', None, ['PoD Lobby N', 'PoD Lobby NW', 'PoD Lobby NE', 'PoD Lobby S']),
create_dungeon_region(player, 'PoD Left Cage', 'Palace of Darkness', None, ['PoD Left Cage SW', 'PoD Left Cage Down Stairs']),
create_dungeon_region(player, 'PoD Middle Cage', 'Palace of Darkness', None, ['PoD Middle Cage S', 'PoD Middle Cage SE', 'PoD Middle Cage N', 'PoD Middle Cage Down Stairs']),
create_dungeon_region(player, 'PoD Shooter Room', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['PoD Shooter Room Up Stairs']),
@@ -381,7 +409,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'PoD Boss', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize'], ['PoD Boss SE']),
# swamp
create_dungeon_region(player, 'Swamp Lobby', 'Swamp Palace', None, ['Swamp Palace Exit', 'Swamp Lobby Moat']),
create_dungeon_region(player, 'Swamp Lobby', 'Swamp Palace', None, ['Swamp Lobby S', 'Swamp Lobby Moat']),
create_dungeon_region(player, 'Swamp Entrance', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Entrance Down Stairs', 'Swamp Entrance Moat']),
create_dungeon_region(player, 'Swamp Pot Row', 'Swamp Palace', ['Swamp Palace - Pot Row Pot Key'], ['Swamp Pot Row Up Stairs', 'Swamp Pot Row WN', 'Swamp Pot Row WS']),
create_dungeon_region(player, 'Swamp Map Ledge', 'Swamp Palace', ['Swamp Palace - Map Chest'], ['Swamp Map Ledge EN']),
@@ -430,7 +458,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Swamp Boss', 'Swamp Palace', ['Swamp Palace - Boss', 'Swamp Palace - Prize'], ['Swamp Boss SW']),
# sw
create_dungeon_region(player, 'Skull 1 Lobby', 'Skull Woods', None, ['Skull Woods First Section Exit', 'Skull 1 Lobby WS', 'Skull 1 Lobby ES']),
create_dungeon_region(player, 'Skull 1 Lobby', 'Skull Woods', None, ['Skull 1 Lobby S', 'Skull 1 Lobby WS', 'Skull 1 Lobby ES']),
create_dungeon_region(player, 'Skull Map Room', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Map Room WS', 'Skull Map Room SE']),
create_dungeon_region(player, 'Skull Pot Circle', 'Skull Woods', None, ['Skull Pot Circle WN', 'Skull Pot Circle Star Path']),
create_dungeon_region(player, 'Skull Pull Switch', 'Skull Woods', None, ['Skull Pull Switch EN', 'Skull Pull Switch S']),
@@ -439,14 +467,14 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Skull Pot Prison', 'Skull Woods', ['Skull Woods - Pot Prison'], ['Skull Pot Prison ES', 'Skull Pot Prison SE']),
create_dungeon_region(player, 'Skull Compass Room', 'Skull Woods', ['Skull Woods - Compass Chest'], ['Skull Compass Room NE', 'Skull Compass Room ES', 'Skull Compass Room WS']),
create_dungeon_region(player, 'Skull Left Drop', 'Skull Woods', None, ['Skull Left Drop ES']),
create_dungeon_region(player, 'Skull 2 East Lobby', 'Skull Woods', None, ['Skull 2 East Lobby NW', 'Skull 2 East Lobby WS', 'Skull Woods Second Section Exit (East)']),
create_dungeon_region(player, 'Skull 2 East Lobby', 'Skull Woods', None, ['Skull 2 East Lobby NW', 'Skull 2 East Lobby WS', 'Skull 2 East Lobby SW']),
create_dungeon_region(player, 'Skull Big Key', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Big Key SW', 'Skull Big Key WN']),
create_dungeon_region(player, 'Skull Lone Pot', 'Skull Woods', None, ['Skull Lone Pot EN']),
create_dungeon_region(player, 'Skull Small Hall', 'Skull Woods', None, ['Skull Small Hall ES', 'Skull Small Hall WS']),
create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', None, ['Skull Back Drop Star Path', ]),
create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby NW', 'Skull Woods Second Section Exit (West)']),
create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby NW', 'Skull 2 West Lobby S']),
create_dungeon_region(player, 'Skull X Room', 'Skull Woods', None, ['Skull X Room SW']),
create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby EN', 'Skull Woods Final Section Exit']),
create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby EN', 'Skull 3 Lobby SW']),
create_dungeon_region(player, 'Skull East Bridge', 'Skull Woods', None, ['Skull East Bridge WN', 'Skull East Bridge WS']),
create_dungeon_region(player, 'Skull West Bridge Nook', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull West Bridge Nook ES']),
create_dungeon_region(player, 'Skull Star Pits', 'Skull Woods', None, ['Skull Star Pits SW', 'Skull Star Pits ES']),
@@ -457,7 +485,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Skull Boss', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']),
# tt
create_dungeon_region(player, 'Thieves Lobby', 'Thieves\' Town', ['Thieves\' Town - Map Chest'], ['Thieves Town Exit', 'Thieves Lobby N Edge', 'Thieves Lobby NE Edge', 'Thieves Lobby E']),
create_dungeon_region(player, 'Thieves Lobby', 'Thieves\' Town', ['Thieves\' Town - Map Chest'], ['Thieves Lobby S', 'Thieves Lobby N Edge', 'Thieves Lobby NE Edge', 'Thieves Lobby E']),
create_dungeon_region(player, 'Thieves Ambush', 'Thieves\' Town', ['Thieves\' Town - Ambush Chest'], ['Thieves Ambush S Edge', 'Thieves Ambush SE Edge', 'Thieves Ambush ES Edge', 'Thieves Ambush EN Edge', 'Thieves Ambush E']),
create_dungeon_region(player, 'Thieves Rail Ledge', 'Thieves\' Town', None, ['Thieves Rail Ledge NW', 'Thieves Rail Ledge W', 'Thieves Rail Ledge Drop Down']),
create_dungeon_region(player, 'Thieves BK Corner', 'Thieves\' Town', None, ['Thieves BK Corner WN Edge', 'Thieves BK Corner WS Edge', 'Thieves BK Corner S Edge', 'Thieves BK Corner SW Edge', 'Thieves BK Corner NE']),
@@ -483,14 +511,15 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Thieves Basement Block', 'Thieves\' Town', None, ['Thieves Basement Block Up Stairs', 'Thieves Basement Block WN', 'Thieves Basement Block Path']),
create_dungeon_region(player, 'Thieves Blocked Entry', 'Thieves\' Town', None, ['Thieves Blocked Entry Path', 'Thieves Blocked Entry SW']),
create_dungeon_region(player, 'Thieves Lonely Zazak', 'Thieves\' Town', None, ['Thieves Lonely Zazak WS', 'Thieves Lonely Zazak ES', 'Thieves Lonely Zazak NW']),
create_dungeon_region(player, 'Thieves Blind\'s Cell', 'Thieves\' Town', ['Thieves\' Town - Blind\'s Cell', 'Suspicious Maiden'], ['Thieves Blind\'s Cell WS']),
create_dungeon_region(player, "Thieves Blind's Cell", 'Thieves\' Town', None, ["Thieves Blind's Cell WS", "Thieves Blind's Cell Door"]),
create_dungeon_region(player, "Thieves Blind's Cell Interior", 'Thieves\' Town', ['Thieves\' Town - Blind\'s Cell', 'Suspicious Maiden'], ["Thieves Blind's Cell Exit"]),
create_dungeon_region(player, 'Thieves Conveyor Bridge', 'Thieves\' Town', None, ['Thieves Conveyor Bridge EN', 'Thieves Conveyor Bridge ES', 'Thieves Conveyor Bridge WS', 'Thieves Conveyor Bridge Block Path']),
create_dungeon_region(player, 'Thieves Conveyor Block', 'Thieves\' Town', None, ['Thieves Conveyor Block Path', 'Thieves Conveyor Block WN']),
create_dungeon_region(player, 'Thieves Big Chest Room', 'Thieves\' Town', ['Thieves\' Town - Big Chest'], ['Thieves Big Chest Room ES']),
create_dungeon_region(player, 'Thieves Trap', 'Thieves\' Town', None, ['Thieves Trap EN']),
# ice
create_dungeon_region(player, 'Ice Lobby', 'Ice Palace', None, ['Ice Palace Exit', 'Ice Lobby WS']),
create_dungeon_region(player, 'Ice Lobby', 'Ice Palace', None, ['Ice Lobby SE', 'Ice Lobby WS']),
create_dungeon_region(player, 'Ice Jelly Key', 'Ice Palace', ['Ice Palace - Jelly Key Drop'], ['Ice Jelly Key ES', 'Ice Jelly Key Down Stairs']),
create_dungeon_region(player, 'Ice Floor Switch', 'Ice Palace', None, ['Ice Floor Switch Up Stairs', 'Ice Floor Switch ES']),
create_dungeon_region(player, 'Ice Cross Left', 'Ice Palace', None, ['Ice Cross Left WS', 'Ice Cross Left Push Block']),
@@ -538,7 +567,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Ice Boss', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
# mire
create_dungeon_region(player, 'Mire Lobby', 'Misery Mire', None, ['Misery Mire Exit', 'Mire Lobby Gap']),
create_dungeon_region(player, 'Mire Lobby', 'Misery Mire', None, ['Mire Lobby S', 'Mire Lobby Gap']),
create_dungeon_region(player, 'Mire Post-Gap', 'Misery Mire', None, ['Mire Post-Gap Gap', 'Mire Post-Gap Down Stairs']),
create_dungeon_region(player, 'Mire 2', 'Misery Mire', None, ['Mire 2 Up Stairs', 'Mire 2 NE']),
create_dungeon_region(player, 'Mire Hub', 'Misery Mire', None, ['Mire Hub SE', 'Mire Hub ES', 'Mire Hub E', 'Mire Hub NE', 'Mire Hub WN', 'Mire Hub WS', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier']),
@@ -595,7 +624,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Mire Boss', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize'], ['Mire Boss SW']),
# tr
create_dungeon_region(player, 'TR Main Lobby', 'Turtle Rock', None, ['TR Main Lobby Gap', 'Turtle Rock Exit (Front)']),
create_dungeon_region(player, 'TR Main Lobby', 'Turtle Rock', None, ['TR Main Lobby Gap', 'TR Main Lobby SE']),
create_dungeon_region(player, 'TR Lobby Ledge', 'Turtle Rock', None, ['TR Lobby Ledge NE', 'TR Lobby Ledge Gap']),
create_dungeon_region(player, 'TR Compass Room', 'Turtle Rock', ['Turtle Rock - Compass Chest'], ['TR Compass Room NW']),
create_dungeon_region(player, 'TR Hub', 'Turtle Rock', None, ['TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE']),
@@ -617,8 +646,8 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'TR Dodgers', 'Turtle Rock', None, ['TR Dodgers WN', 'TR Dodgers SE', 'TR Dodgers NE']),
create_dungeon_region(player, 'TR Big View', 'Turtle Rock', None, ['TR Big View WS']),
create_dungeon_region(player, 'TR Big Chest', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['TR Big Chest Gap', 'TR Big Chest NE']),
create_dungeon_region(player, 'TR Big Chest Entrance', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (East)', 'TR Big Chest Entrance Gap']),
create_dungeon_region(player, 'TR Lazy Eyes', 'Turtle Rock', None, ['Turtle Rock Ledge Exit (West)', 'TR Lazy Eyes ES']),
create_dungeon_region(player, 'TR Big Chest Entrance', 'Turtle Rock', None, ['TR Big Chest Entrance SE', 'TR Big Chest Entrance Gap']),
create_dungeon_region(player, 'TR Lazy Eyes', 'Turtle Rock', None, ['TR Lazy Eyes SE', 'TR Lazy Eyes ES']),
create_dungeon_region(player, 'TR Dash Room', 'Turtle Rock', None, ['TR Dash Room SW', 'TR Dash Room ES', 'TR Dash Room NW']),
create_dungeon_region(player, 'TR Tongue Pull', 'Turtle Rock', None, ['TR Tongue Pull WS', 'TR Tongue Pull NE']),
create_dungeon_region(player, 'TR Rupees', 'Turtle Rock', None, ['TR Rupees SE']),
@@ -627,14 +656,14 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'TR Dash Bridge', 'Turtle Rock', None, ['TR Dash Bridge NW', 'TR Dash Bridge SW', 'TR Dash Bridge WS']),
create_dungeon_region(player, 'TR Eye Bridge', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
['Turtle Rock Isolated Ledge Exit', 'TR Eye Bridge NW']),
['TR Eye Bridge SW', 'TR Eye Bridge NW']),
create_dungeon_region(player, 'TR Crystal Maze', 'Turtle Rock', None, ['TR Crystal Maze ES', 'TR Crystal Maze Forwards Path']),
create_dungeon_region(player, 'TR Crystal Maze End', 'Turtle Rock', None, ['TR Crystal Maze Blue Path', 'TR Crystal Maze Cane Path', 'TR Crystal Maze North Stairs']),
create_dungeon_region(player, 'TR Final Abyss', 'Turtle Rock', None, ['TR Final Abyss South Stairs', 'TR Final Abyss NW']),
create_dungeon_region(player, 'TR Boss', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize'], ['TR Boss SW']),
# gt
create_dungeon_region(player, 'GT Lobby', 'Ganon\'s Tower', None, ['GT Lobby Left Down Stairs', 'GT Lobby Up Stairs', 'GT Lobby Right Down Stairs', inv_flag and 'Inverted Ganons Tower Exit' or 'Ganons Tower Exit']),
create_dungeon_region(player, 'GT Lobby', 'Ganon\'s Tower', None, ['GT Lobby Left Down Stairs', 'GT Lobby Up Stairs', 'GT Lobby Right Down Stairs', 'GT Lobby S']),
create_dungeon_region(player, 'GT Bob\'s Torch', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch'], ['GT Torch Up Stairs', 'GT Torch WN', 'GT Torch EN', 'GT Torch SW']),
create_dungeon_region(player, 'GT Hope Room', 'Ganon\'s Tower', ['Ganons Tower - Hope Room - Left', 'Ganons Tower - Hope Room - Right'], ['GT Hope Room Up Stairs', 'GT Hope Room WN', 'GT Hope Room EN']),
create_dungeon_region(player, 'GT Big Chest', 'Ganon\'s Tower', ['Ganons Tower - Big Chest'], ['GT Big Chest NW', 'GT Big Chest SW']),
@@ -653,7 +682,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'GT Hookshot East Platform', 'Ganon\'s Tower', None, ['GT Hookshot EN', 'GT Hookshot East-North Path', 'GT Hookshot East-South Path']),
create_dungeon_region(player, 'GT Hookshot North Platform', 'Ganon\'s Tower', None, ['GT Hookshot NW', 'GT Hookshot North-East Path', 'GT Hookshot North-South Path']),
create_dungeon_region(player, 'GT Hookshot South Platform', 'Ganon\'s Tower', None, ['GT Hookshot ES', 'GT Hookshot South-East Path', 'GT Hookshot South-North Path', 'GT Hookshot Platform Blue Barrier']),
create_dungeon_region(player, 'GT Hookshot South Entry', 'Ganon\'s Tower', None, ['GT Hookshot SW', 'GT Hookshot Entry Blue Barrier']),
create_dungeon_region(player, 'GT Hookshot South Entry', 'Ganon\'s Tower', None, ['GT Hookshot SW', 'GT Hookshot Entry Blue Barrier', 'GT Hookshot Entry Boomerang Path']),
create_dungeon_region(player, 'GT Map Room', 'Ganon\'s Tower', ['Ganons Tower - Map Chest'], ['GT Map Room WS']),
create_dungeon_region(player, 'GT Double Switch Entry', 'Ganon\'s Tower', None, ['GT Double Switch NW', 'GT Double Switch Orange Barrier', 'GT Double Switch Orange Barrier 2']),
create_dungeon_region(player, 'GT Double Switch Switches', 'Ganon\'s Tower', None, ['GT Double Switch Blue Path', 'GT Double Switch Orange Path']),
@@ -709,7 +738,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'GT Staredown', 'Ganon\'s Tower', None, ['GT Staredown WS', 'GT Staredown Up Ladder']),
create_dungeon_region(player, 'GT Falling Torches', 'Ganon\'s Tower', None, ['GT Falling Torches Down Ladder', 'GT Falling Torches NE', 'GT Falling Torches Hole']),
create_dungeon_region(player, 'GT Mini Helmasaur Room', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left',
'Ganons Tower - Mini Helmasaur Room - Right', 'Ganons Tower - Mini Helmasuar Key Drop'], ['GT Mini Helmasaur Room SE', 'GT Mini Helmasaur Room WN']),
'Ganons Tower - Mini Helmasaur Room - Right', 'Ganons Tower - Mini Helmasaur Key Drop'], ['GT Mini Helmasaur Room SE', 'GT Mini Helmasaur Room WN']),
create_dungeon_region(player, 'GT Bomb Conveyor', 'Ganon\'s Tower', None, ['GT Bomb Conveyor EN', 'GT Bomb Conveyor SW']),
create_dungeon_region(player, 'GT Crystal Circles', 'Ganon\'s Tower', ['Ganons Tower - Pre-Moldorm Chest'], ['GT Crystal Circles NW', 'GT Crystal Circles SW']),
create_dungeon_region(player, 'GT Left Moldorm Ledge', 'Ganon\'s Tower', None, ['GT Left Moldorm Ledge Drop Down', 'GT Left Moldorm Ledge NW']),
@@ -780,9 +809,9 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
for location in locations:
if location in key_only_locations:
ko_hint = 'in a pot' if 'Pot' in location else 'with an enemy'
ret.locations.append(Location(player, location, None, False, ko_hint, ret, key_only_locations[location]))
if location in key_drop_data:
ko_hint = key_drop_data[location][2]
ret.locations.append(Location(player, location, None, False, ko_hint, ret, key_drop_data[location][3]))
else:
address, player_address, crystal, hint_text = location_table[location]
ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address))
@@ -831,6 +860,29 @@ def create_shops(world, player):
for index, item in enumerate(inventory):
shop.add_inventory(index, *item)
def adjust_locations(world, player):
if world.keydropshuffle[player]:
for location in key_drop_data.keys():
loc = world.get_location(location, player)
key_item = loc.item
key_item.location = None
loc.forced_item = None
loc.item = None
loc.event = False
loc.address = key_drop_data[location][1]
loc.player_address = key_drop_data[location][0]
item_dungeon = key_item.name.split('(')[1][:-1]
item_dungeon = 'Hyrule Castle' if item_dungeon == 'Escape' else item_dungeon
dungeon = world.get_dungeon(item_dungeon, player)
if key_item.smallkey and not world.retro[player]:
dungeon.small_keys.append(key_item)
elif key_item.bigkey:
dungeon.big_key = key_item
# (type, room_id, shopkeeper, custom, locked, [items])
# item = (item, price, max=0, replacement=None, replacement_price=0)
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
@@ -849,40 +901,40 @@ shop_table = {
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
}
key_only_locations = {
'Hyrule Castle - Map Guard Key Drop': 'Small Key (Escape)',
'Hyrule Castle - Boomerang Guard Key Drop': 'Small Key (Escape)',
'Hyrule Castle - Key Rat Key Drop': 'Small Key (Escape)',
'Hyrule Castle - Big Key Drop': 'Big Key (Escape)',
'Eastern Palace - Dark Square Pot Key': 'Small Key (Eastern Palace)',
'Eastern Palace - Dark Eyegore Key Drop': 'Small Key (Eastern Palace)',
'Desert Palace - Desert Tiles 1 Pot Key': 'Small Key (Desert Palace)',
'Desert Palace - Beamos Hall Pot Key': 'Small Key (Desert Palace)',
'Desert Palace - Desert Tiles 2 Pot Key': 'Small Key (Desert Palace)',
'Castle Tower - Dark Archer Key Drop': 'Small Key (Agahnims Tower)',
'Castle Tower - Circle of Pots Key Drop': 'Small Key (Agahnims Tower)',
'Swamp Palace - Pot Row Pot Key': 'Small Key (Swamp Palace)',
'Swamp Palace - Trench 1 Pot Key': 'Small Key (Swamp Palace)',
'Swamp Palace - Hookshot Pot Key': 'Small Key (Swamp Palace)',
'Swamp Palace - Trench 2 Pot Key': 'Small Key (Swamp Palace)',
'Swamp Palace - Waterway Pot Key': 'Small Key (Swamp Palace)',
'Skull Woods - West Lobby Pot Key': 'Small Key (Skull Woods)',
'Skull Woods - Spike Corner Key Drop': 'Small Key (Skull Woods)',
'Thieves\' Town - Hallway Pot Key': 'Small Key (Thieves Town)',
'Thieves\' Town - Spike Switch Pot Key': 'Small Key (Thieves Town)',
'Ice Palace - Jelly Key Drop': 'Small Key (Ice Palace)',
'Ice Palace - Conveyor Key Drop': 'Small Key (Ice Palace)',
'Ice Palace - Hammer Block Key Drop': 'Small Key (Ice Palace)',
'Ice Palace - Many Pots Pot Key': 'Small Key (Ice Palace)',
'Misery Mire - Spikes Pot Key': 'Small Key (Misery Mire)',
'Misery Mire - Fishbone Pot Key': 'Small Key (Misery Mire)',
'Misery Mire - Conveyor Crystal Key Drop': 'Small Key (Misery Mire)',
'Turtle Rock - Pokey 1 Key Drop': 'Small Key (Turtle Rock)',
'Turtle Rock - Pokey 2 Key Drop': 'Small Key (Turtle Rock)',
'Ganons Tower - Conveyor Cross Pot Key': 'Small Key (Ganons Tower)',
'Ganons Tower - Double Switch Pot Key': 'Small Key (Ganons Tower)',
'Ganons Tower - Conveyor Star Pits Pot Key': 'Small Key (Ganons Tower)',
'Ganons Tower - Mini Helmasuar Key Drop': 'Small Key (Ganons Tower)'
key_drop_data = {
'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037, 'in Hyrule Castle', 'Small Key (Escape)'],
'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034, 'in Hyrule Castle', 'Small Key (Escape)'],
'Hyrule Castle - Key Rat Key Drop': [0x14000c, 0x14000d, 'in Hyrule Castle', 'Small Key (Escape)'],
'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d, 'in Hyrule Castle', 'Big Key (Escape)'],
'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b, 'in Eastern Palace', 'Small Key (Eastern Palace)'],
'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049, 'in Eastern Palace', 'Small Key (Eastern Palace)'],
'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031, 'in Desert Palace', 'Small Key (Desert Palace)'],
'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b, 'in Desert Palace', 'Small Key (Desert Palace)'],
'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028, 'in Desert Palace', 'Small Key (Desert Palace)'],
'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061, 'in Castle Tower', 'Small Key (Agahnims Tower)'],
'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052, 'in Castle Tower', 'Small Key (Agahnims Tower)'],
'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e, 'in Skull Woods', 'Small Key (Skull Woods)'],
'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c, 'near Mothula', 'Small Key (Skull Woods)'],
"Thieves' Town - Hallway Pot Key": [0x14005d, 0x14005e, "in Thieves' Town", 'Small Key (Thieves Town)'],
"Thieves' Town - Spike Switch Pot Key": [0x14004e, 0x14004f, "in Thieves' Town", 'Small Key (Thieves Town)'],
'Ice Palace - Jelly Key Drop': [0x140003, 0x140004, 'in Ice Palace', 'Small Key (Ice Palace)'],
'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022, 'in Ice Palace', 'Small Key (Ice Palace)'],
'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025, 'in Ice Palace', 'Small Key (Ice Palace)'],
'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046, 'in Ice Palace', 'Small Key (Ice Palace)'],
'Misery Mire - Spikes Pot Key': [0x140054, 0x140055 , 'in Misery Mire', 'Small Key (Misery Mire)'],
'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c, 'in forgotten Mire', 'Small Key (Misery Mire)'],
'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064 , 'in Misery Mire', 'Small Key (Misery Mire)'],
'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058, 'in Turtle Rock', 'Small Key (Turtle Rock)'],
'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007, 'in Turtle Rock', 'Small Key (Turtle Rock)'],
'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f, "atop Ganon's Tower", 'Small Key (Ganons Tower)']
}
dungeon_events = [
@@ -1143,3 +1195,8 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}}
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}}

201
Rom.py
View File

@@ -8,8 +8,10 @@ import random
import struct
import sys
import subprocess
import bps.apply
import bps.io
from BaseClasses import CollectionState, ShopType, Region, Location, DoorType
from BaseClasses import CollectionState, ShopType, Region, Location, DoorType, RegionType
from DoorShuffle import compass_data, DROptions, boss_indicator
from Dungeons import dungeon_music_addresses
from Regions import location_table
@@ -22,7 +24,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'b9e578ef0af231041070bd9049a55646'
RANDOMIZERBASEHASH = '16fab813f3cbef58c66a47595a3858ee'
class JsonRom(object):
@@ -112,16 +114,16 @@ class LocalRom(object):
if JAP10HASH != basemd5.hexdigest():
logging.getLogger('').warning('Supplied Base Rom does not match known MD5 for JAP(1.0) release. Will try to patch anyway.')
orig_buffer = self.buffer.copy()
# extend to 2MB
self.buffer.extend(bytearray([0x00] * (0x200000 - len(self.buffer))))
# load randomizer patches
with open(local_path('data/base2current.json'), 'r') as stream:
patches = json.load(stream)
for patch in patches:
if isinstance(patch, dict):
for baseaddress, values in patch.items():
self.write_bytes(int(baseaddress), values)
with open(local_path('data/base2current.bps'), 'rb') as stream:
bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, self.buffer)
self.create_json_patch(orig_buffer)
# verify md5
patchedmd5 = hashlib.md5()
@@ -129,6 +131,29 @@ class LocalRom(object):
if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
def create_json_patch(self, orig_buffer):
# extend to 2MB
orig_buffer.extend(bytearray([0x00] * (len(self.buffer) - len(orig_buffer))))
i = 0
patches = []
while i < len(self.buffer):
if self.buffer[i] == orig_buffer[i]:
i += 1
continue
patch_start = i
patch_contents = []
while self.buffer[i] != orig_buffer[i]:
patch_contents.append(self.buffer[i])
i += 1
patches.append({patch_start: patch_contents})
with open(local_path('data/base2current.json'), 'w') as fp:
json.dump(patches, fp, separators=(',', ':'))
def write_crc(self):
crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF
inv = crc ^ 0xFFFF
@@ -503,7 +528,7 @@ def patch_rom(world, rom, player, team, enemized):
if not location.crystal:
if location.item is not None:
# Keys in their native dungeon should use the orignal item code for keys
# Keys in their native dungeon should use the original item code for keys
if location.parent_region.dungeon:
if location.parent_region.dungeon.is_dungeon_item(location.item):
if location.item.bigkey:
@@ -599,40 +624,51 @@ def patch_rom(world, rom, player, team, enemized):
if world.experimental[player]:
dr_flags |= DROptions.Map_Info
dr_flags |= DROptions.Debug
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
and world.mixed_travel[player] == 'prevent':
dr_flags |= DROptions.Rails
# fix hc big key problems (map and compass too)
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player]:
rom.write_byte(0x151f1, 2)
rom.write_byte(0x15270, 2)
sanctuary = world.get_region('Sanctuary', player)
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
update_compasses(rom, world, player)
# patch doors
if world.doorShuffle[player] == 'crossed':
rom.write_byte(0x138002, 2)
for name, layout in world.key_layout[player].items():
offset = compass_data[name][4]//2
rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops)
rom.write_byte(0x13f02a+offset, layout.max_chests)
if world.retro[player]:
rom.write_byte(0x13f02a+offset, layout.max_chests + layout.max_drops)
else:
rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops) # not currently used
rom.write_byte(0x13f02a+offset, layout.max_chests)
builder = world.dungeon_layouts[player][name]
rom.write_byte(0x13f070+offset, builder.location_cnt % 10)
rom.write_byte(0x13f07e+offset, builder.location_cnt // 10)
bk_status = 1 if builder.bk_required else 0
bk_status = 2 if builder.bk_provided else bk_status
rom.write_byte(0x13f038+offset*2, bk_status)
rom.write_byte(0x151f1, 2)
rom.write_byte(0x15270, 2)
rom.write_byte(0x1597b, 2)
if compass_code_good(rom):
update_compasses(rom, world, player)
else:
logging.getLogger('').warning('Randomizer rom update! Compasses in crossed are borken')
if player in world.sanc_portal.keys():
rom.write_byte(0x159a6, world.sanc_portal[player].ent_offset)
for room in world.rooms:
if room.player == player and room.palette is not None:
rom.write_byte(0x13f200+room.index, room.palette)
if world.doorShuffle[player] == 'basic':
rom.write_byte(0x138002, 1)
for door in world.doors:
if door.dest is not None and door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs,
DoorType.Open, DoorType.StraightStairs]:
rom.write_bytes(door.getAddress(), door.dest.getTarget(door))
for room in world.rooms:
if room.player == player and room.modified:
rom.write_bytes(room.address(), room.rom_data())
for paired_door in world.paired_doors[player]:
rom.write_bytes(paired_door.address_a(world, player), paired_door.rom_data_a(world, player))
rom.write_bytes(paired_door.address_b(world, player), paired_door.rom_data_b(world, player))
if world.doorShuffle[player] != 'vanilla':
for builder in world.dungeon_layouts[player].values():
if builder.pre_open_stonewall:
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW':
@@ -640,7 +676,7 @@ def patch_rom(world, rom, player, team, enemized):
for name, pair in boss_indicator.items():
dungeon_id, boss_door = pair
opposite_door = world.get_door(boss_door, player).dest
if opposite_door.roomIndex > -1:
if opposite_door and opposite_door.roomIndex > -1:
dungeon_name = opposite_door.entrance.parent_region.dungeon.name
dungeon_id = boss_indicator[dungeon_name][0]
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
@@ -650,12 +686,47 @@ def patch_rom(world, rom, player, team, enemized):
if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted':
rom.write_byte(0x138006, 1)
for portal in world.dungeon_portals[player]:
if not portal.default:
offset = portal.ent_offset
rom.write_byte(0x14577 + offset*2, portal.current_room())
rom.write_bytes(0x14681 + offset*8, portal.relative_coords())
rom.write_bytes(0x14aa9 + offset*2, portal.scroll_x())
rom.write_bytes(0x14bb3 + offset*2, portal.scroll_y())
rom.write_bytes(0x14cbd + offset*2, portal.link_y())
rom.write_bytes(0x14dc7 + offset*2, portal.link_x())
rom.write_bytes(0x14fdb + offset*2, portal.camera_x())
rom.write_byte(0x152f9 + offset, portal.bg_setting())
rom.write_byte(0x1537e + offset, portal.hv_scroll())
rom.write_byte(0x15403 + offset, portal.scroll_quad())
rom.write_byte(0x15aee + portal.exit_offset, portal.current_room())
if portal.boss_exit_idx > -1:
rom.write_byte(0x7939 + portal.boss_exit_idx, portal.current_room())
# fix skull woods exit, if not fixed during exit patching
if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla':
write_int16(rom, 0x15DB5 + 2 * exit_ids['Skull Woods Final Section Exit'][1], 0x00F8)
write_custom_shops(rom, world, player)
def credits_digit(num):
# top: $54 is 1, 55 2, etc , so 57=4, 5C=9
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
return 0x53+num, 0x79+num
# collection rate address: 238C37
if world.keydropshuffle[player]:
rom.write_byte(0x140000, 1)
mid_top, mid_bot = credits_digit(4)
last_top, last_bot = credits_digit(9)
# top half
rom.write_byte(0x118C53, mid_top)
rom.write_byte(0x118C54, last_top)
# bottom half
rom.write_byte(0x118C71, mid_bot)
rom.write_byte(0x118C72, last_bot)
# patch medallion requirements
if world.required_medallions[player][0] == 'Bombos':
rom.write_byte(0x180022, 0x00) # requirement
@@ -709,7 +780,7 @@ def patch_rom(world, rom, player, team, enemized):
TRIFORCE_PIECE = ItemFactory('Triforce Piece', player).code
GREEN_CLOCK = ItemFactory('Green Clock', player).code
rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on
rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on
# handle difficulty_adjustments
if world.difficulty_adjustments[player] == 'hard':
@@ -1298,7 +1369,13 @@ def patch_rom(world, rom, player, team, enemized):
# set correct flag for hera basement item
hera_basement = world.get_location('Tower of Hera - Basement Cage', player)
if hera_basement.item is not None and hera_basement.item.name == 'Small Key (Tower of Hera)' and hera_basement.item.player == player:
is_small_key_this_dungeon = False
if hera_basement.item is not None and hera_basement.item.smallkey:
item_dungeon = hera_basement.item.name.split('(')[1][:-1]
if item_dungeon == 'Escape':
item_dungeon = 'Hyrule Castle'
is_small_key_this_dungeon = hera_basement.parent_region.dungeon.name == item_dungeon
if is_small_key_this_dungeon:
rom.write_byte(0x4E3BB, 0xE4)
else:
rom.write_byte(0x4E3BB, 0xEB)
@@ -1313,6 +1390,11 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0xFED31, 0x2A) # preopen bombable exit
rom.write_byte(0xFEE41, 0x2A) # preopen bombable exit
if world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]:
for room in world.rooms:
if room.player == player and room.modified:
rom.write_bytes(room.address(), room.rom_data())
write_strings(rom, world, player, team)
rom.write_byte(0x18636C, 1 if world.remote_items[player] else 0)
@@ -1631,7 +1713,10 @@ def write_strings(rom, world, player, team):
if ped_hint:
hint = dest.pedestal_hint_text if dest.pedestal_hint_text else "unknown item"
else:
hint = dest.hint_text if dest.hint_text else "something"
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
hint = dest.dungeon.name
else:
hint = dest.hint_text if dest.hint_text else "something"
if dest.player != player:
if ped_hint:
hint += f" for {world.player_names[dest.player][team]}!"
@@ -2139,66 +2224,20 @@ def patch_shuffled_dark_sanc(world, rom, player):
rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00])
# 24B118 and 20BB32
compass_r_addr = 0x123118 # a9 90 24 8f 9a c7 7e
compass_w_addr = 0x103b32 # e2 20 ad 0c 04 c9 00 d0
def compass_code_good(rom):
if isinstance(rom, LocalRom):
# a990248f9ac77e
if rom.buffer[compass_r_addr] != 0xa9 or rom.buffer[compass_r_addr+1] != 0x90 or rom.buffer[compass_r_addr+2] != 0x24:
return False
if rom.buffer[compass_r_addr+3] != 0x8f or rom.buffer[compass_r_addr+4] != 0x9a or rom.buffer[compass_r_addr+5] != 0xc7:
return False
if rom.buffer[compass_w_addr] != 0xe2 or rom.buffer[compass_w_addr+1] != 0x20 or rom.buffer[compass_w_addr+2] != 0xad:
return False
if rom.buffer[compass_w_addr+3] != 0x0c or rom.buffer[compass_w_addr+4] != 0x04 or rom.buffer[compass_w_addr+5] != 0xc9:
return False
return True
def update_compasses(rom, world, player):
layouts = world.dungeon_layouts[player]
# cmp XX : bne escape
# cpy 32 : bne escape
# brl .itemCounts
# c9 02 d0 eastern
# nop #2
new_code = [0x07, 0xC0, 0x32, 0xD0, 0x03, 0x82, 0xB9, 0x01, 0xC9, 0x02, 0xD0, 0x10, 0xEA, 0xEA]
rom.write_bytes(compass_w_addr + 8, new_code)
# 05 06 07 08
# C9 00 D0 02 80 04 C9 02 D0 15 C0 32 D0 03 82 B3 01
# C9 XX D0 07 C0 32 D0 03 82 B9 01 C9 02 D0 10 EA EA
provided_dungeon = False
for name, builder in layouts.items():
digit_offset, sram_byte, write_offset, jmp_nop_flag, dungeon_id = compass_data[name]
digit1 = builder.location_cnt // 10
digit2 = builder.location_cnt % 10
rom.write_byte(compass_r_addr+digit_offset, 0x90+digit1)
rom.write_byte(compass_r_addr+digit_offset+7, 0x90+digit2)
# read compass count code
start_address = compass_r_addr+digit_offset+15
# -0x59 relative to compass_r_addr, +8000 because rom -120000 to get rid of high byte
jmp_address = compass_r_addr-0x118059
# lda $7ef4(sb); jmp $jmp_address
rom.write_bytes(start_address, [0xaf, sram_byte, 0xf4, 0x7e, 0x4c, jmp_address % 0x100, jmp_address // 0x100])
# write compass count code
write_address = compass_w_addr+write_offset
# 0x186 relative to compass_r_addr, +8000 because rom -100000 to get rid of high byte
jmp_address = compass_w_addr-0xf7e7a
# lda $7ef4(sb); inc; sta $7ef4(sb)
rom.write_bytes(write_address, [0xaf, sram_byte, 0xf4, 0x7e, 0x1a, 0x8f, sram_byte, 0xf4, 0x7e])
if jmp_nop_flag == 0:
rom.write_bytes(write_address+9, [0x4c, jmp_address % 0x100, jmp_address // 0x100]) # jmp $jmp_address
else:
for i in range(0, jmp_nop_flag):
rom.write_byte(write_address+9+i, 0xea) # nop
dungeon_id = compass_data[name][4]
rom.write_byte(0x187000 + dungeon_id//2, builder.location_cnt)
if builder.bk_provided:
rom.write_byte(compass_w_addr+6, dungeon_id)
if provided_dungeon:
logging.getLogger('').warning('Multiple dungeons have forced BKs! Compass code might need updating?')
rom.write_byte(0x186FFF, dungeon_id)
provided_dungeon = True
if not provided_dungeon:
rom.write_byte(0x186FFF, 0xff)
InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main',

View File

@@ -9,7 +9,9 @@ def create_rooms(world, player):
# Room(player, 0x03, 0x509cf).door(Position.SouthW, DoorKind.CaveEntrance),
Room(player, 0x04, 0xfe25c).door(Position.NorthW, DoorKind.StairKey2).door(Position.InteriorW, DoorKind.Dashable).door(Position.InteriorS, DoorKind.Dashable).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.Normal),
Room(player, 0x06, 0xfa192).door(Position.SouthW, DoorKind.Trap),
Room(player, 0x07, None),
# Room(player, 0x08, 0x5064f).door(Position.InteriorS2, DoorKind.CaveEntranceLow08).door(Position.SouthE, DoorKind.CaveEntrance).door(Position.SouthW2, DoorKind.NormalLow2).door(Position.SouthW2, DoorKind.ToggleFlag),
Room(player, 0x09, None),
Room(player, 0x0a, 0xfa734).door(Position.North, DoorKind.StairKey),
Room(player, 0x0b, 0xfabf0).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorN, DoorKind.SmallKey),
Room(player, 0x0c, 0xfef53).door(Position.South, DoorKind.DungeonEntrance),
@@ -23,6 +25,7 @@ def create_rooms(world, player):
Room(player, 0x14, 0xfe464).door(Position.SouthE, DoorKind.SmallKey).door(Position.WestS, DoorKind.SmallKey).door(Position.NorthW, DoorKind.Normal).door(Position.WestN, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.EastN, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x15, 0xfe63e).door(Position.WestS, DoorKind.Trap).door(Position.WestN, DoorKind.Normal),
Room(player, 0x16, 0xfa150).door(Position.InteriorV, DoorKind.Bombable).door(Position.InteriorW, DoorKind.SmallKey).door(Position.InteriorE, DoorKind.Normal).door(Position.NorthW, DoorKind.Normal),
Room(player, 0x17, None),
# Room(player, 0x18, 0x506e5).door(Position.NorthW2, DoorKind.NormalLow).door(Position.NorthW2, DoorKind.ToggleFlag),
Room(player, 0x19, 0xfacc6).door(Position.East, DoorKind.Bombable).door(Position.EastN, DoorKind.SmallKey),
Room(player, 0x1a, 0xfa670).door(Position.InteriorE, DoorKind.SmallKey).door(Position.WestN, DoorKind.SmallKey).door(Position.West, DoorKind.Bombable).door(Position.SouthW, DoorKind.SmallKey).door(Position.InteriorN, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
@@ -38,6 +41,7 @@ def create_rooms(world, player):
Room(player, 0x23, 0xfed30).door(Position.SouthE, DoorKind.BombableEntrance).door(Position.EastS, DoorKind.Normal),
Room(player, 0x24, 0xfe6ee).door(Position.NorthE, DoorKind.BigKey).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorW, DoorKind.Trap2).door(Position.InteriorE, DoorKind.Trap2).door(Position.SouthE, DoorKind.DungeonEntrance).door(Position.NorthW, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x26, 0xf9cbb).door(Position.South, DoorKind.SmallKey).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorE, DoorKind.TrapTriggerable).door(Position.InteriorN, DoorKind.Normal),
Room(player, 0x27, None),
Room(player, 0x28, 0xf92a8).door(Position.NorthW, DoorKind.StairKey2).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0x2a, 0xfa594).door(Position.NorthE, DoorKind.Trap).door(Position.NorthW, DoorKind.SmallKey).door(Position.EastS, DoorKind.Bombable).door(Position.East2, DoorKind.NormalLow).door(Position.SouthW, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x2b, 0xfaaa7).door(Position.InteriorS, DoorKind.Bombable).door(Position.WestS, DoorKind.Bombable).door(Position.NorthW, DoorKind.Trap).door(Position.West2, DoorKind.NormalLow),
@@ -66,6 +70,7 @@ def create_rooms(world, player):
Room(player, 0x40, 0xf8eea).door(Position.InteriorS2, DoorKind.NormalLow2),
Room(player, 0x41, 0x50f15).door(Position.South, DoorKind.Trap),
Room(player, 0x42, None),
Room(player, 0x43, 0xf87f8).door(Position.NorthW, DoorKind.BigKey).door(Position.InteriorE, DoorKind.SmallKey).door(Position.SouthE, DoorKind.SmallKey),
Room(player, 0x44, 0xfdbcd).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorS, DoorKind.SmallKey).door(Position.EastN, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x45, 0xfdcae).door(Position.WestN, DoorKind.Trap).door(Position.InteriorW, DoorKind.Normal).door(Position.WestS, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
@@ -82,6 +87,7 @@ def create_rooms(world, player):
Room(player, 0x51, 0x51029).door(Position.North, DoorKind.Normal).door(Position.North, DoorKind.DungeonChanger),
Room(player, 0x52, 0x51230).door(Position.WestN2, DoorKind.Warp).door(Position.SouthW2, DoorKind.NormalLow2).door(Position.South, DoorKind.Normal),
Room(player, 0x53, 0xf88ad).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap2).door(Position.NorthE, DoorKind.SmallKey),
Room(player, 0x54, None),
# Room(player, 0x55, 0x50166).door(Position.InteriorW2, DoorKind.NormalLow).door(Position.SouthW, DoorKind.Normal).door(Position.SouthW, DoorKind.IncognitoEntrance),
Room(player, 0x56, 0xfbb4e).door(Position.InteriorW, DoorKind.SmallKey).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal).door(Position.EastS, DoorKind.Normal),
Room(player, 0x57, 0xfbbd2).door(Position.InteriorN, DoorKind.Bombable).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.EastS, DoorKind.SmallKey).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.WestS, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
@@ -109,6 +115,7 @@ def create_rooms(world, player):
Room(player, 0x6d, 0xffa4e).door(Position.NorthW, DoorKind.Trap).door(Position.InteriorW, DoorKind.Trap).door(Position.WestS, DoorKind.Trap),
Room(player, 0x6e, 0xfc74b).door(Position.NorthE, DoorKind.Trap),
Room(player, 0x70, None),
Room(player, 0x71, 0x52341).door(Position.InteriorW, DoorKind.SmallKey).door(Position.SouthW2, DoorKind.TrapTriggerableLow).door(Position.InteriorS2, DoorKind.TrapTriggerableLow),
Room(player, 0x72, 0x51fda).door(Position.InteriorV, DoorKind.SmallKey),
Room(player, 0x73, 0xf8972).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorE, DoorKind.Normal),
@@ -122,11 +129,14 @@ def create_rooms(world, player):
Room(player, 0x7e, 0xfc7c6).door(Position.SouthE, DoorKind.SmallKey).door(Position.InteriorS, DoorKind.TrapTriggerable).door(Position.EastN, DoorKind.Normal),
Room(player, 0x7f, 0xfc827).door(Position.WestN, DoorKind.Trap).door(Position.InteriorW, DoorKind.Normal),
Room(player, 0x80, None),
Room(player, 0x81, 0x5224b).door(Position.NorthW2, DoorKind.NormalLow2),
Room(player, 0x82, None),
Room(player, 0x83, 0xf8bba).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthW, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x84, 0xf8cb7).door(Position.South, DoorKind.DungeonEntrance),
Room(player, 0x85, 0xf8d7d).door(Position.NorthE, DoorKind.Trap).door(Position.InteriorN, DoorKind.SmallKey).door(Position.SouthE, DoorKind.DungeonEntrance).door(Position.InteriorS, DoorKind.Normal),
Room(player, 0x87, 0xfd1b7).door(Position.InteriorN, DoorKind.Trap2).door(Position.InteriorE, DoorKind.Normal),
Room(player, 0x89, None),
Room(player, 0x8b, 0xff33f).door(Position.InteriorN, DoorKind.TrapTriggerable).door(Position.InteriorS, DoorKind.SmallKey).door(Position.EastN, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.NorthW, DoorKind.Normal),
Room(player, 0x8c, 0xff3ef).door(Position.EastN, DoorKind.Trap).door(Position.InteriorW, DoorKind.Trap2).door(Position.InteriorN, DoorKind.SmallKey).door(Position.WestN, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal).door(Position.SouthE, DoorKind.Normal),
Room(player, 0x8d, 0xff4e0).door(Position.SouthE, DoorKind.Trap).door(Position.InteriorN, DoorKind.SmallKey).door(Position.WestN, DoorKind.Normal).door(Position.NorthE, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal),
@@ -153,6 +163,7 @@ def create_rooms(world, player):
Room(player, 0xa3, 0xfb667).door(Position.West, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal),
Room(player, 0xa4, 0xfe741).door(Position.SouthW, DoorKind.Trap),
Room(player, 0xa5, 0xffb7f).door(Position.NorthE, DoorKind.Trap).door(Position.InteriorE, DoorKind.Trap2).door(Position.InteriorW, DoorKind.Trap2),
Room(player, 0xa6, None),
Room(player, 0xa8, 0x51887).door(Position.InteriorS, DoorKind.Trap2).door(Position.InteriorW, DoorKind.TrapTriggerable).door(Position.SouthE, DoorKind.SmallKey).door(Position.InteriorN2, DoorKind.NormalLow2).door(Position.EastN2, DoorKind.NormalLow2).door(Position.East, DoorKind.Normal),
Room(player, 0xa9, 0x519c9).door(Position.West, DoorKind.Trap).door(Position.East, DoorKind.Trap).door(Position.North, DoorKind.BigKey).door(Position.WestN2, DoorKind.NormalLow2).door(Position.EastN2, DoorKind.NormalLow2).door(Position.South, DoorKind.Normal),
Room(player, 0xaa, 0x51b29).door(Position.InteriorE, DoorKind.Trap2).door(Position.WestN2, DoorKind.NormalLow2).door(Position.InteriorN, DoorKind.Normal).door(Position.InteriorS, DoorKind.Normal).door(Position.West, DoorKind.Normal).door(Position.SouthW, DoorKind.Normal),
@@ -250,6 +261,7 @@ class Room(object):
self.doorListAddress = address
self.doorList = []
self.modified = False
self.palette = None
def kind(self, door):
return self.doorList[door.doorListPos][1]

View File

@@ -2,7 +2,6 @@ import logging
from collections import deque
from BaseClasses import CollectionState, RegionType, DoorType, Entrance
from Regions import key_only_locations
from RoomData import DoorKind
@@ -42,7 +41,7 @@ def set_rules(world, player):
elif world.goal[player] == 'ganon':
# require aga2 to beat ganon
add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
if world.mode[player] != 'inverted':
set_big_bomb_rules(world, player)
else:
@@ -124,13 +123,12 @@ def global_rules(world, player):
set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player))
set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player))
set_rule(world.get_location('Spike Cave', player), lambda state:
state.has('Hammer', player) and state.can_lift_rocks(player) and
((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
(state.has('Cane of Byrna', player) and
(state.can_extend_magic(player, 12, True) or
(state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4))))))
state.has('Hammer', player) and state.can_lift_rocks(player) and
((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
(state.has('Cane of Byrna', player) and
(state.can_extend_magic(player, 12, True) or
(state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4))))))
)
set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player))
@@ -184,6 +182,7 @@ def global_rules(world, player):
set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize', player))
set_rule(world.get_entrance('Swamp Lobby Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
set_rule(world.get_entrance('Swamp Entrance Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
set_rule(world.get_entrance('Swamp Trench 1 Approach Dry', player), lambda state: not state.has('Trench 1 Filled', player))
set_rule(world.get_entrance('Swamp Trench 1 Key Ledge Dry', player), lambda state: not state.has('Trench 1 Filled', player))
set_rule(world.get_entrance('Swamp Trench 1 Departure Dry', player), lambda state: not state.has('Trench 1 Filled', player))
@@ -298,6 +297,7 @@ def global_rules(world, player):
set_rule(world.get_entrance('GT Hookshot East-South Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
set_rule(world.get_entrance('GT Hookshot North-East Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
set_rule(world.get_entrance('GT Hookshot North-South Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
set_rule(world.get_entrance('GT Hookshot Entry Boomerang Path', player), lambda state: state.has('Blue Boomerang', player) or state.has('Red Boomerang', player))
set_rule(world.get_entrance('GT Firesnake Room Hook Path', player), lambda state: state.has('Hookshot', player))
# I am tempted to stick an invincibility rule for getting across falling bridge
set_rule(world.get_entrance('GT Ice Armos NE', player), lambda state: world.get_region('GT Ice Armos', player).dungeon.bosses['bottom'].can_defeat(state))
@@ -591,7 +591,7 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player))
set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player))
set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player))
set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player))
set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_sword(player) and state.has_misery_mire_medallion(player)) # sword required to cast magic (!)
set_rule(world.get_entrance('Hookshot Cave', player), lambda state: state.can_lift_rocks(player))
@@ -606,7 +606,7 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)', player), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling
set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_sword(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!)
# new inverted spots
set_rule(world.get_entrance('Post Aga Teleporter', player), lambda state: state.has('Beat Agahnim 1', player))
set_rule(world.get_entrance('Mire Mirror Spot', player), lambda state: state.has_Mirror(player))
@@ -624,7 +624,7 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Graveyard Cave Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Bomb Hut Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Skull Woods Mirror Spot', player), lambda state: state.has_Mirror(player))
# inverted flute spots
set_rule(world.get_entrance('DDM Flute', player), lambda state: state.can_flute(player))
@@ -637,7 +637,7 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('EDDM Flute', player), lambda state: state.can_flute(player))
set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.can_flute(player))
set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.can_flute(player))
set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player])
set_rule(world.get_entrance('Inverted Ganons Tower', player), lambda state: False) # This is a safety for the TR function below to not require GT entrance in its key logic.
@@ -755,8 +755,8 @@ def no_glitches_rules(world, player):
def open_rules(world, player):
# softlock protection as you can reach the sewers small key door with a guard drop key
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_key('Small Key (Escape)', player))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_key('Small Key (Escape)', player))
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has_sm_key('Small Key (Escape)', player))
def swordless_rules(world, player):
@@ -774,7 +774,7 @@ def swordless_rules(world, player):
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_Pearl(player) and state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has_Pearl(player) and state.has_misery_mire_medallion(player)) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player) and state.has_Mirror(player))
set_rule(world.get_location('Bombos Tablet', player), lambda state: state.has('Book of Mudora', player) and state.has('Hammer', player) and state.has_Mirror(player))
else:
# only need ddm access for aga tower in inverted
set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has_turtle_rock_medallion(player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!)
@@ -902,7 +902,9 @@ def find_rules_for_zelda_delivery(world, player):
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:
valid_region = connect and connect not in visited and\
(connect.type == RegionType.Dungeon or connect.name == 'Hyrule Castle Ledge')
if valid_region:
rule = ext.access_rule
rule_list = list(path_rules)
next_path = list(path)
@@ -1260,7 +1262,7 @@ def set_inverted_big_bomb_rules(world, player):
LW_bush_entrances = ['Bush Covered House',
'Light World Bomb Hut',
'Graveyard Cave']
set_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.can_reach('East Dark World', 'Region', player) and state.can_reach('Inverted Big Bomb Shop', 'Region', player) and state.has('Crystal 5', player) and state.has('Crystal 6', player))
# crossing peg bridge starting from the southern dark world
@@ -1528,10 +1530,10 @@ bunny_impassible_doors = {
'Ice Backwards Room Hole', 'Ice Switch Room SE', 'Ice Antechamber NE', 'Ice Antechamber Hole', 'Mire Lobby Gap',
'Mire Post-Gap Gap', 'Mire 2 NE', 'Mire Hub Upper Blue Barrier', 'Mire Hub Lower Blue Barrier',
'Mire Hub Right Blue Barrier', 'Mire Hub Top Blue Barrier', 'Mire Hub Switch Blue Barrier N',
'Mire Hub Switch Blue Barrier S', 'Mire Falling Bridge WN',
'Mire Map Spike Side Blue Barrier', 'Mire Map Spot Blue Barrier', 'Mire Crystal Dead End Left Barrier',
'Mire Crystal Dead End Right Barrier', 'Mire Cross ES', 'Mire Hidden Shooters Block Path S',
'Mire Hidden Shooters Block Path N', 'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
'Mire Hub Switch Blue Barrier S', 'Mire Falling Bridge WN', 'Mire Map Spike Side Blue Barrier',
'Mire Map Spot Blue Barrier', 'Mire Crystal Dead End Left Barrier', 'Mire Crystal Dead End Right Barrier',
'Mire Cross ES', 'Mire Hidden Shooters Block Path S', 'Mire Hidden Shooters Block Path N',
'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
'Mire South Fish Blue Barrier', 'Mire Tile Room NW', 'Mire Compass Blue Barrier', 'Mire Attic Hint Hole',
'Mire Dark Shooters SW', 'Mire Crystal Mid Blue Barrier', 'Mire Crystal Left Blue Barrier', 'TR Main Lobby Gap',
'TR Lobby Ledge Gap', 'TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE', 'TR Torches NW',
@@ -1543,14 +1545,15 @@ bunny_impassible_doors = {
'GT Crystal Conveyor NE', 'GT Crystal Conveyor WN', 'GT Conveyor Cross EN', 'GT Conveyor Cross WN',
'GT Hookshot East-North Path', 'GT Hookshot East-South Path', 'GT Hookshot North-East Path',
'GT Hookshot North-South Path', 'GT Hookshot South-East Path', 'GT Hookshot South-North Path',
'GT Hookshot Platform Blue Barrier', 'GT Hookshot Entry Blue Barrier', 'GT Double Switch Blue Path',
'GT Double Switch Key Blue Path', 'GT Double Switch Blue Barrier', 'GT Double Switch Transition Blue',
'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Ice Armos NE', 'GT Ice Armos WS',
'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS', 'GT Mimics 2 NE',
'GT Hidden Spikes EN', 'GT Cannonball Bridge SE', 'GT Gauntlet 1 WN', 'GT Gauntlet 2 EN', 'GT Gauntlet 2 SW',
'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW', 'GT Gauntlet 5 NW',
'GT Gauntlet 5 WS', 'GT Lanmolas 2 ES', 'GT Lanmolas 2 NW', 'GT Wizzrobes 1 SW', 'GT Wizzrobes 2 SE',
'GT Wizzrobes 2 NE', 'GT Torch Cross ES', 'GT Falling Torches NE', 'GT Moldorm Gap', 'GT Validation Block Path'
'GT Hookshot Platform Blue Barrier', 'GT Hookshot Entry Blue Barrier', 'GT Hookshot Entry Boomerang Path',
'GT Double Switch Blue Path', 'GT Double Switch Key Blue Path', 'GT Double Switch Blue Barrier',
'GT Double Switch Transition Blue', 'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS',
'GT Ice Armos NE', 'GT Ice Armos WS', 'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS',
'GT Mimics 2 NE', 'GT Hidden Spikes EN', 'GT Cannonball Bridge SE', 'GT Gauntlet 1 WN', 'GT Gauntlet 2 EN',
'GT Gauntlet 2 SW', 'GT Gauntlet 3 NW', 'GT Gauntlet 3 SW', 'GT Gauntlet 4 NW', 'GT Gauntlet 4 SW',
'GT Gauntlet 5 NW', 'GT Gauntlet 5 WS', 'GT Lanmolas 2 ES', 'GT Lanmolas 2 NW', 'GT Wizzrobes 1 SW',
'GT Wizzrobes 2 SE', 'GT Wizzrobes 2 NE', 'GT Torch Cross ES', 'GT Falling Torches NE', 'GT Moldorm Gap',
'GT Validation Block Path'
}
@@ -1559,11 +1562,12 @@ def add_key_logic_rules(world, player):
for d_name, d_logic in key_logic.items():
for door_name, keys in d_logic.door_rules.items():
spot = world.get_entrance(door_name, player)
add_rule(spot, create_advanced_key_rule(d_logic, player, keys))
if not world.retro[player] or world.mode[player] != 'standard' or not retro_in_hc(spot):
add_rule(spot, create_advanced_key_rule(d_logic, player, keys))
if keys.opposite:
add_rule(spot, create_advanced_key_rule(d_logic, player, keys.opposite), 'or')
for location in d_logic.bk_restricted:
if location.name not in key_only_locations.keys():
if not location.forced_item:
forbid_item(location, d_logic.bk_name, player)
for location in d_logic.sm_restricted:
forbid_item(location, d_logic.small_key_name, player)
@@ -1573,28 +1577,32 @@ def add_key_logic_rules(world, player):
add_rule(world.get_location(chest.name, player), create_rule(d_logic.bk_name, player))
def retro_in_hc(spot):
return spot.parent_region.dungeon.name == 'Hyrule Castle' if spot.parent_region.dungeon else False
def create_rule(item_name, player):
return lambda state: state.has(item_name, player)
def create_key_rule(small_key_name, player, keys):
return lambda state: state.has_key(small_key_name, player, keys)
return lambda state: state.has_sm_key(small_key_name, player, keys)
def create_key_rule_allow_small(small_key_name, player, keys, location):
loc = location.name
return lambda state: state.has_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1))
return lambda state: state.has_sm_key(small_key_name, player, keys) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1))
def create_key_rule_bk_exception(small_key_name, big_key_name, player, keys, bk_keys, bk_locs):
chest_names = [x.name for x in bk_locs]
return lambda state: (state.has_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys))
def create_key_rule_bk_exception_or_allow(small_key_name, big_key_name, player, keys, location, bk_keys, bk_locs):
loc = location.name
chest_names = [x.name for x in bk_locs]
return lambda state: (state.has_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_key(small_key_name, player, bk_keys))
return lambda state: (state.has_sm_key(small_key_name, player, keys) and not item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names)))) or (item_name(state, loc, player) in [(small_key_name, player)] and state.has_sm_key(small_key_name, player, keys-1)) or (item_in_locations(state, big_key_name, player, zip(chest_names, [player] * len(chest_names))) and state.has_sm_key(small_key_name, player, bk_keys))
def create_advanced_key_rule(key_logic, player, rule):

View File

@@ -20,7 +20,10 @@ normal_offset_table = {
0xc1: 0x81, 0xc2: 0x82, 0xc3: 0x83, 0xc4: 0x84, 0xc5: 0x85, 0xc6: 0x86, 0xc7: 0x87, 0xc8: 0x88,
0xc9: 0x89, 0xcb: 0x8A, 0xcc: 0x8B, 0xce: 0x8C, 0xd1: 0x8D, 0xd2: 0x8E, 0xd5: 0x8F, 0xd6: 0x90,
0xd8: 0x91, 0xd9: 0x92, 0xda: 0x93, 0xdb: 0x94, 0xdc: 0x95,
0x40: 0x96, 0x42: 0x97 # newcomers for str stairs
0x40: 0x96, 0x42: 0x97, # newcomers for str stairs
# newcomers for entrances
0x28: 0x98, 0x0e: 0x99, 0x0c: 0x9a, 0x98: 0x9b, 0x83: 0x9c, 0x84: 0x9d, 0x77: 0x9e, 0xe0: 0x9f,
0x63: 0xa0
}
@@ -84,3 +87,40 @@ divisor_lookup = {
0xa0: {0x8: 7, 0x10: 6, 0x18: 7, 0x20: 4, 0x30: 6, 0x50: 1, 0xa0: 0},
}
# I don't need this right now, but I don't want to throw it away either
# Note: this value + 0x028000 is where the header is for the room (PC not SNES addressing)
# room_header_lookup = {
# 0x0: 0x462, 0x1: 0x46c, 0x2: 0x47a, 0x3: 0x5dd, 0x4: 0x485, 0x5: 0x490, 0x6: 0x490, 0x7: 0x497,
# 0x8: 0x4a2, 0x9: 0x4a9, 0xa: 0x4b5, 0xb: 0x4c0, 0xc: 0x4cb, 0xd: 0x4d8, 0xe: 0x4df, 0xf: 0x4ea,
# 0x10: 0x4ea, 0x11: 0x4f1, 0x12: 0x4fc, 0x13: 0x503, 0x14: 0x511, 0x15: 0x518, 0x16: 0x523, 0x17: 0x52e,
# 0x18: 0xc73, 0x19: 0x53a, 0x1a: 0x541, 0x1b: 0x54d, 0x1c: 0x558, 0x1d: 0x563, 0x1e: 0x56e, 0x1f: 0x579,
# 0x20: 0x584, 0x21: 0x58b, 0x22: 0x58b, 0x23: 0x503, 0x24: 0x592, 0x25: 0x599, 0x26: 0x599, 0x27: 0x5a6,
# 0x28: 0x5b2, 0x29: 0x5bd, 0x2a: 0x5c4, 0x2b: 0x5cb, 0x2c: 0xc73, 0x2d: 0x5d6, 0x2e: 0x5d6, 0x2f: 0x5dd,
# 0x30: 0x5e4, 0x31: 0x5ef, 0x32: 0x5fb, 0x33: 0x606, 0x34: 0x60d, 0x35: 0x618, 0x36: 0x61f, 0x37: 0x618,
# 0x38: 0x626, 0x39: 0x631, 0x3a: 0x63b, 0x3b: 0x646, 0x3c: 0x651, 0x3d: 0x658, 0x3e: 0x663, 0x3f: 0x66e,
# 0x40: 0x67a, 0x41: 0x686, 0x42: 0x691, 0x43: 0x69d, 0x44: 0x6a4, 0x45: 0x6ab, 0x46: 0x6b6, 0x47: 0x6bd,
# 0x48: 0x6bd, 0x49: 0x6bd, 0x4a: 0x6c4, 0x4b: 0x6d0, 0x4c: 0x6da, 0x4d: 0x6e5, 0x4e: 0x6f0, 0x4f: 0x6fb,
# 0x50: 0x705, 0x51: 0x713, 0x52: 0x71e, 0x53: 0x72c, 0x54: 0x737, 0x55: 0x742, 0x56: 0x749, 0x57: 0x750,
# 0x58: 0x757, 0x59: 0x75e, 0x5a: 0x765, 0x5b: 0x76c, 0x5c: 0x773, 0x5d: 0x77e, 0x5e: 0x789, 0x5f: 0x794,
# 0x60: 0x7a0, 0x61: 0x7a7, 0x62: 0x7a0, 0x63: 0x7b2, 0x64: 0x7bd, 0x65: 0x7c8, 0x66: 0x7d2, 0x67: 0x7dd,
# 0x68: 0x7e4, 0x69: 0x7eb, 0x6a: 0x7eb, 0x6b: 0x7f7, 0x6c: 0x802, 0x6d: 0x80d, 0x6e: 0x814, 0x6f: 0x81f,
# 0x70: 0x81f, 0x71: 0x82b, 0x72: 0x836, 0x73: 0x841, 0x74: 0x848, 0x75: 0x84f, 0x76: 0x856, 0x77: 0x863,
# 0x78: 0x870, 0x79: 0x870, 0x7a: 0x870, 0x7b: 0x870, 0x7c: 0x87a, 0x7d: 0x881, 0x7e: 0x88b, 0x7f: 0x896,
# 0x80: 0x8a1, 0x81: 0x8ac, 0x82: 0x8ac, 0x83: 0x8b3, 0x84: 0x8ba, 0x85: 0x8c1, 0x86: 0x8c8, 0x87: 0x8c8,
# 0x88: 0x8d4, 0x89: 0x8d4, 0x8a: 0x8de, 0x8b: 0x8de, 0x8c: 0x8e5, 0x8d: 0x8f2, 0x8e: 0x8f9, 0x8f: 0x904,
# 0x90: 0x904, 0x91: 0x90b, 0x92: 0x916, 0x93: 0x91d, 0x94: 0x928, 0x95: 0x928, 0x96: 0x92f, 0x97: 0x93a,
# 0x98: 0x945, 0x99: 0x950, 0x9a: 0x95b, 0x9b: 0x95b, 0x9c: 0x965, 0x9d: 0x96c, 0x9e: 0x976, 0x9f: 0x981,
# 0xa0: 0x988, 0xa1: 0x993, 0xa2: 0x99a, 0xa3: 0x993, 0xa4: 0x9a5, 0xa5: 0x9ac, 0xa6: 0x9b7, 0xa7: 0x9c2,
# 0xa8: 0x9cc, 0xa9: 0x9d3, 0xaa: 0x9dd, 0xab: 0x9e4, 0xac: 0x9ef, 0xad: 0x9f6, 0xae: 0x9f6, 0xaf: 0xa01,
# 0xb0: 0xa08, 0xb1: 0xa14, 0xb2: 0xa1e, 0xb3: 0xa25, 0xb4: 0xa2c, 0xb5: 0xa37, 0xb6: 0xa42, 0xb7: 0x50a,
# 0xb8: 0xa4d, 0xb9: 0xa54, 0xba: 0xa5b, 0xbb: 0xa62, 0xbc: 0xa69, 0xbd: 0xa74, 0xbe: 0xa74, 0xbf: 0xa7f,
# 0xc0: 0xa86, 0xc1: 0xa92, 0xc2: 0xa99, 0xc3: 0xaa0, 0xc4: 0xaa7, 0xc5: 0xab2, 0xc6: 0x50a, 0xc7: 0xab9,
# 0xc8: 0xac0, 0xc9: 0xac7, 0xca: 0xace, 0xcb: 0xace, 0xcc: 0xace, 0xcd: 0xad5, 0xce: 0xad5, 0xcf: 0xadf,
# 0xd0: 0xadf, 0xd1: 0xaeb, 0xd2: 0xaf6, 0xd3: 0xb01, 0xd4: 0xb01, 0xd5: 0xab2, 0xd6: 0x50a, 0xd7: 0xb01,
# 0xd8: 0xb01, 0xd9: 0xb08, 0xda: 0xb0f, 0xdb: 0xace, 0xdc: 0xace, 0xdd: 0xb1a, 0xde: 0xb1a, 0xdf: 0xb21,
# 0xe0: 0xb2c, 0xe1: 0xb37, 0xe2: 0xb3e, 0xe3: 0xb45, 0xe4: 0xb4c, 0xe5: 0xb4c, 0xe6: 0xb53, 0xe7: 0xb53,
# 0xe8: 0xb5a, 0xe9: 0xb68, 0xea: 0xb68, 0xeb: 0xb73, 0xec: 0xb7e, 0xed: 0xb7e, 0xee: 0xb8a, 0xef: 0xb94,
# 0xf0: 0xb53, 0xf1: 0xb53, 0xf2: 0xba0, 0xf3: 0xba0, 0xf4: 0xba5, 0xf5: 0xba5, 0xf6: 0xbac, 0xf7: 0xbac,
# 0xf8: 0xbac, 0xf9: 0xbba, 0xfa: 0xbc1, 0xfb: 0xbcc, 0xfc: 0xbd7, 0xfd: 0xbd7, 0xfe: 0xbba, 0xff: 0xbe3
# }

137
TestSuite.py Normal file
View File

@@ -0,0 +1,137 @@
import subprocess
import sys
import traceback
import io
import multiprocessing
import concurrent.futures
import argparse
from collections import OrderedDict
cpu_threads = multiprocessing.cpu_count()
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
def main(args=None):
successes = []
errors = []
task_mapping = []
tests = OrderedDict()
successes.append(f"Testing {args.dr} DR with {args.count} Tests" + (f" (intensity={args.tense})" if args.dr in ['basic', 'crossed'] else ""))
print(successes[0])
max_attempts = args.count
pool = concurrent.futures.ThreadPoolExecutor(max_workers=cpu_threads)
dead_or_alive = 0
alive = 0
def test(testname: str, command: str):
tests[testname] = [command]
for mode in [['Open', ''],
['Std ', ' --mode standard'],
['Inv ', ' --mode inverted']]:
basecommand = f"python3.8 DungeonRandomizer.py --door_shuffle {args.dr} --intensity {args.tense} --suppress_rom --suppress_spoiler"
def gen_seed():
taskcommand = basecommand + " " + command + mode[1]
return subprocess.run(taskcommand, capture_output=True, shell=True, text=True)
for x in range(1, max_attempts + 1):
task = pool.submit(gen_seed)
task.success = False
task.name = testname
task.mode = mode[0]
task_mapping.append(task)
test("Vanilla ", "--shuffle vanilla")
test("Retro ", "--retro --shuffle vanilla")
test("Keysanity ", "--shuffle vanilla --keydropshuffle --keysanity")
test("Simple ", "--shuffle simple")
test("Full ", "--shuffle full")
test("Crossed ", "--shuffle crossed")
test("Insanity ", "--shuffle insanity")
from tqdm import tqdm
with tqdm(concurrent.futures.as_completed(task_mapping),
total=len(task_mapping), unit="seed(s)",
desc=f"Success rate: 0.00%") as progressbar:
for task in progressbar:
dead_or_alive += 1
try:
result = task.result()
if result.returncode:
raise Exception(result.stderr)
except:
error = io.StringIO()
traceback.print_exc(file=error)
errors.append([task.name + task.mode, error.getvalue()])
else:
alive += 1
task.success = True
progressbar.set_description(f"Success rate: {(alive/dead_or_alive)*100:.2f}% - {task.name}{task.mode}")
def get_results(testname: str):
result = ""
for mode in ['Open', 'Std ', 'Inv ']:
dead_or_alive = [task.success for task in task_mapping if task.name == testname and task.mode == mode]
alive = [x for x in dead_or_alive if x]
success = f"{testname}{mode} Rate: {(len(alive) / len(dead_or_alive)) * 100:.2f}%"
successes.append(success)
print(success)
result += f"{(len(alive)/len(dead_or_alive))*100:.2f}%\t"
return result.strip()
results = []
for t in tests.keys():
results.append(get_results(t))
for result in results:
print(result)
successes.append(result)
return successes, errors
if __name__ == "__main__":
successes = []
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--count', default=0, type=lambda value: max(int(value), 0))
parser.add_argument('--cpu_threads', default=cpu_threads, type=lambda value: max(int(value), 1))
parser.add_argument('--help', default=False, action='store_true')
args = parser.parse_args()
if args.help:
parser.print_help()
exit(0)
cpu_threads = args.cpu_threads
for dr in [['vanilla', args.count if args.count else 2, 1],
['basic', args.count if args.count else 5, 3],
['crossed', args.count if args.count else 10, 3]]:
for tense in range(1, dr[2] + 1):
args = argparse.Namespace()
args.dr = dr[0]
args.tense = tense
args.count = dr[1]
s, errors = main(args=args)
if successes:
successes += [""] * 2
successes += s
print()
if errors:
with open(f"{dr[0]}{(f'-{tense}' if dr[0] in ['basic', 'crossed'] else '')}-errors.txt", 'w') as stream:
for error in errors:
stream.write(error[0] + "\n")
stream.write(error[1] + "\n\n")
with open("success.txt", "w") as stream:
stream.write(str.join("\n", successes))
input("Press enter to continue")

239
Utils.py
View File

@@ -4,6 +4,7 @@ import re
import subprocess
import sys
import xml.etree.ElementTree as ET
from collections import defaultdict
def int16_as_bytes(value):
value = value & 0xFFFF
@@ -225,6 +226,47 @@ def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan)
print(string)
def room_palette_data(old_rom):
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
offset = defaultdict(list)
for i in range(0, 256):
pointer_offset = 0x0271e2+i*2
header_offset = old_rom_data[pointer_offset + 1] << 8
header_offset += old_rom_data[pointer_offset]
header_offset -= 0x8000
header_offset += 0x020000
offset[header_offset].append(i)
# print(f'{hex(i)}: {hex(old_rom_data[header_offset+1])}')
for header_offset, rooms in offset.items():
print(f'{hex(header_offset)}: {[hex(x) for x in rooms]}')
# Palette notes:
# HC: 0
# Sewer/Dungeon: 1
# AT: 0xc near boss, 0x0 (other) 26 (f4, f1)
# Sanc: 0x1d
# Hera: 0x6
# Desert: 0x4 (boss and near boss), 0x9 (desert tiles 1 + desert main)
# Eastern: 0xb
# Pod: 0xf, x10 (boss)
# Swamp: 0x8 (boss), 0xa (other)
# Skull: 0xe (boss), 0xd (other)
# TT: 0x17, 0x23 (attic)
# Ice: 0x13, 0x14 (boss)
# Mire: 0x11 (other) , 0x12 (boss/preroom)
# TR: 0x18, 0x19 (boss+pre)
# GT: 0x28 (entrance + B1), 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?)
# Aga2: 0x1b, 0x1b (Pre aga2)
# Caves: 0x7, 0x20
# Uncle: 0x1
# Ganon: 0x21
# Houses: 0x2
def print_wiki_doors_by_region(d_regions, world, player):
for d, region_list in d_regions.items():
tile_map = {}
@@ -415,7 +457,202 @@ def print_graph(world):
ET.dump(root)
def extract_data_from_us_rom(rom):
with open(rom, 'rb') as stream:
rom_data = bytearray(stream.read())
rooms = [0x9a, 0x69, 0x78, 0x79, 0x7a, 0x88, 0x8a, 0xad]
for room in rooms:
b2idx = room*2
b3idx = room*3
headerptr = 0x110000 + b2idx # zscream specific
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x108000 # zscream specific
header, objectdata, spritedata, secretdata = [], [], [], []
for i in range(0, 14):
header.append(rom_data[headerloc+i])
objectptr = 0xF8000 + b3idx
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
objectloc -= 0x100000
stop, idx = False, 0
ffcnt = 0
mode = 0
while ffcnt < 2:
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
b3 = rom_data[objectloc+idx+2]
objectdata.append(b1)
objectdata.append(b2)
if b1 == 0xff and b2 == 0xff:
ffcnt += 1
mode = 0
if b1 == 0xf0 and b2 == 0xff:
mode = 1
if not mode and ffcnt < 2:
objectdata.append(b3)
idx += 3
else:
idx += 2
spriteptr = 0x4d62e + b2idx
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
done, idx = False, 0
while not done:
b1 = rom_data[spriteloc+idx]
spritedata.append(b1)
if b1 == 0xff:
done = True
idx += 1
secretptr = 0xdb69 + b2idx
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
done, idx = False, 0
while not done:
b1 = rom_data[secretloc+idx]
b2 = rom_data[secretloc+idx+1]
b3 = rom_data[secretloc+idx+2]
secretdata.append(b1)
secretdata.append(b2)
if b1 == 0xff and b2 == 0xff:
done = True
else:
secretdata.append(b3)
idx += 3
print(f'Room {room:02x}')
print(f'db {",".join([f"${x:02x}" for x in header])}')
print(f'Obj Length: {len(objectdata)}')
print_data_block(objectdata)
print('Sprites')
print_data_block(spritedata)
print('Secrets')
print_data_block(secretdata)
blockdata, torchdata = [], []
blockloc = 0x271de
for i in range(0, 128):
idx = i*4
b1 = rom_data[blockloc+idx]
b2 = rom_data[blockloc+idx+1]
room_idx = b1 + b2*0x100
if room_idx in rooms:
blockdata.append(b1)
blockdata.append(b2)
blockdata.append(rom_data[blockloc+idx+2])
blockdata.append(rom_data[blockloc+idx+3])
torchloc = 0x2736A
nomatch = False
append = False
for i in range(0, 192):
idx = i*2
b1 = rom_data[torchloc+idx]
b2 = rom_data[torchloc+idx+1]
if nomatch:
if b1 == 0xff and b2 == 0xff:
nomatch = False
elif not append:
room_idx = b1 + b2*0x100
if room_idx in rooms:
append = True
else:
nomatch = True
if append:
torchdata.append(b1)
torchdata.append(b2)
if b1 == 0xff and b2 == 0xff:
append = False
print('Blocks')
print_data_block(blockdata)
print('Torches')
print_data_block(torchdata)
print()
def print_data_block(block):
for i in range(0, len(block)//16 + 1):
slice = block[i*16:i*16+16]
print(f'db {",".join([f"${x:02x}" for x in slice])}')
def extract_data_from_jp_rom(rom):
with open(rom, 'rb') as stream:
rom_data = bytearray(stream.read())
# rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d]
rooms = [0x1a, 0x2a, 0xd1]
for room in rooms:
b2idx = room*2
b3idx = room*3
headerptr = 0x271e2 + b2idx
headerloc = rom_data[headerptr] + rom_data[headerptr+1]*0x100 + 0x18000
header, objectdata, spritedata, secretdata = [], [], [], []
for i in range(0, 14):
header.append(rom_data[headerloc+i])
objectptr = 0xF8000 + b3idx
objectloc = rom_data[objectptr] + rom_data[objectptr+1]*0x100 + rom_data[objectptr+2]*0x10000
objectloc -= 0x100000
stop, idx = False, 0
ffcnt = 0
mode = 0
while ffcnt < 2:
b1 = rom_data[objectloc+idx]
b2 = rom_data[objectloc+idx+1]
b3 = rom_data[objectloc+idx+2]
objectdata.append(b1)
objectdata.append(b2)
if b1 == 0xff and b2 == 0xff:
ffcnt += 1
mode = 0
if b1 == 0xf0 and b2 == 0xff:
mode = 1
if not mode and ffcnt < 2:
objectdata.append(b3)
idx += 3
else:
idx += 2
spriteptr = 0x4d62e + b2idx
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
secretptr = 0xdb67 + b2idx
secretloc = rom_data[secretptr] + rom_data[secretptr+1]*0x100
done, idx = False, 0
while not done:
b1 = rom_data[spriteloc+idx]
spritedata.append(b1)
if b1 == 0xff:
done = True
idx += 1
done, idx = False, 0
while not done:
b1 = rom_data[secretloc+idx]
b2 = rom_data[secretloc+idx+1]
b3 = rom_data[secretloc+idx+2]
secretdata.append(b1)
secretdata.append(b2)
if b1 == 0xff and b2 == 0xff:
done = True
else:
secretdata.append(b3)
idx += 3
print(f'Room {room:02x}')
print(f'HeaderPtr {headerptr:06x}')
print(f'HeaderLoc {headerloc:06x}')
print(f'db {",".join([f"${x:02x}" for x in header])}')
print(f'Obj Length: {len(objectdata)}')
print(f'ObjectPtr {objectptr:06x}')
print(f'ObjectLoc {objectloc:06x}')
for i in range(0, len(objectdata)//16 + 1):
slice = objectdata[i*16:i*16+16]
print(f'db {",".join([f"${x:02x}" for x in slice])}')
print(f'SpritePtr {spriteptr:06x}')
print(f'SpriteLoc {spriteloc:06x}')
print_data_block(spritedata)
print(f'SecretPtr {secretptr:06x}')
print(f'SecretLoc {secretloc:06x}')
print_data_block(secretdata)
print()
if __name__ == '__main__':
# make_new_base2current()
# read_entrance_data(old_rom=sys.argv[1])
read_layout_data(old_rom=sys.argv[1])
# room_palette_data(old_rom=sys.argv[1])
# extract_data_from_us_rom(sys.argv[1])
extract_data_from_jp_rom(sys.argv[1])

View File

@@ -1,4 +1,5 @@
!add = "clc : adc"
!addl = "clc : adc.l"
!sub = "sec : sbc"
!bge = "bcs"
!blt = "bcc"
@@ -36,3 +37,7 @@ incsrc hudadditions.asm
warnpc $279700
incsrc doortables.asm
warnpc $288000
; deals with own hooks
incsrc keydropshuffle.asm

View File

@@ -39,21 +39,22 @@ db $70
org $279F00
DoorOffset:
db $00,$01,$02,$00,$03,$00,$04,$00,$00,$00,$00,$00,$00,$05,$00,$00
db $00,$01,$02,$00,$03,$00,$04,$00,$00,$00,$00,$00,$9A,$05,$99,$00
db $00,$06,$07,$08,$09,$0A,$0B,$00,$00,$0C,$0D,$0E,$00,$0F,$10,$11
db $12,$13,$14,$15,$16,$00,$17,$00,$00,$00,$18,$19,$00,$00,$1A,$00
db $12,$13,$14,$15,$16,$00,$17,$00,$98,$00,$18,$19,$00,$00,$1A,$00
db $1B,$00,$1C,$1D,$1E,$1F,$20,$21,$22,$23,$24,$25,$00,$26,$27,$00
db $96,$28,$97,$29,$2A,$2B,$2C,$00,$00,$2D,$2E,$2F,$30,$31,$32,$00
db $33,$34,$35,$36,$00,$00,$37,$38,$39,$3A,$3B,$3C,$3D,$3E,$3F,$40
db $41,$42,$43,$00,$00,$00,$44,$45,$46,$00,$47,$48,$49,$4A,$4B,$00
db $00,$4C,$00,$00,$00,$4D,$4E,$00,$00,$00,$00,$4F,$50,$51,$52,$53
db $00,$54,$00,$00,$00,$55,$00,$00,$00,$00,$00,$56,$57,$58,$59,$00
db $5A,$5B,$5C,$5D,$00,$5E,$5F,$00,$00,$60,$00,$61,$62,$63,$64,$65
db $41,$42,$43,$A0,$00,$00,$44,$45,$46,$00,$47,$48,$49,$4A,$4B,$00
; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler
db $00,$4C,$00,$00,$00,$4D,$4E,$9E,$00,$00,$00,$4F,$50,$51,$52,$53
db $00,$54,$00,$9C,$9D,$55,$00,$00,$00,$00,$00,$56,$57,$58,$59,$00
db $5A,$5B,$5C,$5D,$00,$5E,$5F,$00,$9B,$60,$00,$61,$62,$63,$64,$65
db $66,$67,$68,$69,$6A,$6B,$00,$00,$6C,$6D,$6E,$6F,$70,$00,$71,$72
db $00,$73,$74,$75,$76,$77,$78,$79,$7A,$7B,$7C,$7D,$7E,$00,$7F,$80
db $00,$81,$82,$83,$84,$85,$86,$87,$88,$89,$00,$8A,$8B,$00,$8C,$00
db $00,$8D,$8E,$00,$00,$8F,$90,$00,$91,$92,$93,$94,$95,$00,$00,$00
db $00
db $9f
org $27A000
DoorTable:
@@ -210,7 +211,16 @@ dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003,
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT SE Quad
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Aga 6F
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewers Rope
; this should end at 27AE40 about (152 * 24 bytes = 3648 or E40)
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Desert West Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Desert Main Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Hera Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Tower Lobby
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Desert Back Lobby
; this should end at 27AF18 about (160 * 24 bytes = 3840 or F18)
; some values you can hardcode for spirals
;dw $0070, $36a0 ; ->HC Stairwell
;dw $0072, $4ff8 ; ->HC Map Room
@@ -609,5 +619,25 @@ db $04,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$05,$05,$ff,$0a,$0a,$ff,$0b,$ff
db $04,$0c,$0c,$ff,$ff,$0d,$0d,$ff,$05,$05,$05,$0a,$0a,$ff,$0b,$06
db $04,$06,$06,$06,$06,$06,$06,$06,$06,$ff,$06,$06,$ff,$06,$06,$06
db $06,$06,$03,$03,$03,$03,$ff,$ff,$06,$06,$06,$06,$ff,$06,$06,$06
;27f200
;27f200
PaletteTable:
db $21,$00,$00,$07,$00,$08,$00,$00,$07,$00,$00,$00,$00,$00,$00,$21
db $21,$00,$00,$00,$00,$00,$00,$00,$07,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$0e,$00,$00,$07,$00,$00,$07
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$07,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$13
db $00,$00,$00,$00,$00,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler
db $00,$00,$00,$00,$00,$00,$00,$06,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$14,$20
db $00,$07,$20,$20,$07,$07,$07,$07,$07,$20,$20,$07,$20,$20,$20,$20
db $07,$07,$02,$02,$02,$02,$07,$07,$07,$20,$20,$07,$20,$20,$20,$07
;27f300

View File

@@ -59,6 +59,9 @@ rts
nop #5
.done
org $01b618 ; Bank01.asm : 7963 Dungeon_LoadHeader (REP #$20 : INY : LDA [$0D], Y)
nop : jsl OverridePaletteHeader
org $00d377 ;Bank 00 line 3185
DecompDungAnimatedTiles:
org $00fda4 ;Bank 00 line 8882
@@ -101,10 +104,6 @@ nop : stz $0dd0, X : rts
.not_in_ganons_tower
org $208206
jsl MirrorCheckOverride2
org $208270
jsl MirrorCheckOverride2
org $07a955 ; <- Bank07.asm : around 6564 (JP is a bit different) (STZ $05FC : STZ $05FD)
jsl BlockEraseFix
nop #2
@@ -123,19 +122,30 @@ nop #3
org $028fc9
nop #2 : jsl BlindAtticFix
; also rando's hooks.asm line 1360
; 106e4e -> goes to a0ee4e
org $a0ee8a ; <- 6FC4C - headsup_display.asm : 836 (LDA $7EF36E : AND.w #$00FF : ADD.w #$0007 : AND.w #$FFF8 : TAX)
jsl DrHudOverride
org $028409
jsl SuctionOverworldFix
org $0ded04 ; <- rando's hooks.asm line 2192 - 6ED04 - equipment.asm : 1963 (REP #$30)
jsl DrHudDungeonItemsAdditions
org $098638 ; rando's hooks.asm line 2192
jsl CountChestKeys
;org $098638 ; rando's hooks.asm line 2192
;jsl CountChestKeys
org $06D192 ; rando's hooks.asm line 457
jsl CountAbsorbedKeys
; rando's hooks.asm line 1020
org $05FC7E ; <- 2FC7E - sprite_dash_item.asm : 118 (LDA $7EF36F : INC A : STA $7EF36F)
jsl CountBonkItem
;org $05FC7E ; <- 2FC7E - sprite_dash_item.asm : 118 (LDA $7EF36F : INC A : STA $7EF36F)
;jsl CountBonkItem
org $019dbd ; <- Bank01.asm : 4465 of Object_Draw8xN (LDA $9B52, Y : STA $7E2000, X)
jsl CutoffEntranceRug : bra .nextTile : nop
.nextTile
;maybe set 02e2 to 0
org $0799de ; <- Bank07.asm : 4088 (LDA.b #$15 : STA $5D)
JSL StoreTempBunnyState
;
org $08c450 ; <- ancilla_receive_item.asm : 146-148 (STY $5D : STZ $02D8)
JSL RetrieveBunnyState : NOP
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes

View File

@@ -54,4 +54,14 @@ CgramAuxToMain: ; ripped this from bank02 because it ended with rts
; tell NMI to upload new CGRAM data
inc $15
rts
}
}
OverridePaletteHeader:
lda.l DRMode : cmp #$02 : bne +
cpx #$01c2 : !bge +
rep #$20
txa : lsr : tax
lda.l PaletteTable, x
iny : rtl
+ rep #$20 : iny : lda [$0D], Y ; what we wrote over
rtl

View File

@@ -13,11 +13,6 @@ HudAdditions:
LDX.b $05 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+10 ; draw 100's digit
LDX.b $06 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+12 ; draw 10's digit
LDX.b $07 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+14 ; draw 1's digit
lda $7ef29b : and #$0020 : beq +
lda #$207f : bra .drawthing
+ lda #$345e
.drawthing STA !GOAL_DRAW_ADDRESS+16 ; castle gate indicator
++
ldx $040c : cpx #$ff : bne + : rts : +
@@ -35,35 +30,32 @@ HudAdditions:
.reminder sta $7ec702
+ lda.w DRFlags : and #$0004 : beq .restore
lda $7ef368 : and.l $0098c0, x : beq .restore
; lda #$2811 : sta $7ec740
; lda $7ef366 : and.l $0098c0, x : bne .check
; lda.w BigKeyStatus, x : bne + ; change this, if bk status changes to one byte
; lda #$2574 : bra ++
; + cmp #$0002 : bne +
; lda #$2420 : bra ++
; + lda #$207f : bra ++
; .check lda #$2826
; ++ sta $7ec742
txa : lsr : tax
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
lda #$2830 : sta $7ec7a4
lda.l GenericKeys : and #$00ff : bne +
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
lda #$2830 : sta $7ec7a4
+
lda.w ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6
; lda #$2871 : sta $7ec780
; lda.w TotalKeys, x
; sep #$20 : !sub $7ef4b0, x : rep #$20 ; todo 4b0 no longer in use
; jsr ConvertToDisplay : sta $7ec782
; todo 4b0 no longer in use
.restore
plb : rts
}
;column distance for BK/Smalls
HudOffsets:
; none hc east desert aga swamp pod mire skull ice hera tt tr gt
dw $fffe, $0000, $0006, $0008, $0002, $0010, $000e, $0018, $0012, $0016, $000a, $0014, $001a, $001e
; offset from 1644
RowOffsets:
dw $0000, $0000, $0040, $0080, $0000, $0080, $0040, $0080, $00c0, $0040, $00c0, $0000, $00c0, $0000
ColumnOffsets:
dw $0000, $0000, $0000, $0000, $000a, $000a, $000a, $0014, $000a, $0014, $0000, $0014, $0014, $001e
DrHudDungeonItemsAdditions:
{
jsl DrawHUDDungeonItems
@@ -73,33 +65,80 @@ DrHudDungeonItemsAdditions:
phx : phy : php
rep #$30
lda !HUD_FLAG : and.w #$0020 : beq + : bra ++ : +
lda HUDDungeonItems : and.w #$0003 : bne + : bra ++ : +
lda.w #$2810 : sta $1684 ; small keys icon
lda.w #$2811 : sta $16c4 ; big key icon
lda.w #$2810 : sta $1704 ; small keys icon
lda.w #$24f5 : sta $1606 : sta $1610 : sta $161a : sta $1624
sta $1644 : sta $164a : sta $1652 : sta $1662 : sta $1684 : sta $16c4
ldx #$0000
- sta $1704, x : sta $170e, x : sta $1718, x
inx #2 : cpx #$0008 : !blt -
lda !HUD_FLAG : and.w #$0020 : beq + : brl ++ : +
lda HUDDungeonItems : and.w #$0007 : bne + : brl ++ : +
; bk symbols
lda.w #$2811 : sta $1606 : sta $1610 : sta $161a : sta $1624
; sm symbols
lda.w #$2810 : sta $160a : sta $1614 : sta $161e : sta $16e4
; blank out stuff
lda.w #$24f5 : sta $1724
ldx #$0002
- lda $7ef364 : and.l $0098c0, x : beq + ; must have compass
lda.l HudOffsets, x : tay
jsr BkStatus : sta $16C6, y ; big key status
phx
txa : lsr : tax
lda.l ChestKeys, x : jsr ConvertToDisplay2 : sta $1706, y ; small key totals
plx
+ inx #2 : cpx #$001b : bcc -
- lda #$0000 : !addl RowOffsets,x : !addl ColumnOffsets, x : tay
lda.l DungeonReminderTable, x : sta $1644, y : iny #2
lda.w #$24f5 : sta $1644, y
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
jsr BkStatus : sta $1644, y : bra .smallKey ; big key status
+ lda $7ef366 : and.l $0098c0, x : beq .smallKey
lda.w #$2826 : sta $1644, y
.smallKey
+ iny #2
cpx #$001a : bne +
tya : !add #$003c : tay
+ stx $00
txa : lsr : tax
lda.w #$24f5 : sta $1644, y
lda.l $7ef37c, x : beq +
jsr ConvertToDisplay2 : sta $1644, y
+ iny #2 : lda.w #$24f5 : sta $1644, y
phx : ldx $00
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
plx : lda.l ChestKeys, x : jsr ConvertToDisplay2 : sta $1644, y ; small key totals
bra .skipStack
+ plx
.skipStack iny #2
cpx #$000d : beq +
lda.w #$24f5 : sta $1644, y
+
ldx $00
+ inx #2 : cpx #$001b : bcs ++ : brl -
++
lda !HUD_FLAG : and.w #$0020 : bne + : bra ++ : +
lda HUDDungeonItems : and.w #$000c : bne + : bra ++ : +
lda.w #$24f5 : sta $1704 ; blank
lda !HUD_FLAG : and.w #$0020 : bne + : brl ++ : +
lda HUDDungeonItems : and.w #$000c : bne + : brl ++ : +
; map symbols (do I want these) ; note compass symbol is 2c20
lda.w #$2821 : sta $1606 : sta $1610 : sta $161a : sta $1624
; blank out a couple thing from old hud
lda.w #$24f5 : sta $16e4 : sta $1724
sta $160a : sta $1614 : sta $161e ; blank out sm key indicators
ldx #$0002
- lda $7ef364 : and.l $0098c0, x : beq + ; must have compass
lda.l HudOffsets, x : tay
- lda #$0000 ; start of hud area
!addl RowOffsets, x : !addl ColumnOffsets, x : tay
lda.l DungeonReminderTable, x : sta $1644, y
iny #2
lda.w #$24f5 : sta $1644, y ; blank out map spot
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
lda #$2826 : sta $1644, y ; check mark
+ iny #2
cpx #$001a : bne +
tya : !add #$003c : tay
+ lda $7ef364 : and.l $0098c0, x : beq + ; must have compass
phx ; total chest counts
txa : lsr : tax
lda.l TotalLocationsLow, x : jsr ConvertToDisplay2 : sta $1706, y
lda.l TotalLocationsHigh, x : jsr ConvertToDisplay2 : sta $16c6, y
lda.l TotalLocationsHigh, x : jsr ConvertToDisplay2 : sta $1644, y : iny #2
lda.l TotalLocationsLow, x : jsr ConvertToDisplay2 : sta $1644, y
plx
+
bra .skipBlanks
+ lda.w #$24f5 : sta $1644, y : iny #2 : sta $1644, y
.skipBlanks iny #2
cpx #$001a : beq +
lda.w #$24f5 : sta $1644, y ; blank out spot
+ inx #2 : cpx #$001b : bcc -
++
plp : ply : plx : rtl
@@ -108,7 +147,7 @@ DrHudDungeonItemsAdditions:
BkStatus:
lda $7ef366 : and.l $0098c0, x : bne +++ ; has the bk already
lda.l BigKeyStatus, x : bne ++
lda #$2482 : rts ; 0/O for no BK
lda #$2827 : rts ; 0/O for no BK
++ cmp #$0002 : bne +
lda #$2420 : rts ; symbol for BnC
+ lda #$24f5 : rts ; black otherwise
@@ -124,29 +163,7 @@ ConvertToDisplay2:
cmp #$000a : !blt +
!add #$2553 : rts
+ !add #$2816 : rts
++ lda #$2483 : rts ; 0/O for 0 or placeholder digit
CountChestKeys:
jsl ItemDowngradeFix
jsr CountChest
rtl
CountChest:
lda !MULTIWORLD_ITEM_PLAYER_ID : bne .end
cpy #$24 : beq +
cpy #$a0 : !blt .end
cpy #$ae : !bge .end
pha : phx
tya : and #$0f : bne ++
inc a
++ tax : bra .count
+ pha : phx
lda $040c : lsr : tax
.count
lda $7ef4b0, x : inc : sta $7ef4b0, x
lda $7ef4e0, x : inc : sta $7ef4e0, x
.restore plx : pla
.end rts
++ lda #$2827 : rts ; 0/O for 0 or placeholder digit ;2483
CountAbsorbedKeys:
jsl IncrementSmallKeysNoPrimary : phx
@@ -155,20 +172,6 @@ CountAbsorbedKeys:
lda $7ef4b0, x : inc : sta $7ef4b0, x
+ plx : rtl
CountBonkItem:
jsl GiveBonkItem
lda $a0 ; check room ID - only bonk keys in 2 rooms so we're just checking the lower byte
cmp #115 : bne + ; Desert Bonk Key
lda.l BonkKey_Desert
bra ++
+ : cmp #140 : bne + ; GTower Bonk Key
lda.l BonkKey_GTower
bra ++
+ lda.b #$24 ; default to small key
++ cmp #$24 : bne +
phy : tay : jsr CountChest : ply
+ rtl
;================================================================================
; 16-bit A, 8-bit X
; in: A(b) - Byte to Convert

182
asm/keydropshuffle.asm Normal file
View File

@@ -0,0 +1,182 @@
org $06926e ; <- 3126e - sprite_prep.asm : 2664 (LDA $0B9B : STA $0CBA, X)
jsl SpriteKeyPrep : nop #2
org $06d049 ; <- 35049 sprite_absorbable : 31-32 (JSL Sprite_DrawRippleIfInWater : JSR Sprite_DrawAbsorbable)
jsl SpriteKeyDrawGFX : bra + : nop : +
org $06d180
jsl BigKeyGet : bcs $07 : nop #5
org $06d18d ; <- 3518D - sprite_absorbable.asm : 274 (LDA $7EF36F : INC A : STA $7EF36F)
jsl KeyGet
org $06f9f3 ; bank06.asm : 6732 (JSL Sprite_LoadProperties)
jsl LoadProperties_PreserveItemMaybe
org $06d23a
Sprite_DrawAbsorbable:
org $1eff81
Sprite_DrawRippleIfInWater:
org $0db818
Sprite_LoadProperties:
org $288000 ;140000
ShuffleKeyDrops:
db 0
ShuffleKeyDropsReserved:
db 0
LootTable: ;PC: 140002
db $0e, $00, $24 ;; ice jelly key
db $13, $00, $24 ;; pokey 2
db $16, $00, $24 ;; swamp waterway pot
db $21, $00, $24 ;; key rat
db $35, $00, $24 ;; swamp trench 2 pot
db $36, $00, $24 ;; hookshot pot
db $37, $00, $24 ;; trench 1 pot
db $38, $00, $24 ;; pot row pot
db $39, $00, $24 ;; skull gibdo
db $3d, $00, $24 ;; gt minihelma
db $3e, $00, $24 ;; ice conveyor
db $3f, $00, $24 ;; ice hammer block ??? is this a dungeon secret?
db $43, $00, $24 ;; tiles 2 pot
db $53, $00, $24 ;; beamos hall pot
db $56, $00, $24 ;; skull west lobby pot
db $63, $00, $24 ;; desert tiles 1 pot
db $71, $00, $24 ;; boomerang guard
db $72, $00, $24 ;; hc map guard
db $7b, $00, $24 ;; gt star pits pot
db $80, $00, $32 ;; a big key (for the current dungeon)
db $8b, $00, $24 ;; gt conv cross block
db $9b, $00, $24 ;; gt dlb switch pot
db $9f, $00, $24 ;; ice many pots
db $99, $00, $24 ;; eastern eyegore
db $a1, $00, $24 ;; mire fishbone pot
db $ab, $00, $24 ;; tt spike switch pot
db $b0, $00, $24 ;; tower circle of pots usain
db $b3, $00, $24 ;; mire spikes pot
db $b6, $00, $24 ;; pokey 1
db $ba, $00, $24 ;; eastern dark pot
db $bc, $00, $24 ;; tt hallway pot
db $c0, $00, $24 ;; tower dark archer
db $c1, $00, $24 ;; mire glitchy jelly
db $ff, $00, $ff
;140068
KeyTable:
db $a0, $a0, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9, $aa, $ab, $ac, $ad
SpriteKeyPrep:
{
lda $0b9b : sta $0cba, x ; what we wrote over
pha
lda.l ShuffleKeyDrops : beq +
phx
ldx #$fd
- inx #3 : lda.l LootTable, x : cmp #$ff : beq ++ : cmp $a0 : bne -
inx : lda.l LootTable, x : sta !MULTIWORLD_SPRITEITEM_PLAYER_ID
inx : lda.l LootTable, x
plx : sta $0e80, x
cmp #$24 : bne +++
lda $a0 : cmp #$80 : bne + : lda #$24
+++ jsl PrepDynamicTile : bra +
++ plx : lda #$24 : sta $0e80, x
+ pla
rtl
}
SpriteKeyDrawGFX:
{
jsl Sprite_DrawRippleIfInWater
pha
lda.l ShuffleKeyDrops : bne +
- pla
phk : pea.w .jslrtsreturn-1
pea.w $068014 ; an rtl address - 1 in Bank06
jml Sprite_DrawAbsorbable
.jslrtsreturn
rtl
+ lda $0e80, x
cmp #$24 : bne +
lda $a0 : cmp #$80 : bne - : lda #$24
+ jsl DrawDynamicTile ; see DrawHeartPieceGFX if problems
cmp #$03 : bne +
pha : lda $0e60, x : ora.b #$20 : sta $0E60, x : pla
+
jsl.l Sprite_DrawShadowLong
pla : rtl
}
KeyGet:
{
lda $7ef36f ; what we wrote over
pha
lda.l ShuffleKeyDrops : bne +
- pla : rtl
+ ldy $0e80, x
lda $a0 : cmp #$87 : bne +
jsr ShouldKeyBeCountedForDungeon : bcc -
jsl CountChestKeyLong : bra -
+ phy
jsr KeyGetPlayer : sta !MULTIWORLD_ITEM_PLAYER_ID
jsl.l $0791b3 ; Player_HaltDashAttackLong
jsl.l Link_ReceiveItem
pla : sta $00
lda !MULTIWORLD_ITEM_PLAYER_ID : bne .end
phx
lda $040c : lsr : tax
lda $00 : cmp KeyTable, x : bne +
- plx : pla : rtl
+ cmp #$af : beq - ; universal key
cmp #$24 : beq - ; small key for this dungeon
plx
.end
pla : dec : rtl
}
; Input Y - the item type
ShouldKeyBeCountedForDungeon:
{
phx
lda $040c : lsr : tax
tya : cmp KeyTable, x : bne +
- plx : sec : rts
+ cmp #$24 : beq -
plx : clc : rts
}
BigKeyGet:
{
lda.l ShuffleKeyDrops : bne +
- stz $02e9 : ldy.b #$32 ; what we wrote over
phx : jsl Link_ReceiveItem : plx ; what we wrote over
clc : rtl
+
ldy $0e80, x
cpy #$32 : beq -
+ sec : rtl
}
KeyGetPlayer:
{
phx
ldx #$fd
- inx #3 : lda.l LootTable, x : cmp #$ff : beq ++ : cmp $a0 : bne -
++ inx : lda.l LootTable, x
plx
rts
}
LoadProperties_PreserveItemMaybe:
{
lda.l ShuffleKeyDrops : bne +
jsl Sprite_LoadProperties : rtl
+ lda $0e80, x : pha
jsl Sprite_LoadProperties
pla : sta $0e80, x
rtl
}

View File

@@ -190,8 +190,9 @@ PrepScrollToEdge:
+ sta $05
lda $01 : and #$10 : beq +
lda #01
+ sta $ee
rts
+ sta $ee : bne +
stz $0476
+ rts
}
; Normal Flags should be in $01
@@ -199,7 +200,9 @@ PrepScrollToEdge:
PrepScrollToNormal:
{
lda $01 : sta $fe : and #$04 : lsr #2 : sta $ee ; trap door and layer
stz $05 : lda #$78 : sta $04
bne +
stz $0476
+ stz $05 : lda #$78 : sta $04
lda $01 : and #$03 : beq .end
cmp #$02 : !bge +
lda #$f8 : sta $04 : bra .end

View File

@@ -11,7 +11,7 @@ LampCheckOverride:
LDA $7EF3CA : BNE +
.lightWorld
LDA $040C : CMP.b #$02 : BNE ++ ; check if we're in HC
LDA $040C : CMP.b #$04 : !BGE ++ ; check if we're in HC
LDA LampConeSewers : BRA .done
++
LDA LampConeLightWorld : BRA .done
@@ -49,10 +49,6 @@ MirrorCheckOverride:
rtl
+ lda.l DRScroll : rtl
MirrorCheckOverride2:
lda $7ef353 : and #$02 : rtl
BlockEraseFix:
lda $7ef353 : and #$02 : beq +
stz $05fc : stz $05fd
@@ -79,4 +75,49 @@ BlindAtticFix:
lda.l DRMode : beq +
lda #$01 : rtl
+ lda $7EF3CC : cmp.b #$06
rtl
rtl
SuctionOverworldFix:
stz $50 : stz $5e
lda.l DRMode : beq +
stz $49
+ rtl
; TT Alcove, Mire bridges, pod falling, SW torch room, TR Pipe room, Bob's Room, Ice Many Pots, Mire Hub
; swamp waterfall, Gauntlet 3, Eastern Push block
CutoffRooms:
db $bc, $a2, $1a, $49, $14, $8c, $9f, $c2
db $66, $5d, $a8
; Don't forget CutoffRoomCount!!!
CutoffEntranceRug:
pha : phx
lda.l DRMode : beq .norm
lda $04 : cmp #$000A : beq +
cmp #$000C : bne .norm
+ lda $a0 : sep #$20 : ldx #$0000
- cmp.l CutoffRooms, x : beq .check
inx : cpx #$000B : !blt - ; CutoffRoomCount is here!
rep #$20
.norm plx : pla : lda $9B52, y : sta $7E2000, x ; what we wrote over
rtl
.check
rep #$20
lda $0c : cmp #$0006 : !bge .skip
lda $0e : cmp #$0008 : !bge .skip
cmp #$0004 : !blt .skip
bra .norm
.skip plx : pla : rtl
StoreTempBunnyState:
LDA $5D : CMP #$1C : BNE +
STA $5F
+ LDA #$15 : STA $5D ; what we wrote over
RTL
RetrieveBunnyState:
STY $5D : STZ $02D8 ; what we wrote over
LDA $5F : BEQ +
STA $5D
+ RTL

View File

@@ -177,11 +177,11 @@ ScrollX: ;change the X offset variables
rts
LimitXCamera:
cmp #$0080 : !bge +
cmp #$0079 : !bge +
lda #$0000 : bra .end
+ cmp #$0181 : !blt +
lda #$0180
+ !sub #$0080
+ cmp #$0178 : !blt +
lda #$0178
+ !sub #$0078
.end rts
CheckRoomLayoutX:

BIN
data/base2current.bps Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -59,6 +59,17 @@
"expert"
]
},
"keydropshuffle" : {
"action": "store_true",
"type": "bool"
},
"mixed_travel" : {
"choices": [
"prevent",
"allow",
"force"
]
},
"timer": {
"choices": [
"none",

View File

@@ -202,7 +202,7 @@
"Door Shuffle Intensity Level (default: %(default)s)",
"1: Shuffles normal doors and spiral staircases",
"2: And shuffles open edges and straight staircases",
"3: (Coming soon) And shuffles dungeon lobbies",
"3: And shuffles dungeon lobbies",
"random: Picks one of those at random"
],
"experimental": [ "Enable experimental features. (default: %(default)s)" ],
@@ -244,6 +244,13 @@
"compassshuffle": [ "Compasses are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
"bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
"keydropshuffle": [ "Key Drops (Pots and Enemies) are shuffled and other items can take their place (default: %(default)s)" ],
"mixed_travel": [
"How to handle potential traversal between dungeon in Crossed door shuffle",
"Prevent: Rails are placed to prevent bombs jump and hovering from changing dungeon except with glitched logic settings",
"Allow: Take the rails off, \"I know what I'm doing\"",
"Force: Force these troublesome connections to be in the same dungeon (but not in logic). No rails will appear"
],
"retro": [
"Keys are universal, shooting arrows costs rupees,",
"and a few other little things make this more like Zelda-1. (default: %(default)s)"

View File

@@ -46,12 +46,12 @@
"adjust.rom.dialog.success": "Success",
"adjust.rom.dialog.success.message": "Rom patched successfully.",
"randomizer.dungeon.keysanity": "Shuffle: ",
"randomizer.dungeon.mapshuffle": "Maps",
"randomizer.dungeon.compassshuffle": "Compasses",
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
"randomizer.dungeon.keydropshuffle": "Key Drops (pots and enemies)",
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
@@ -61,7 +61,7 @@
"randomizer.dungeon.dungeonintensity": "Intensity Level",
"randomizer.dungeon.dungeonintensity.1": "1: Normal Supertile and Spiral Stairs",
"randomizer.dungeon.dungeonintensity.2": "2: Open Edges and Straight Stairs",
"randomizer.dungeon.dungeonintensity.3": "3: (Coming soon) Dungeon Lobbies",
"randomizer.dungeon.dungeonintensity.3": "3: Dungeon Lobbies",
"randomizer.dungeon.dungeonintensity.random": "Random",
"randomizer.dungeon.experimental": "Enable Experimental Features",
@@ -72,6 +72,10 @@
"randomizer.dungeon.dungeon_counters.on": "On",
"randomizer.dungeon.dungeon_counters.pickup": "On Compass Pickup",
"randomizer.dungeon.mixed_travel": "Mixed Dungeon Travel ",
"randomizer.dungeon.mixed_travel.prevent": "Prevent Mixed Dungeon Travel",
"randomizer.dungeon.mixed_travel.allow": "Allow Mixed Dungeon Travel",
"randomizer.dungeon.mixed_travel.force": "Force Reachable Areas to Same Dungeon",
"randomizer.enemizer.potshuffle": "Pot Shuffle",

View File

@@ -3,6 +3,7 @@
"mapshuffle": { "type": "checkbox" },
"compassshuffle": { "type": "checkbox" },
"smallkeyshuffle": { "type": "checkbox" },
"bigkeyshuffle": { "type": "checkbox" }
"bigkeyshuffle": { "type": "checkbox" },
"keydropshuffle": { "type": "checkbox" }
}
}

View File

@@ -19,7 +19,7 @@
"random"
],
"config": {
"width": 40
"width": 35
}
},
"experimental": { "type": "checkbox" },
@@ -32,6 +32,18 @@
"on",
"pickup"
]
},
"mixed_travel": {
"type" : "selectbox",
"default": "auto",
"options": [
"prevent",
"allow",
"force"
],
"config": {
"width": 35
}
}
}
}

View File

@@ -1,2 +1,3 @@
aenum
fast-enum
fast-enum
python-bps-continued

View File

@@ -86,10 +86,12 @@ SETTINGSTOPROCESS = {
"compassshuffle": "compassshuffle",
"smallkeyshuffle": "keyshuffle",
"bigkeyshuffle": "bigkeyshuffle",
"keydropshuffle": "keydropshuffle",
"dungeondoorshuffle": "door_shuffle",
"dungeonintensity": "intensity",
"experimental": "experimental",
"dungeon_counters": "dungeon_counters"
"dungeon_counters": "dungeon_counters",
"mixed_travel": "mixed_travel"
},
"gameoptions": {
"hints": "hints",