231
BaseClasses.py
231
BaseClasses.py
@@ -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
4
CLI.py
@@ -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": "",
|
||||
|
||||
619
DoorShuffle.py
619
DoorShuffle.py
@@ -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
240
Doors.py
@@ -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)
|
||||
|
||||
@@ -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'],
|
||||
}
|
||||
|
||||
|
||||
46
Dungeons.py
46
Dungeons.py
@@ -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 = {
|
||||
|
||||
@@ -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'),
|
||||
|
||||
23
ItemList.py
23
ItemList.py
@@ -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])
|
||||
|
||||
|
||||
|
||||
@@ -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
27
Main.py
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
32
README.md
32
README.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
205
Regions.py
205
Regions.py
@@ -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
201
Rom.py
@@ -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',
|
||||
|
||||
12
RoomData.py
12
RoomData.py
@@ -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]
|
||||
|
||||
78
Rules.py
78
Rules.py
@@ -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):
|
||||
|
||||
42
Tables.py
42
Tables.py
@@ -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
137
TestSuite.py
Normal 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
239
Utils.py
@@ -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])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
12
asm/gfx.asm
12
asm/gfx.asm
@@ -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
|
||||
@@ -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
182
asm/keydropshuffle.asm
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
BIN
data/base2current.bps
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -59,6 +59,17 @@
|
||||
"expert"
|
||||
]
|
||||
},
|
||||
"keydropshuffle" : {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"mixed_travel" : {
|
||||
"choices": [
|
||||
"prevent",
|
||||
"allow",
|
||||
"force"
|
||||
]
|
||||
},
|
||||
"timer": {
|
||||
"choices": [
|
||||
"none",
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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",
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"mapshuffle": { "type": "checkbox" },
|
||||
"compassshuffle": { "type": "checkbox" },
|
||||
"smallkeyshuffle": { "type": "checkbox" },
|
||||
"bigkeyshuffle": { "type": "checkbox" }
|
||||
"bigkeyshuffle": { "type": "checkbox" },
|
||||
"keydropshuffle": { "type": "checkbox" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
aenum
|
||||
fast-enum
|
||||
fast-enum
|
||||
python-bps-continued
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user