Merge remote-tracking branch 'upstream/DoorDevUnstable' into OverworldShuffle

This commit is contained in:
codemann8
2021-02-08 12:02:11 -06:00
358 changed files with 868 additions and 459 deletions
+8 -8
View File
@@ -26,8 +26,8 @@ jobs:
# os & python versions # os & python versions
strategy: strategy:
matrix: matrix:
os-name: [ ubuntu-latest, ubuntu-16.04, macOS-latest, windows-latest ] os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ]
python-version: [ 3.7 ] python-version: [ 3.8 ]
# needs: [ install-test ] # needs: [ install-test ]
steps: steps:
# checkout commit # checkout commit
@@ -57,11 +57,11 @@ jobs:
# run build-gui.py # run build-gui.py
- name: Build GUI - name: Build GUI
run: | run: |
python ./build-gui.py python ./source/meta/build-gui.py
# run build-dr.py # run build-dr.py
- name: Build DungeonRandomizer - name: Build DungeonRandomizer
run: | run: |
python ./build-dr.py python ./source/meta/build-dr.py
# prepare binary artifacts for later step # prepare binary artifacts for later step
- name: Prepare Binary Artifacts - name: Prepare Binary Artifacts
env: env:
@@ -88,8 +88,8 @@ jobs:
strategy: strategy:
matrix: matrix:
# install/release on not xenial # install/release on not xenial
os-name: [ ubuntu-latest, macOS-latest, windows-latest ] os-name: [ ubuntu-latest, ubuntu-18.04, macOS-latest, windows-latest ]
python-version: [ 3.7 ] python-version: [ 3.8 ]
needs: [ install-build ] needs: [ install-build ]
steps: steps:
@@ -150,9 +150,9 @@ jobs:
# os & python versions # os & python versions
strategy: strategy:
matrix: matrix:
# release only on bionic # release only on focal/bionic
os-name: [ ubuntu-latest ] os-name: [ ubuntu-latest ]
python-version: [ 3.7 ] python-version: [ 3.8 ]
needs: [ install-prepare-release ] needs: [ install-prepare-release ]
steps: steps:
+17 -6
View File
@@ -153,8 +153,9 @@ class World(object):
self._door_cache[(door.name, door.player)] = door self._door_cache[(door.name, door.player)] = door
def remove_door(self, door, player): def remove_door(self, door, player):
if (door, player) in self._door_cache.keys(): if (door.name, player) in self._door_cache.keys():
del self._door_cache[(door, player)] del self._door_cache[(door.name, player)]
if door in self.doors:
self.doors.remove(door) self.doors.remove(door)
def get_regions(self, player=None): def get_regions(self, player=None):
@@ -1070,7 +1071,7 @@ hook_dir_map = {
def hook_from_door(door): def hook_from_door(door):
if door.type == DoorType.SpiralStairs: if door.type == DoorType.SpiralStairs:
return Hook.Stairs return Hook.Stairs
if door.type in [DoorType.Normal, DoorType.Open, DoorType.StraightStairs]: if door.type in [DoorType.Normal, DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
return hook_dir_map[door.direction] return hook_dir_map[door.direction]
return None return None
@@ -1188,7 +1189,8 @@ class Door(object):
# rom properties # rom properties
self.roomIndex = -1 self.roomIndex = -1
# 0,1,2 + Direction (N:0, W:3, S:6, E:9) for normal # 0,1,2 for normal
# 0-7 for ladder
# 0-4 for spiral offset thing # 0-4 for spiral offset thing
self.doorIndex = -1 self.doorIndex = -1
self.layer = -1 # 0 for normal floor, 1 for the inset layer self.layer = -1 # 0 for normal floor, 1 for the inset layer
@@ -1219,6 +1221,7 @@ class Door(object):
# self.connected = False # combine with Dest? # self.connected = False # combine with Dest?
self.dest = None self.dest = None
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed) self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
self.blocked_orig = False
self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue) self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue)
self.smallKey = False # There's a small key door on this side self.smallKey = False # There's a small key door on this side
self.bigKey = False # There's a big key door on this side self.bigKey = False # There's a big key door on this side
@@ -1230,7 +1233,7 @@ class Door(object):
self.dead = False self.dead = False
self.entrance = entrance self.entrance = entrance
if entrance is not None: if entrance is not None and not entrance.door:
entrance.door = self entrance.door = self
def getAddress(self): def getAddress(self):
@@ -1238,6 +1241,8 @@ class Door(object):
return 0x13A000 + normal_offset_table[self.roomIndex] * 24 + (self.doorIndex + self.direction.value * 3) * 2 return 0x13A000 + normal_offset_table[self.roomIndex] * 24 + (self.doorIndex + self.direction.value * 3) * 2
elif self.type == DoorType.SpiralStairs: elif self.type == DoorType.SpiralStairs:
return 0x13B000 + (spiral_offset_table[self.roomIndex] + self.doorIndex) * 4 return 0x13B000 + (spiral_offset_table[self.roomIndex] + self.doorIndex) * 4
elif self.type == DoorType.Ladder:
return 0x13C700 + self.doorIndex * 2
elif self.type == DoorType.Open: elif self.type == DoorType.Open:
base_address = { base_address = {
Direction.North: 0x13C500, Direction.North: 0x13C500,
@@ -1254,6 +1259,12 @@ class Door(object):
if src.type == DoorType.StraightStairs: if src.type == DoorType.StraightStairs:
bitmask += 0x40 bitmask += 0x40
return [self.roomIndex, bitmask + self.doorIndex] return [self.roomIndex, bitmask + self.doorIndex]
if self.type == DoorType.Ladder:
bitmask = 4 * (self.layer ^ 1 if src.toggle else self.layer)
bitmask += 0x08 * self.doorIndex
if src.type == DoorType.StraightStairs:
bitmask += 0x40
return [self.roomIndex, bitmask + 0x03]
if self.type == DoorType.SpiralStairs: if self.type == DoorType.SpiralStairs:
bitmask = int(self.layer) << 2 bitmask = int(self.layer) << 2
bitmask += 0x10 * int(self.zeroHzCam) bitmask += 0x10 * int(self.zeroHzCam)
@@ -1316,7 +1327,7 @@ class Door(object):
return self return self
def no_exit(self): def no_exit(self):
self.blocked = True self.blocked = self.blocked_orig = True
return self return self
def no_entrance(self): def no_entrance(self):
+173 -49
View File
@@ -8,17 +8,43 @@ from typing import DefaultDict, Dict, List
from functools import reduce from functools import reduce
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo
from Doors import reset_portals
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
from Dungeons import dungeon_bigs, dungeon_keys, dungeon_hints from Dungeons import dungeon_bigs, dungeon_keys, dungeon_hints
from Items import ItemFactory from Items import ItemFactory
from RoomData import DoorKind, PairedDoor from RoomData import DoorKind, PairedDoor, reset_rooms
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances 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 create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
from DungeonGenerator import dungeon_portals, dungeon_drops from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout
def link_doors(world, player): def link_doors(world, player):
attempt, valid = 1, False
while not valid:
try:
link_doors_main(world, player)
valid = True
except GenerationException as e:
logging.getLogger('').debug(f'Irreconcilable generation. {str(e)} Starting a new attempt.')
attempt += 1
if attempt > 10:
raise Exception('Could not create world in 10 attempts. Generation algorithms need more work', e)
for door in world.doors:
if door.player == player:
door.dest = None
door.entranceFlag = False
ent = door.entrance
if door.type != DoorType.Logical and ent.connected_region is not None:
ent.connected_region.entrances = [x for x in ent.connected_region.entrances if x != ent]
ent.connected_region = None
for portal in world.dungeon_portals[player]:
disconnect_portal(portal, world, player)
reset_portals(world, player)
reset_rooms(world, player)
def link_doors_main(world, player):
# Drop-down connections & push blocks # Drop-down connections & push blocks
for exitName, regionName in logical_connections: for exitName, regionName in logical_connections:
@@ -32,19 +58,20 @@ def link_doors(world, player):
connect_simple_door(world, exitName, regionName, player) connect_simple_door(world, exitName, regionName, player)
for exitName, regionName in dungeon_warps: for exitName, regionName in dungeon_warps:
connect_simple_door(world, exitName, regionName, player) connect_simple_door(world, exitName, regionName, player)
for ent, ext in ladders:
connect_two_way(world, ent, ext, player)
if world.intensity[player] < 2: if world.intensity[player] < 2:
for entrance, ext in open_edges: for entrance, ext in open_edges:
connect_two_way(world, entrance, ext, player) connect_two_way(world, entrance, ext, player)
for entrance, ext in straight_staircases: for entrance, ext in straight_staircases:
connect_two_way(world, entrance, ext, player) connect_two_way(world, entrance, ext, player)
for entrance, ext in ladders:
connect_two_way(world, entrance, ext, player)
if world.intensity[player] < 3 or world.doorShuffle == 'vanilla': if world.intensity[player] < 3 or world.doorShuffle == 'vanilla':
mirror_route = world.get_entrance('Sanctuary Mirror Route', player) mirror_route = world.get_entrance('Sanctuary Mirror Route', player)
mr_door = mirror_route.door mr_door = mirror_route.door
sanctuary = mirror_route.parent_region sanctuary = mirror_route.parent_region
if mirror_route in sanctuary.exits:
sanctuary.exits.remove(mirror_route) sanctuary.exits.remove(mirror_route)
world.remove_entrance(mirror_route, player) world.remove_entrance(mirror_route, player)
world.remove_door(mr_door, player) world.remove_door(mr_door, player)
@@ -60,6 +87,9 @@ def link_doors(world, player):
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
world.get_portal('Sanctuary', player).destination = True world.get_portal('Sanctuary', player).destination = True
world.get_portal('Desert East', player).destination = True world.get_portal('Desert East', player).destination = True
if world.mode[player] == 'inverted':
world.get_portal('Desert West', player).destination = True
if world.mode[player] == 'open':
world.get_portal('Skull 2 West', 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 Lazy Eyes', player).destination = True
world.get_portal('Turtle Rock Eye Bridge', player).destination = True world.get_portal('Turtle Rock Eye Bridge', player).destination = True
@@ -77,6 +107,8 @@ def link_doors(world, player):
connect_simple_door(world, exitName, regionName, player) connect_simple_door(world, exitName, regionName, player)
for entrance, ext in spiral_staircases: for entrance, ext in spiral_staircases:
connect_two_way(world, entrance, ext, player) connect_two_way(world, entrance, ext, player)
for entrance, ext in ladders:
connect_two_way(world, entrance, ext, player)
for entrance, ext in default_door_connections: for entrance, ext in default_door_connections:
connect_two_way(world, entrance, ext, player) connect_two_way(world, entrance, ext, player)
for ent, ext in default_one_way_connections: for ent, ext in default_one_way_connections:
@@ -131,7 +163,7 @@ def create_door_spoiler(world, player):
door_a = ext.door door_a = ext.door
connect = ext.connected_region connect = ext.connected_region
if door_a and door_a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open, if door_a and door_a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open,
DoorType.StraightStairs] and door_a not in done: DoorType.StraightStairs, DoorType.Ladder] and door_a not in done:
done.add(door_a) done.add(door_a)
door_b = door_a.dest door_b = door_a.dest
if door_b and not isinstance(door_b, Region): if door_b and not isinstance(door_b, Region):
@@ -385,6 +417,9 @@ def choose_portals(world, player):
possible_portals = outstanding_portals if not info.sole_entrance else [x for x in outstanding_portals if x != info.sole_entrance] 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) choice, portal = assign_portal(candidates, possible_portals, world, player)
if choice.deadEnd: if choice.deadEnd:
if choice.passage:
portal.destination = True
else:
portal.deadEnd = True portal.deadEnd = True
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals) clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
the_rest = info.total - len(portal_assignment[dungeon]) the_rest = info.total - len(portal_assignment[dungeon])
@@ -474,7 +509,6 @@ def connect_portal(portal, world, player):
ent, ext, entrance_name = portal_map[portal.name] ent, ext, entrance_name = portal_map[portal.name]
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']: if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
ext = 'Inverted ' + ext ext = 'Inverted ' + ext
# ent = 'Inverted ' + ent
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying 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 = world.get_entrance(ext, player)
portal_entrance.connected_region = target_exit.parent_region portal_entrance.connected_region = target_exit.parent_region
@@ -485,23 +519,21 @@ def connect_portal(portal, world, player):
chosen_door = world.get_door(portal_entrance.name, player) chosen_door = world.get_door(portal_entrance.name, player)
chosen_door.blocked = False chosen_door.blocked = False
connect_door_only(world, chosen_door, portal_region, player) connect_door_only(world, chosen_door, portal_region, player)
portal_entrance.parent_region.entrances.append(edit_entrance)
# todo: remove this? def disconnect_portal(portal, world, player):
def connect_portal_copy(portal, world, player):
ent, ext, entrance_name = portal_map[portal.name] ent, ext, entrance_name = portal_map[portal.name]
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']: portal_entrance = world.get_entrance(portal.door.entrance.name, player)
ext = 'Inverted ' + ext # portal_region = world.get_region(portal.name + ' Portal', player)
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)
portal_entrance.connected_region = target_exit.parent_region
portal_region = world.get_region(portal.name + ' Portal', player)
portal_region.entrances.append(portal_entrance)
edit_entrance = world.get_entrance(entrance_name, player) edit_entrance = world.get_entrance(entrance_name, player)
edit_entrance.connected_region = portal_entrance.parent_region
chosen_door = world.get_door(portal_entrance.name, player) chosen_door = world.get_door(portal_entrance.name, player)
chosen_door.blocked = False
connect_door_only(world, chosen_door, portal_region, player) # reverse work
if edit_entrance in portal_entrance.parent_region.entrances:
portal_entrance.parent_region.entrances.remove(edit_entrance)
chosen_door.blocked = chosen_door.blocked_orig
chosen_door.entranceFlag = False
def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allowed=False, crossed=False, bk_shuffle=False): def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allowed=False, crossed=False, bk_shuffle=False):
@@ -638,7 +670,7 @@ def within_dungeon(world, player):
dungeon_builders[key].entrance_list = list(entrances_map[key]) dungeon_builders[key].entrance_list = list(entrances_map[key])
recombinant_builders = {} recombinant_builders = {}
entrances, splits = create_dungeon_entrances(world, player) entrances, splits = create_dungeon_entrances(world, player)
builder_info = entrances, splits, world, player builder_info = entrances, splits, connections_tuple, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info) handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player) main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
@@ -662,12 +694,12 @@ def within_dungeon(world, player):
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info): def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info):
dungeon_entrances, split_dungeon_entrances, world, player = builder_info dungeon_entrances, split_dungeon_entrances, c_tuple, world, player = builder_info
if dungeon_entrances is None: if dungeon_entrances is None:
dungeon_entrances = default_dungeon_entrances dungeon_entrances = default_dungeon_entrances
if split_dungeon_entrances is None: if split_dungeon_entrances is None:
split_dungeon_entrances = split_region_starts split_dungeon_entrances = split_region_starts
builder_info = dungeon_entrances, split_dungeon_entrances, world, player builder_info = dungeon_entrances, split_dungeon_entrances, c_tuple, world, player
for name, split_list in split_dungeon_entrances.items(): for name, split_list in split_dungeon_entrances.items():
builder = dungeon_builders.pop(name) builder = dungeon_builders.pop(name)
@@ -705,10 +737,11 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
continue continue
origin_list = list(builder.entrance_list) origin_list = list(builder.entrance_list)
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name) find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player)
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player): if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
if last_key == builder.name or loops > 1000: if last_key == builder.name or loops > 1000:
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin' origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
raise Exception('Infinite loop detected for "%s" located at %s' % (builder.name, origin_name)) raise GenerationException(f'Infinite loop detected for "{builder.name}" located at {origin_name}')
sector_queue.append(builder) sector_queue.append(builder)
last_key = builder.name last_key = builder.name
loops += 1 loops += 1
@@ -852,6 +885,22 @@ def aga_tower_enabled(enabled):
return False return False
def treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player):
# what about ER dungeons? - find an example? (bad key doors 0 keys not valid)
if split_dungeon and name in multiple_portal_map:
possible_entrances = []
for portal_name in multiple_portal_map[name]:
portal = world.get_portal(portal_name, player)
portal_entrance = world.get_entrance(portal_map[portal_name][0], player)
if not portal.destination and portal_entrance.parent_region.name not in world.inaccessible_regions[player]:
possible_entrances.append(portal)
if len(possible_entrances) == 1:
single_portal = possible_entrances[0]
if single_portal.door.entrance.parent_region.name in origin_list and len(origin_list) == 1:
return False
return split_dungeon
# goals: # goals:
# 1. have enough chests to be interesting (2 more than dungeon items) # 1. have enough chests to be interesting (2 more than dungeon items)
# 2. have a balanced amount of regions added (check) # 2. have a balanced amount of regions added (check)
@@ -886,7 +935,7 @@ def cross_dungeon(world, player):
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name] 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) loc.forced_item = loc.item = ItemFactory(key_name, player)
recombinant_builders = {} recombinant_builders = {}
builder_info = entrances, splits, world, player builder_info = entrances, splits, connections_tuple, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info) handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player) main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
@@ -1277,11 +1326,10 @@ def combine_layouts(recombinant_builders, dungeon_builders, entrances_map):
if recombine.master_sector is None: if recombine.master_sector is None:
recombine.master_sector = builder.master_sector recombine.master_sector = builder.master_sector
recombine.master_sector.name = recombine.name recombine.master_sector.name = recombine.name
recombine.pre_open_stonewall = builder.pre_open_stonewall recombine.pre_open_stonewalls = builder.pre_open_stonewalls
else: else:
recombine.master_sector.regions.extend(builder.master_sector.regions) recombine.master_sector.regions.extend(builder.master_sector.regions)
if builder.pre_open_stonewall: recombine.pre_open_stonewalls.update(builder.pre_open_stonewalls)
recombine.pre_open_stonewall = builder.pre_open_stonewall
recombine.layout_starts = list(entrances_map[recombine.name]) recombine.layout_starts = list(entrances_map[recombine.name])
dungeon_builders[recombine.name] = recombine dungeon_builders[recombine.name] = recombine
@@ -1609,6 +1657,7 @@ def change_door_to_small_key(d, world, player):
def smooth_door_pairs(world, player): def smooth_door_pairs(world, player):
all_doors = [x for x in world.doors if x.player == player] all_doors = [x for x in world.doors if x.player == player]
skip = set() skip = set()
bd_candidates, dashable_counts, bombable_counts = defaultdict(list), defaultdict(int), defaultdict(int)
for door in all_doors: for door in all_doors:
if door.type in [DoorType.Normal, DoorType.Interior] and door not in skip and not door.entranceFlag: if door.type in [DoorType.Normal, DoorType.Interior] and door not in skip and not door.entranceFlag:
partner = door.dest partner = door.dest
@@ -1636,11 +1685,8 @@ def smooth_door_pairs(world, player):
remove_pair(door, world, player) remove_pair(door, world, player)
elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]: elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]:
if valid_pair: if valid_pair:
if type_a == type_b: new_type = type_a
add_pair(door, partner, world, player) if type_a != type_b:
spoiler_type = 'Bomb Door' if type_a == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
else:
new_type = DoorKind.Dashable if type_a == DoorKind.Dashable or type_b == DoorKind.Dashable else DoorKind.Bombable new_type = DoorKind.Dashable if type_a == DoorKind.Dashable or type_b == DoorKind.Dashable else DoorKind.Bombable
if type_a != new_type: if type_a != new_type:
room_a.change(door.doorListPos, new_type) room_a.change(door.doorListPos, new_type)
@@ -1649,6 +1695,8 @@ def smooth_door_pairs(world, player):
add_pair(door, partner, world, player) add_pair(door, partner, world, player)
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door' spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player) world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
counter = bombable_counts if new_type == DoorKind.Bombable else dashable_counts
counter[door.entrance.parent_region.dungeon] += 1
else: else:
if type_a in [DoorKind.Bombable, DoorKind.Dashable]: if type_a in [DoorKind.Bombable, DoorKind.Dashable]:
room_a.change(door.doorListPos, DoorKind.Normal) room_a.change(door.doorListPos, DoorKind.Normal)
@@ -1656,8 +1704,9 @@ def smooth_door_pairs(world, player):
elif type_b in [DoorKind.Bombable, DoorKind.Dashable]: elif type_b in [DoorKind.Bombable, DoorKind.Dashable]:
room_b.change(partner.doorListPos, DoorKind.Normal) room_b.change(partner.doorListPos, DoorKind.Normal)
remove_pair(partner, world, player) remove_pair(partner, world, player)
elif world.experimental[player] and valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey: elif valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey:
random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b) bd_candidates[door.entrance.parent_region.dungeon].append(door)
shuffle_bombable_dashable(bd_candidates, bombable_counts, dashable_counts, world, player)
world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original] world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original]
@@ -1694,17 +1743,59 @@ def stateful_door(door, kind):
return False return False
def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b): def shuffle_bombable_dashable(bd_candidates, bombable_counts, dashable_counts, world, player):
r_kind = random.choices([DoorKind.Normal, DoorKind.Bombable, DoorKind.Dashable], [15, 4, 6], k=1)[0] if world.doorShuffle[player] == 'basic':
if r_kind != DoorKind.Normal: for dungeon, candidates in bd_candidates.items():
if door.type == DoorType.Normal: diff = bomb_dash_counts[dungeon.name][1] - dashable_counts[dungeon]
add_pair(door, partner, world, player) if diff > 0:
if type_a != r_kind: for chosen in random.sample(candidates, min(diff, len(candidates))):
room_a.change(door.doorListPos, r_kind) change_pair_type(chosen, DoorKind.Dashable, world, player)
if type_b != r_kind: candidates.remove(chosen)
room_b.change(partner.doorListPos, r_kind) diff = bomb_dash_counts[dungeon.name][0] - bombable_counts[dungeon]
spoiler_type = 'Bomb Door' if r_kind == DoorKind.Bombable else 'Dash Door' if diff > 0:
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player) for chosen in random.sample(candidates, min(diff, len(candidates))):
change_pair_type(chosen, DoorKind.Bombable, world, player)
candidates.remove(chosen)
for excluded in candidates:
remove_pair_type_if_present(excluded, world, player)
elif world.doorShuffle[player] == 'crossed':
all_candidates = sum(bd_candidates.values(), [])
all_bomb_counts = sum(bombable_counts.values())
all_dash_counts = sum(dashable_counts.values())
if all_dash_counts < 8:
for chosen in random.sample(all_candidates, min(8 - all_dash_counts, len(all_candidates))):
change_pair_type(chosen, DoorKind.Dashable, world, player)
all_candidates.remove(chosen)
if all_bomb_counts < 12:
for chosen in random.sample(all_candidates, min(12 - all_bomb_counts, len(all_candidates))):
change_pair_type(chosen, DoorKind.Bombable, world, player)
all_candidates.remove(chosen)
for excluded in all_candidates:
remove_pair_type_if_present(excluded, world, player)
def change_pair_type(door, new_type, world, player):
room_a = world.get_room(door.roomIndex, player)
room_a.change(door.doorListPos, new_type)
if door.type != DoorType.Interior:
room_b = world.get_room(door.dest.roomIndex, player)
room_b.change(door.dest.doorListPos, new_type)
add_pair(door, door.dest, world, player)
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(door.name + ' <-> ' + door.dest.name, spoiler_type, player)
def remove_pair_type_if_present(door, world, player):
room_a = world.get_room(door.roomIndex, player)
if room_a.kind(door) in [DoorKind.Bombable, DoorKind.Dashable]:
room_a.change(door.doorListPos, DoorKind.Normal)
if door.type != DoorType.Interior:
remove_pair(door, world, player)
if door.type != DoorType.Interior:
room_b = world.get_room(door.dest.roomIndex, player)
if room_b.kind(door.dest) in [DoorKind.Bombable, DoorKind.Dashable]:
room_b.change(door.dest.doorListPos, DoorKind.Normal)
remove_pair(door.dest, world, player)
def find_inaccessible_regions(world, player): def find_inaccessible_regions(world, player):
@@ -1727,7 +1818,8 @@ def find_inaccessible_regions(world, player):
queue.append(parent) queue.append(parent)
for ext in next_region.exits: for ext in next_region.exits:
connect = ext.connected_region connect = ext.connected_region
if connect and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions: if connect and connect not in queue and connect not in visited_regions:
if connect.type is not RegionType.Dungeon or connect.name.endswith(' Portal'):
queue.append(connect) queue.append(connect)
world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)]) world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)])
logger = logging.getLogger('') logger = logging.getLogger('')
@@ -1816,12 +1908,12 @@ def check_required_paths(paths, world, player):
if dungeon_name in world.dungeon_layouts[player].keys(): if dungeon_name in world.dungeon_layouts[player].keys():
builder = world.dungeon_layouts[player][dungeon_name] builder = world.dungeon_layouts[player][dungeon_name]
if len(paths[dungeon_name]) > 0: if len(paths[dungeon_name]) > 0:
states_to_explore = {} states_to_explore = defaultdict(list)
for path in paths[dungeon_name]: for path in paths[dungeon_name]:
if type(path) is tuple: if type(path) is tuple:
states_to_explore[tuple([path[0]])] = path[1] states_to_explore[tuple([path[0]])] = path[1]
else: else:
states_to_explore[tuple(builder.path_entrances)] = path states_to_explore[tuple(builder.path_entrances)].append(path)
cached_initial_state = None cached_initial_state = None
for start_regs, dest_regs in states_to_explore.items(): for start_regs, dest_regs in states_to_explore.items():
if type(dest_regs) is not list: if type(dest_regs) is not list:
@@ -1890,13 +1982,14 @@ def check_if_regions_visited(state, check_paths):
if state.visited_at_all(region_target): if state.visited_at_all(region_target):
valid = True valid = True
break break
else: elif not breaking_region:
breaking_region = region_target breaking_region = region_target
return valid, breaking_region return valid, breaking_region
def check_for_pinball_fix(state, bad_region, world, player): def check_for_pinball_fix(state, bad_region, world, player):
pinball_region = world.get_region('Skull Pinball', player) pinball_region = world.get_region('Skull Pinball', player)
# todo: lobby shuffle
if bad_region.name == 'Skull 2 West Lobby' and state.visited_at_all(pinball_region): # revisit this for entrance shuffle if bad_region.name == 'Skull 2 West Lobby' and state.visited_at_all(pinball_region): # revisit this for entrance shuffle
door = world.get_door('Skull Pinball WS', player) door = world.get_door('Skull Pinball WS', player)
room = world.get_room(door.roomIndex, player) room = world.get_room(door.roomIndex, player)
@@ -1919,7 +2012,7 @@ class DROptions(Flag):
Debug = 0x08 Debug = 0x08
Rails = 0x10 # If on, draws rails Rails = 0x10 # If on, draws rails
OriginalPalettes = 0x20 OriginalPalettes = 0x20
Reserved = 0x40 # Reserved for PoD sliding wall? Open_PoD_Wall = 0x40 # If on, pre opens the PoD wall, no bow required
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
@@ -1952,6 +2045,13 @@ logical_connections = [
('PoD Basement Ledge Drop Down', 'PoD Stalfos Basement'), ('PoD Basement Ledge Drop Down', 'PoD Stalfos Basement'),
('PoD Falling Bridge Path N', 'PoD Falling Bridge Ledge'), ('PoD Falling Bridge Path N', 'PoD Falling Bridge Ledge'),
('PoD Falling Bridge Path S', 'PoD Falling Bridge'), ('PoD Falling Bridge Path S', 'PoD Falling Bridge'),
('PoD Bow Statue Crystal Path', 'PoD Bow Statue Moving Wall'),
('PoD Bow Statue Moving Wall Path', 'PoD Bow Statue'),
('PoD Bow Statue Moving Wall Cane Path', 'PoD Bow Statue'),
('PoD Dark Pegs Hammer Path', 'PoD Dark Pegs Ladder'),
('PoD Dark Pegs Ladder Hammer Path', 'PoD Dark Pegs'),
('PoD Dark Pegs Ladder Cane Path', 'PoD Dark Pegs Switch'),
('PoD Dark Pegs Switch Path', 'PoD Dark Pegs Ladder'),
('Swamp Lobby Moat', 'Swamp Entrance'), ('Swamp Lobby Moat', 'Swamp Entrance'),
('Swamp Entrance Moat', 'Swamp Lobby'), ('Swamp Entrance Moat', 'Swamp Lobby'),
('Swamp Trench 1 Approach Dry', 'Swamp Trench 1 Nexus'), ('Swamp Trench 1 Approach Dry', 'Swamp Trench 1 Nexus'),
@@ -2780,6 +2880,14 @@ portal_map = {
'Ganons Tower': ('Ganons Tower', 'Ganons Tower Exit', 'Enter Ganons Tower'), 'Ganons Tower': ('Ganons Tower', 'Ganons Tower Exit', 'Enter Ganons Tower'),
} }
multiple_portal_map = {
'Hyrule Castle': ['Sanctuary', 'Hyrule Castle West', 'Hyrule Castle South', 'Hyrule Castle East'],
'Desert Palace': ['Desert West', 'Desert South', 'Desert East', 'Desert Back'],
'Skull Woods': ['Skull 1', 'Skull 2 West', 'Skull 2 East', 'Skull 3'],
'Turtle Rock': ['Turtle Rock Lazy Eyes', 'Turtle Rock Eye Bridge', 'Turtle Rock Chest', 'Turtle Rock Main'],
}
split_portals = { split_portals = {
'Desert Palace': ['Back', 'Main'], 'Desert Palace': ['Back', 'Main'],
'Skull Woods': ['1', '2', '3'] 'Skull Woods': ['1', '2', '3']
@@ -2800,4 +2908,20 @@ split_portal_defaults = {
} }
} }
bomb_dash_counts = {
'Hyrule Castle': (0, 2),
'Eastern Palace': (0, 0),
'Desert Palace': (0, 0),
'Agahnims Tower': (0, 0),
'Swamp Palace': (2, 0),
'Palace of Darkness': (3, 2),
'Misery Mire': (2, 0),
'Skull Woods': (2, 0),
'Ice Palace': (0, 0),
'Tower of Hera': (0, 0),
'Thieves Town': (1, 1),
'Turtle Rock': (0, 2), # 2 bombs kind of for entrances
'Ganons Tower': (2, 1)
}
+58 -38
View File
@@ -395,8 +395,15 @@ def create_doors(world, player):
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 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 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 SW', Intr).dir(So, 0x1b, Left, High).pos(0),
create_door(player, 'PoD Bow Statue Down Ladder', Lddr).no_entrance(), create_door(player, 'PoD Bow Statue Crystal Path', Lgcl),
create_door(player, 'PoD Dark Pegs Up Ladder', Lddr), create_door(player, 'PoD Bow Statue Moving Wall Path', Lgcl),
create_door(player, 'PoD Bow Statue Moving Wall Cane Path', Lgcl),
create_door(player, 'PoD Bow Statue Down Ladder', Lddr).dir(So, 0x1b, 1, High).no_entrance(),
create_door(player, 'PoD Dark Pegs Up Ladder', Lddr).dir(No, 0x0b, 0, High),
create_door(player, 'PoD Dark Pegs Hammer Path', Lgcl),
create_door(player, 'PoD Dark Pegs Ladder Hammer Path', Lgcl),
create_door(player, 'PoD Dark Pegs Ladder Cane Path', Lgcl),
create_door(player, 'PoD Dark Pegs Switch Path', Lgcl),
create_door(player, 'PoD Dark Pegs WN', Intr).dir(We, 0x0b, Mid, High).small_key().pos(2), create_door(player, 'PoD Dark Pegs WN', Intr).dir(We, 0x0b, Mid, High).small_key().pos(2),
create_door(player, 'PoD Lonely Turtle SW', Intr).dir(So, 0x0b, Mid, High).pos(0), create_door(player, 'PoD Lonely Turtle SW', Intr).dir(So, 0x0b, Mid, High).pos(0),
create_door(player, 'PoD Lonely Turtle EN', Intr).dir(Ea, 0x0b, Mid, High).small_key().pos(2), create_door(player, 'PoD Lonely Turtle EN', Intr).dir(Ea, 0x0b, Mid, High).small_key().pos(2),
@@ -663,7 +670,7 @@ def create_doors(world, player):
create_door(player, 'Ice Pengator Switch ES', Intr).dir(Ea, 0x1f, Bot, High).pos(1), create_door(player, 'Ice Pengator Switch ES', Intr).dir(Ea, 0x1f, Bot, High).pos(1),
create_door(player, 'Ice Dead End WS', Intr).dir(We, 0x1f, Bot, High).pos(1), create_door(player, 'Ice Dead End WS', Intr).dir(We, 0x1f, Bot, High).pos(1),
create_door(player, 'Ice Big Key Push Block', Lgcl), create_door(player, 'Ice Big Key Push Block', Lgcl),
create_door(player, 'Ice Big Key Down Ladder', Lddr), create_door(player, 'Ice Big Key Down Ladder', Lddr).dir(So, 0x1f, 3, High),
create_door(player, 'Ice Stalfos Hint SE', Intr).dir(So, 0x3e, Right, High).pos(0), 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 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).portal(Z, 0x20), create_door(player, 'Ice Conveyor SW', Nrml).dir(So, 0x3e, Left, High).small_key().pos(1).portal(Z, 0x20),
@@ -679,7 +686,7 @@ def create_doors(world, player):
create_door(player, 'Ice Spike Cross ES', Nrml).dir(Ea, 0x5e, Bot, High).small_key().pos(0), 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 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), create_door(player, 'Ice Firebar ES', Intr).dir(Ea, 0x5e, Bot, High).pos(3),
create_door(player, 'Ice Firebar Down Ladder', Lddr), create_door(player, 'Ice Firebar Down Ladder', Lddr).dir(So, 0x5e, 5, High),
create_door(player, 'Ice Spike Cross NE', Intr).dir(No, 0x5e, Right, High).pos(1), create_door(player, 'Ice Spike Cross NE', Intr).dir(No, 0x5e, Right, High).pos(1),
create_door(player, 'Ice Falling Square SE', Intr).dir(So, 0x5e, Right, High).no_exit().pos(1), create_door(player, 'Ice Falling Square SE', Intr).dir(So, 0x5e, Right, High).no_exit().pos(1),
create_door(player, 'Ice Falling Square Hole', Hole), create_door(player, 'Ice Falling Square Hole', Hole),
@@ -689,8 +696,8 @@ def create_doors(world, player):
create_door(player, 'Ice Hammer Block Down Stairs', Sprl).dir(Dn, 0x3f, 0, HTH).ss(Z, 0x11, 0xb8, True, True).kill(), create_door(player, 'Ice Hammer Block Down Stairs', Sprl).dir(Dn, 0x3f, 0, HTH).ss(Z, 0x11, 0xb8, True, True).kill(),
create_door(player, 'Ice Hammer Block ES', Intr).dir(Ea, 0x3f, Bot, High).pos(0), create_door(player, 'Ice Hammer Block ES', Intr).dir(Ea, 0x3f, Bot, High).pos(0),
create_door(player, 'Ice Tongue Pull WS', Intr).dir(We, 0x3f, Bot, High).pos(0), create_door(player, 'Ice Tongue Pull WS', Intr).dir(We, 0x3f, Bot, High).pos(0),
create_door(player, 'Ice Tongue Pull Up Ladder', Lddr), create_door(player, 'Ice Tongue Pull Up Ladder', Lddr).dir(No, 0x3f, 2, High),
create_door(player, 'Ice Freezors Up Ladder', Lddr), create_door(player, 'Ice Freezors Up Ladder', Lddr).dir(No, 0x7e, 4, High),
create_door(player, 'Ice Freezors Hole', Hole), create_door(player, 'Ice Freezors Hole', Hole),
create_door(player, 'Ice Freezors Bomb Hole', Hole), # combine these two? -- they have to lead to the same spot create_door(player, 'Ice Freezors Bomb Hole', Hole), # combine these two? -- they have to lead to the same spot
create_door(player, 'Ice Freezors Ledge Hole', Hole), create_door(player, 'Ice Freezors Ledge Hole', Hole),
@@ -1078,8 +1085,8 @@ def create_doors(world, player):
create_door(player, 'GT Torch Cross WN', Nrml).dir(We, 0x96, 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), create_door(player, 'GT Torch Cross ES', Intr).dir(Ea, 0x96, Bot, High).pos(0),
create_door(player, 'GT Staredown WS', Intr).dir(We, 0x96, Bot, High).pos(0), create_door(player, 'GT Staredown WS', Intr).dir(We, 0x96, Bot, High).pos(0),
create_door(player, 'GT Staredown Up Ladder', Lddr), create_door(player, 'GT Staredown Up Ladder', Lddr).dir(No, 0x96, 6, High),
create_door(player, 'GT Falling Torches Down Ladder', Lddr), create_door(player, 'GT Falling Torches Down Ladder', Lddr).dir(So, 0x3d, 7, High),
create_door(player, 'GT Falling Torches NE', Intr).dir(No, 0x3d, Right, High).pos(0), create_door(player, 'GT Falling Torches NE', Intr).dir(No, 0x3d, Right, High).pos(0),
create_door(player, 'GT Mini Helmasaur Room SE', Intr).dir(So, 0x3d, Right, High).pos(0), create_door(player, 'GT Mini Helmasaur Room SE', Intr).dir(So, 0x3d, Right, High).pos(0),
create_door(player, 'GT Falling Torches Hole', Hole), create_door(player, 'GT Falling Torches Hole', Hole),
@@ -1156,9 +1163,11 @@ def create_doors(world, player):
world.get_door('PoD Map Balcony WS', player).c_switch() world.get_door('PoD Map Balcony WS', player).c_switch()
world.get_door('PoD Map Balcony South Stairs', player).c_switch() world.get_door('PoD Map Balcony South Stairs', player).c_switch()
world.get_door('PoD Bow Statue SW', player).c_switch() world.get_door('PoD Bow Statue SW', player).c_switch()
world.get_door('PoD Bow Statue Down Ladder', player).c_switch() world.get_door('PoD Bow Statue Moving Wall Path', player).barrier(CrystalBarrier.Orange)
world.get_door('PoD Dark Pegs Up Ladder', player).c_switch() world.get_door('PoD Bow Statue Crystal Path', player).c_switch()
world.get_door('PoD Dark Pegs WN', player).c_switch() world.get_door('PoD Dark Pegs WN', player).c_switch()
world.get_door('PoD Dark Pegs Switch Path', player).c_switch()
world.get_door('PoD Dark Pegs Hammer Path', player).c_switch()
world.get_door('Swamp Crystal Switch EN', player).c_switch() world.get_door('Swamp Crystal Switch EN', player).c_switch()
world.get_door('Swamp Crystal Switch SE', player).c_switch() world.get_door('Swamp Crystal Switch SE', player).c_switch()
@@ -1271,36 +1280,11 @@ def create_doors(world, player):
assign_entrances(world, player) assign_entrances(world, player)
dungeon_portals = [ create_portals(world, player)
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
# static portal flags
world.get_door('Sanctuary S', player).dead_end(allowPassage=True) world.get_door('Sanctuary S', player).dead_end(allowPassage=True)
world.get_door('Eastern Hint Tile Blocked Path SE', player).passage = False
world.get_door('TR Big Chest Entrance SE', player).passage = False 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('Sewers Secret Room Key Door S', player).dungeonLink = 'Hyrule Castle'
world.get_door('Desert Cannonball S', player).dead_end() world.get_door('Desert Cannonball S', player).dead_end()
@@ -1336,6 +1320,42 @@ def create_doors(world, player):
world.get_door('Ice Conveyor SW', player).dungeonLink = 'linkIceFalls2' world.get_door('Ice Conveyor SW', player).dungeonLink = 'linkIceFalls2'
def create_portals(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
def reset_portals(world, player):
world.dungeon_portals[player].clear()
world._portal_cache.clear()
create_portals(world, player)
def create_paired_doors(world, player): def create_paired_doors(world, player):
world.paired_doors[player] = [ world.paired_doors[player] = [
PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N', True), PairedDoor('Sewers Secret Room Key Door S', 'Sewers Key Rat Key Door N', True),
+81 -40
View File
@@ -55,19 +55,21 @@ def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player): def generate_dungeon(builder, entrance_region_names, split_dungeon, world, player):
stonewall = check_for_stonewall(builder) stonewalls = check_for_stonewalls(builder)
sector = generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player) sector = generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player)
if stonewall and not stonewall_valid(stonewall): for stonewall in stonewalls:
builder.pre_open_stonewall = stonewall if not stonewall_valid(stonewall):
builder.pre_open_stonewalls.add(stonewall)
return sector return sector
def check_for_stonewall(builder): def check_for_stonewalls(builder):
stonewalls = set()
for sector in builder.sectors: for sector in builder.sectors:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
if door.stonewall: if door.stonewall:
return door stonewalls.add(door)
return None return stonewalls
def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player): def generate_dungeon_main(builder, entrance_region_names, split_dungeon, world, player):
@@ -226,7 +228,7 @@ def gen_dungeon_info(name, available_sectors, entrance_regions, all_regions, pro
o_state_cache = {} o_state_cache = {}
for sector in available_sectors: for sector in available_sectors:
for door in sector.outstanding_doors: for door in sector.outstanding_doors:
if not door.stonewall and door not in proposed_map.keys(): if door not in proposed_map.keys():
hanger_set.add(door) hanger_set.add(door)
bk_flag = group_flags[door_map[door]] bk_flag = group_flags[door_map[door]]
parent = door.entrance.parent_region parent = door.entrance.parent_region
@@ -418,8 +420,7 @@ def check_valid(name, dungeon, hangers, hooks, proposed_map, doors_to_connect, a
true_origin_hooks = [x for x in dungeon['Origin'].hooks.keys() if not x.bigKey or possible_bks > 0 or not bk_needed] 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): if len(true_origin_hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect):
return False return False
if len(true_origin_hooks) == 0 and bk_needed and possible_bks == 0 and len(proposed_map.keys()) == len( if len(true_origin_hooks) == 0 and bk_needed and possible_bks == 0 and len(proposed_map.keys()) == len(doors_to_connect):
doors_to_connect):
return False return False
for key in hangers.keys(): for key in hangers.keys():
if len(hooks[key]) > 0 and len(hangers[key]) == 0: if len(hooks[key]) > 0 and len(hangers[key]) == 0:
@@ -619,7 +620,7 @@ def stonewall_valid(stonewall):
return False # you can get stuck from an entrance return False # you can get stuck from an entrance
else: else:
door = entrance.door door = entrance.door
if door is not None and door != stonewall and not door.blocked and parent not in visited: if (door is None or (door != stonewall and not door.blocked)) and parent not in visited:
visited.add(parent) visited.add(parent)
queue.append(parent) queue.append(parent)
# we didn't find anything bad # we didn't find anything bad
@@ -694,17 +695,17 @@ hang_dir_map = {
def hanger_from_door(door): def hanger_from_door(door):
if door.type == DoorType.SpiralStairs: if door.type == DoorType.SpiralStairs:
return Hook.Stairs return Hook.Stairs
if door.type in [DoorType.Normal, DoorType.Open, DoorType.StraightStairs]: if door.type in [DoorType.Normal, DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
return hang_dir_map[door.direction] return hang_dir_map[door.direction]
return None return None
def connect_doors(a, b): def connect_doors(a, b):
# Return on unsupported types. # Return on unsupported types.
if a.type in [DoorType.Hole, DoorType.Warp, DoorType.Ladder, DoorType.Interior, DoorType.Logical]: if a.type in [DoorType.Hole, DoorType.Warp, DoorType.Interior, DoorType.Logical]:
return return
# Connect supported types # Connect supported types
if a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open, DoorType.StraightStairs]: if a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
if a.blocked: if a.blocked:
connect_one_way(b.entrance, a.entrance) connect_one_way(b.entrance, a.entrance)
elif b.blocked: elif b.blocked:
@@ -1171,7 +1172,7 @@ class DungeonBuilder(object):
self.path_entrances = None # used for pathing/key doors, I think self.path_entrances = None # used for pathing/key doors, I think
self.split_flag = False self.split_flag = False
self.pre_open_stonewall = None # used by stonewall system self.pre_open_stonewalls = set() # used by stonewall system
self.candidates = None self.candidates = None
self.key_doors_num = None self.key_doors_num = None
@@ -1225,15 +1226,15 @@ def simple_dungeon_builder(name, sector_list):
def create_dungeon_builders(all_sectors, connections_tuple, world, player, def create_dungeon_builders(all_sectors, connections_tuple, world, player,
dungeon_entrances=None, split_dungeon_entrances=None): dungeon_entrances=None, split_dungeon_entrances=None):
logger = logging.getLogger('') logger = logging.getLogger('')
logger.info('Shuffling Dungeon Sectors')
if dungeon_entrances is None: if dungeon_entrances is None:
dungeon_entrances = default_dungeon_entrances dungeon_entrances = default_dungeon_entrances
if split_dungeon_entrances is None: if split_dungeon_entrances is None:
split_dungeon_entrances = split_region_starts split_dungeon_entrances = split_region_starts
define_sector_features(all_sectors) define_sector_features(all_sectors)
finished, dungeon_map = False, {} finished, dungeon_map, attempts = False, {}, 0
while not finished: while not finished:
logger.info('Shuffling Dungeon Sectors')
candidate_sectors = dict.fromkeys(all_sectors) candidate_sectors = dict.fromkeys(all_sectors)
global_pole = GlobalPolarity(candidate_sectors) global_pole = GlobalPolarity(candidate_sectors)
@@ -1248,6 +1249,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
candidate_sectors, global_pole) candidate_sectors, global_pole)
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
entrances_map, potentials, connections = connections_tuple entrances_map, potentials, connections = connections_tuple
accessible_sectors, reverse_d_map = set(), {} accessible_sectors, reverse_d_map = set(), {}
for key in dungeon_entrances.keys(): for key in dungeon_entrances.keys():
@@ -1271,7 +1273,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections, identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections,
dungeon_entrances, split_dungeon_entrances) dungeon_entrances, split_dungeon_entrances)
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
calc_allowance_and_dead_ends(builder, connections_tuple, world.dungeon_portals[player]) calc_allowance_and_dead_ends(builder, connections_tuple, world, player)
if world.mode[player] == 'open' and world.shuffle[player] not in ['crossed', 'insanity']: if world.mode[player] == 'open' and world.shuffle[player] not in ['crossed', 'insanity']:
sanc = find_sector('Sanctuary', candidate_sectors) sanc = find_sector('Sanctuary', candidate_sectors)
@@ -1318,17 +1320,33 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
# restart # restart
raise NeutralizingException('Either free location/crystal assignment is already globally invalid') raise NeutralizingException('Either free location/crystal assignment is already globally invalid')
logger.info(world.fish.translate("cli", "cli", "balance.doors")) logger.info(world.fish.translate("cli", "cli", "balance.doors"))
builder_info = dungeon_entrances, split_dungeon_entrances, world, player builder_info = dungeon_entrances, split_dungeon_entrances, connections_tuple, world, player
assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builder_info) assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, builder_info)
# the rest # the rest
assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info) assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info)
dungeon_map.update(complete_dungeons) dungeon_map.update(complete_dungeons)
finished = True finished = True
except NeutralizingException: except (NeutralizingException, GenerationException) as e:
pass attempts += 1
logger.debug(f'Attempt {attempts} failed with {str(e)}')
if attempts >= 10:
raise Exception('Could not find a valid seed quickly, something is likely horribly wrong.', e)
return dungeon_map return dungeon_map
def standard_stair_check(dungeon_map, dungeon, candidate_sectors, global_pole):
# this is because there must be at least one non-dead stairway in hc to get out
# this check may not be necessary
filtered_sectors = [x for x in candidate_sectors if any(y for y in x.outstanding_doors if not y.dead and y.type == DoorType.SpiralStairs)]
valid = False
while not valid:
chosen_sector = random.choice(filtered_sectors)
filtered_sectors.remove(chosen_sector)
valid = global_pole.is_valid_choice(dungeon_map, dungeon, [chosen_sector])
if valid:
assign_sector(chosen_sector, dungeon, candidate_sectors, global_pole)
def identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections, dungeon_entrances, split_dungeon_entrances): def identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections, dungeon_entrances, split_dungeon_entrances):
accessible_overworld, found_connections, explored = set(), set(), False accessible_overworld, found_connections, explored = set(), set(), False
@@ -1376,16 +1394,21 @@ def identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map,
break break
def calc_allowance_and_dead_ends(builder, connections_tuple, portals): # todo: split version that adds allowance for potential entrances
def calc_allowance_and_dead_ends(builder, connections_tuple, world, player):
portals = world.dungeon_portals[player]
entrances_map, potentials, connections = connections_tuple entrances_map, potentials, connections = connections_tuple
needed_connections = [x for x in builder.all_entrances if x not in entrances_map[builder.name]] name = builder.name if not builder.split_flag else builder.name.rsplit(' ', 1)[0]
needed_connections = [x for x in builder.all_entrances if x not in entrances_map[name]]
starting_allowance = 0 starting_allowance = 0
used_sectors = set() used_sectors = set()
destination_entrances = [x.door.entrance.parent_region.name for x in portals if x.destination] destination_entrances = [x.door.entrance.parent_region.name for x in portals if x.destination]
for entrance in entrances_map[builder.name]: dead_ends = [x.door.entrance.parent_region.name for x in portals if x.deadEnd]
for entrance in entrances_map[name]:
sector = find_sector(entrance, builder.sectors) sector = find_sector(entrance, builder.sectors)
if sector:
outflow_target = 0 if entrance not in drop_entrances_allowance else 1 outflow_target = 0 if entrance not in drop_entrances_allowance else 1
if sector not in used_sectors and sector.adj_outflow() > outflow_target: if sector not in used_sectors and (sector.adj_outflow() > outflow_target or entrance in dead_ends):
if entrance not in destination_entrances: if entrance not in destination_entrances:
starting_allowance += 1 starting_allowance += 1
else: else:
@@ -1399,12 +1422,16 @@ def calc_allowance_and_dead_ends(builder, connections_tuple, portals):
builder.allowance = starting_allowance builder.allowance = starting_allowance
for entrance in needed_connections: for entrance in needed_connections:
sector = find_sector(entrance, builder.sectors) sector = find_sector(entrance, builder.sectors)
if sector not in used_sectors: # ignore things on same sector if sector and sector not in used_sectors: # ignore things on same sector
is_destination = entrance in destination_entrances is_destination = entrance in destination_entrances
connect_able = False connect_able = False
if entrance in connections.keys(): if entrance in connections.keys():
enabling_region = connections[entrance] enabling_region = connections[entrance]
connecting_entrances = [x for x in potentials[enabling_region] if x != entrance and x not in dead_entrances and x not in drop_entrances_allowance] check_list = list(potentials[enabling_region])
if enabling_region.name in ['Desert Ledge', 'Desert Palace Entrance (North) Spot']:
alternate = 'Desert Palace Entrance (North) Spot' if enabling_region.name == 'Desert Ledge' else 'Desert Ledge'
check_list.extend(potentials[world.get_region(alternate, player)])
connecting_entrances = [x for x in check_list if x != entrance and x not in dead_entrances and x not in drop_entrances_allowance]
connect_able = len(connecting_entrances) > 0 connect_able = len(connecting_entrances) > 0
if is_destination and sector.branches() == 0: # if is_destination and sector.branches() == 0: #
builder.dead_ends += 1 builder.dead_ends += 1
@@ -1566,7 +1593,11 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
valid = global_pole.is_valid_choice(dungeon_map, builder_choice, test_set) valid = global_pole.is_valid_choice(dungeon_map, builder_choice, test_set)
assign_sector(switch_choice, builder_choice, crystal_switches, global_pole) assign_sector(switch_choice, builder_choice, crystal_switches, global_pole)
return crystal_switches return crystal_switches
if len(crystal_switches) == 0:
raise GenerationException('No crystal switches to assign')
sector_list = list(crystal_switches) sector_list = list(crystal_switches)
if len(population) > len(sector_list):
raise GenerationException('Not enough crystal switch sectors for those needed')
choices = random.sample(sector_list, k=len(population)) choices = random.sample(sector_list, k=len(population))
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[population[i]] builder = dungeon_map[population[i]]
@@ -1577,7 +1608,7 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_sectors, crystal_barriers, global_pole): def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_sectors, crystal_barriers, global_pole):
invalid_builders = [] invalid_builders = []
for name, builder in dungeon_map.items(): for name, builder in dungeon_map.items():
if builder.c_switch_present and not builder.c_locked: if builder.c_switch_present and builder.c_switch_required and not builder.c_locked:
invalid_builders.append(builder) invalid_builders.append(builder)
while len(invalid_builders) > 0: while len(invalid_builders) > 0:
valid_builders = [] valid_builders = []
@@ -1586,7 +1617,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
reachable_crystals = defaultdict() reachable_crystals = defaultdict()
for sector in builder.sectors: for sector in builder.sectors:
if sector.equations is None: if sector.equations is None:
sector.equations = calc_sector_equations(sector, builder) sector.equations = calc_sector_equations(sector)
if sector.is_entrance_sector() and not sector.destination_entrance: if sector.is_entrance_sector() and not sector.destination_entrance:
need_switch = True need_switch = True
for region in sector.get_start_regions(): for region in sector.get_start_regions():
@@ -1620,7 +1651,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
valid, sector, which_list = False, None, None valid, sector, which_list = False, None, None
while not valid: while not valid:
if len(candidates) <= 0: if len(candidates) <= 0:
raise GenerationException(f'need to provide more sophisticatedted crystal connection for {entrance_sector}') raise GenerationException(f'need to provide more sophisticated crystal connection for {entrance_sector}')
sector, which_list = random.choice(list(candidates.items())) sector, which_list = random.choice(list(candidates.items()))
del candidates[sector] del candidates[sector]
valid = global_pole.is_valid_choice(dungeon_map, builder, [sector]) valid = global_pole.is_valid_choice(dungeon_map, builder, [sector])
@@ -1679,7 +1710,7 @@ def find_pol_cand_for_c_switch(access, reachable_crystals, polarized_candidates)
def pol_cand_matches_access_reach(sector, access, reachable_crystals): def pol_cand_matches_access_reach(sector, access, reachable_crystals):
if sector.equations is None: if sector.equations is None:
sector.equations = calc_sector_equations(sector, None) sector.equations = calc_sector_equations(sector)
for eq in sector.equations: for eq in sector.equations:
key, cost_door = eq.cost key, cost_door = eq.cost
if key in access.keys() and access[key]: if key in access.keys() and access[key]:
@@ -1701,7 +1732,7 @@ def find_crystal_cand(access, crystal_switches):
def crystal_cand_matches_access(sector, access): def crystal_cand_matches_access(sector, access):
if sector.equations is None: if sector.equations is None:
sector.equations = calc_sector_equations(sector, None) sector.equations = calc_sector_equations(sector)
for eq in sector.equations: for eq in sector.equations:
key, cost_door = eq.cost key, cost_door = eq.cost
if key in access.keys() and access[key] and eq.c_switch and len(sector.outstanding_doors) > 1: if key in access.keys() and access[key] and eq.c_switch and len(sector.outstanding_doors) > 1:
@@ -1973,6 +2004,9 @@ def polarity_step_3(dungeon_map, polarized_sectors, global_pole):
sample_target = 100 if combos > 10 else combos * 2 sample_target = 100 if combos > 10 else combos * 2
while best_choices is None or samples < sample_target: while best_choices is None or samples < sample_target:
samples += 1 samples += 1
if len(odd_candidates) < len(odd_builders):
raise GenerationException(f'Unable to fix dungeon parity - not enough candidates.'
f' Ref: {next(iter(odd_builders)).name}')
choices = random.sample(odd_candidates, k=len(odd_builders)) choices = random.sample(odd_candidates, k=len(odd_builders))
valid = global_pole.is_valid_multi_choice(dungeon_map, odd_builders, choices) valid = global_pole.is_valid_multi_choice(dungeon_map, odd_builders, choices)
charge = calc_total_charge(dungeon_map, odd_builders, choices) charge = calc_total_charge(dungeon_map, odd_builders, choices)
@@ -2633,7 +2667,7 @@ def valid_entrance(builder, sector_list, builder_info):
if len(builder.sectors) == 0: if len(builder.sectors) == 0:
is_dead_end = True is_dead_end = True
else: else:
entrances, splits, world, player = builder_info entrances, splits, c_tuple, world, player = builder_info
if builder.name not in entrances.keys(): if builder.name not in entrances.keys():
name_parts = builder.name.rsplit(' ', 1) name_parts = builder.name.rsplit(' ', 1)
entrance_list = splits[name_parts[0]][name_parts[1]] entrance_list = splits[name_parts[0]][name_parts[1]]
@@ -2646,8 +2680,8 @@ def valid_entrance(builder, sector_list, builder_info):
for sector in entrances: for sector in entrances:
for region in entrance_list: for region in entrance_list:
if region in sector.region_set(): if region in sector.region_set():
portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == region)) portal = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == region), None)
if not portal.deadEnd: if portal and not portal.deadEnd:
all_dead = False all_dead = False
break break
if not all_dead: if not all_dead:
@@ -2765,10 +2799,10 @@ def split_dungeon_builder(builder, split_list, builder_info):
continue continue
elif len(split_entrances) <= 0: elif len(split_entrances) <= 0:
continue continue
x, y, world, player = builder_info ents, splits, c_tuple, world, player = builder_info
r_name = split_entrances[0] r_name = split_entrances[0]
p = next(x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name) p = next((x for x in world.dungeon_portals[player] if x.door.entrance.parent_region.name == r_name), None)
if not p.deadEnd: if p and not p.deadEnd:
candidates.append(name) candidates.append(name)
merge_keys = random.sample(candidates, merge_attempt+1) if len(candidates) >= merge_attempt+1 else [] merge_keys = random.sample(candidates, merge_attempt+1) if len(candidates) >= merge_attempt+1 else []
for name, split_entrances in split_list.items(): for name, split_entrances in split_list.items():
@@ -2782,6 +2816,7 @@ def split_dungeon_builder(builder, split_list, builder_info):
sub_builder.all_entrances.extend(split_entrances) sub_builder.all_entrances.extend(split_entrances)
if key not in dungeon_map: if key not in dungeon_map:
dungeon_map[key] = sub_builder = DungeonBuilder(key) dungeon_map[key] = sub_builder = DungeonBuilder(key)
sub_builder.split_flag = True
sub_builder.all_entrances = list(split_entrances) sub_builder.all_entrances = list(split_entrances)
for r_name in split_entrances: for r_name in split_entrances:
assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole) assign_sector(find_sector(r_name, candidate_sectors), sub_builder, candidate_sectors, global_pole)
@@ -2799,6 +2834,9 @@ def split_dungeon_builder(builder, split_list, builder_info):
def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info): def balance_split(candidate_sectors, dungeon_map, global_pole, builder_info):
dungeon_entrances, split_dungeon_entrances, connections_tuple, world, player = builder_info
for name, builder in dungeon_map.items():
calc_allowance_and_dead_ends(builder, connections_tuple, world, player)
comb_w_replace = len(dungeon_map) ** len(candidate_sectors) comb_w_replace = len(dungeon_map) ** len(candidate_sectors)
if comb_w_replace <= 10000: if comb_w_replace <= 10000:
combinations = list(itertools.product(dungeon_map.keys(), repeat=len(candidate_sectors))) combinations = list(itertools.product(dungeon_map.keys(), repeat=len(candidate_sectors)))
@@ -2937,6 +2975,7 @@ def check_for_forced_crystal_single(builder, candidate_sectors):
for hook in builder_doors.keys(): for hook in builder_doors.keys():
for door in builder_doors[hook].keys(): for door in builder_doors[hook].keys():
opp = opposite_h_type(hook) opp = opposite_h_type(hook)
if opp in builder_doors.keys():
for d, sector in builder_doors[opp].items(): for d, sector in builder_doors[opp].items():
if d != door and (not sector.blue_barrier or sector.c_switch): if d != door and (not sector.blue_barrier or sector.c_switch):
return False return False
@@ -3217,7 +3256,7 @@ def identify_branching_issues(dungeon_map, builder_info):
def check_for_valid_layout(builder, sector_list, builder_info): def check_for_valid_layout(builder, sector_list, builder_info):
dungeon_entrances, split_dungeon_entrances, world, player = builder_info dungeon_entrances, split_dungeon_entrances, c_tuple, world, player = builder_info
if builder.name in split_dungeon_entrances.keys(): if builder.name in split_dungeon_entrances.keys():
try: try:
temp_builder = DungeonBuilder(builder.name) temp_builder = DungeonBuilder(builder.name)
@@ -3633,14 +3672,14 @@ def copy_door_equations(builder, sector_list):
for sector in builder.sectors + sector_list: for sector in builder.sectors + sector_list:
if sector.equations is None: if sector.equations is None:
# todo: sort equations? # todo: sort equations?
sector.equations = calc_sector_equations(sector, builder) sector.equations = calc_sector_equations(sector)
curr_list = equations[sector] = [] curr_list = equations[sector] = []
for equation in sector.equations: for equation in sector.equations:
curr_list.append(equation.copy()) curr_list.append(equation.copy())
return equations return equations
def calc_sector_equations(sector, builder): def calc_sector_equations(sector):
equations = [] equations = []
is_entrance = sector.is_entrance_sector() and not sector.destination_entrance is_entrance = sector.is_entrance_sector() and not sector.destination_entrance
if is_entrance: if is_entrance:
@@ -3670,6 +3709,8 @@ def calc_door_equation(door, sector, look_for_entrance):
eq.benefit[hook_from_door(door)].append(door) eq.benefit[hook_from_door(door)].append(door)
eq.required = True eq.required = True
eq.c_switch = door.crystal == CrystalBarrier.Either eq.c_switch = door.crystal == CrystalBarrier.Either
# exceptions for long entrances ???
# if door.name in ['PoD Dark Alley']:
eq.entrance_flag = True eq.entrance_flag = True
return eq, flag return eq, flag
eq = DoorEquation(door) eq = DoorEquation(door)
+2 -2
View File
@@ -213,8 +213,8 @@ pod_regions = [
'PoD Map Balcony', 'PoD Conveyor', 'PoD Mimics 1', 'PoD Jelly Hall', 'PoD Warp Hint', 'PoD Warp Room', 'PoD Map Balcony', 'PoD Conveyor', 'PoD Mimics 1', 'PoD Jelly Hall', 'PoD Warp Hint', 'PoD Warp Room',
'PoD Stalfos Basement', 'PoD Basement Ledge', 'PoD Big Key Landing', 'PoD Falling Bridge', 'PoD Stalfos Basement', 'PoD Basement Ledge', 'PoD Big Key Landing', 'PoD Falling Bridge',
'PoD Falling Bridge Ledge', 'PoD Dark Maze', 'PoD Big Chest Balcony', 'PoD Compass Room', 'PoD Dark Basement', 'PoD Falling Bridge Ledge', 'PoD Dark Maze', 'PoD Big Chest Balcony', 'PoD Compass Room', 'PoD Dark Basement',
'PoD Harmless Hellway', 'PoD Mimics 2', 'PoD Bow Statue', 'PoD Dark Pegs', 'PoD Lonely Turtle', 'PoD Turtle Party', 'PoD Harmless Hellway', 'PoD Mimics 2', 'PoD Bow Statue', 'PoD Bow Statue Moving Wall', 'PoD Dark Pegs', 'PoD Dark Pegs Ladder', 'PoD Dark Pegs Switch',
'PoD Dark Alley', 'PoD Callback', 'PoD Boss', 'Palace of Darkness Portal' 'PoD Lonely Turtle', 'PoD Turtle Party', 'PoD Dark Alley', 'PoD Callback', 'PoD Boss', 'Palace of Darkness Portal'
] ]
swamp_regions = [ swamp_regions = [
+3 -3
View File
@@ -3616,7 +3616,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
('Sanctuary Grave', 'Sewer Drop'), ('Sanctuary Grave', 'Sewer Drop'),
('Sanctuary Exit', 'Sanctuary Area'), ('Sanctuary Exit', 'Sanctuary Area'),
('Old Man Cave (West)', 'Old Man Cave'), ('Old Man Cave (West)', 'Old Man Cave Ledge'),
('Old Man Cave (East)', 'Old Man Cave'), ('Old Man Cave (East)', 'Old Man Cave'),
('Old Man Cave Exit (West)', 'Death Mountain Entrance'), ('Old Man Cave Exit (West)', 'Death Mountain Entrance'),
('Old Man Cave Exit (East)', 'West Death Mountain (Bottom)'), ('Old Man Cave Exit (East)', 'West Death Mountain (Bottom)'),
@@ -3762,7 +3762,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
('Two Brothers House (West)', 'Two Brothers House'), ('Two Brothers House (West)', 'Two Brothers House'),
('Two Brothers House Exit (East)', 'Light World'), ('Two Brothers House Exit (East)', 'Light World'),
('Two Brothers House Exit (West)', 'Maze Race Ledge'), ('Two Brothers House Exit (West)', 'Maze Race Ledge'),
('Sanctuary', 'Sanctuary'), ('Sanctuary', 'Sanctuary Portal'),
('Sanctuary Grave', 'Sewer Drop'), ('Sanctuary Grave', 'Sewer Drop'),
('Sanctuary Exit', 'Light World'), ('Sanctuary Exit', 'Light World'),
('Old Man House (Bottom)', 'Old Man House'), ('Old Man House (Bottom)', 'Old Man House'),
@@ -3833,7 +3833,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
('Old Man Cave Exit (West)', 'West Dark World'), ('Old Man Cave Exit (West)', 'West Dark World'),
('Old Man Cave Exit (East)', 'Dark Death Mountain'), ('Old Man Cave Exit (East)', 'Dark Death Mountain'),
('Dark Death Mountain Fairy', 'Old Man Cave'), ('Dark Death Mountain Fairy', 'Old Man Cave'),
('Bumper Cave (Bottom)', 'Old Man Cave'), ('Bumper Cave (Bottom)', 'Old Man Cave Ledge'),
('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'), ('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'),
('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'), ('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'),
('Bumper Cave Exit (Bottom)', 'Light World'), ('Bumper Cave Exit (Bottom)', 'Light World'),
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
import json import json
import os import os
import sys import sys
+6
View File
@@ -749,6 +749,12 @@ def fill_specific_items(world):
all_state = world.get_all_state(True) all_state = world.get_all_state(True)
fill_restrictive(world, all_state, [cage], [key_item]) fill_restrictive(world, all_state, [cage], [key_item])
location = world.get_location('Tower of Hera - Map Chest', 1)
key_item = next(x for x in world.itempool if 'Byrna' in x.name)
world.itempool.remove(key_item)
fast_fill(world, [key_item], [location])
# somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria') # somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria')
# shooter = world.get_location('Palace of Darkness - Shooter Room', 1) # shooter = world.get_location('Palace of Darkness - Shooter Room', 1)
# world.itempool.remove(somaria) # world.itempool.remove(somaria)
+15 -7
View File
@@ -335,8 +335,9 @@ def adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_coun
test_set = None test_set = None
needed = rule.needed_keys_w_bk needed = rule.needed_keys_w_bk
if needed > 0: if needed > 0:
accessible_loc.update(key_counter.other_locations) all_accessible = set(accessible_loc)
blocked_loc = key_layout.all_locations-accessible_loc all_accessible.update(key_counter.other_locations)
blocked_loc = key_layout.all_locations-all_accessible
for location in blocked_loc: for location in blocked_loc:
if location not in key_logic.location_rules.keys(): if location not in key_logic.location_rules.keys():
loc_rule = LocationRule() loc_rule = LocationRule()
@@ -373,11 +374,16 @@ def refine_placement_rules(key_layout, max_ctr):
rule.needed_keys_w_bk -= len(key_onlys) rule.needed_keys_w_bk -= len(key_onlys)
if rule.needed_keys_w_bk == 0: if rule.needed_keys_w_bk == 0:
rules_to_remove.append(rule) rules_to_remove.append(rule)
if rule.bk_relevant and len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1: # todo: evaluate this usage
new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk # if rule.bk_relevant and len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1:
if len(new_restricted - key_logic.bk_restricted) > 0: # new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk
key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations # if len(new_restricted | key_logic.bk_restricted) < len(key_layout.all_chest_locations):
changed = True # if len(new_restricted - key_logic.bk_restricted) > 0:
# key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations
# changed = True
# else:
# rules_to_remove.append(rule)
# changed = True
if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk: if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk:
logging.getLogger('').warning('Invalid rule - what went wrong here??') logging.getLogger('').warning('Invalid rule - what went wrong here??')
rules_to_remove.append(rule) rules_to_remove.append(rule)
@@ -501,6 +507,8 @@ def find_bk_locked_sections(key_layout, world, player):
key_layout.all_chest_locations.update(counter.free_locations) key_layout.all_chest_locations.update(counter.free_locations)
key_layout.item_locations.update(counter.free_locations) key_layout.item_locations.update(counter.free_locations)
key_layout.item_locations.update(counter.key_only_locations) key_layout.item_locations.update(counter.key_only_locations)
key_layout.all_locations.update(key_layout.item_locations)
key_layout.all_locations.update(counter.other_locations)
if counter.big_key_opened and counter.important_location: if counter.big_key_opened and counter.important_location:
big_chest_allowed_big_key = False big_chest_allowed_big_key = False
if not counter.big_key_opened: if not counter.big_key_opened:
+5 -10
View File
@@ -17,7 +17,7 @@ from InvertedRegions import create_inverted_regions, mark_dark_world_regions
from EntranceShuffle import link_entrances, link_inverted_entrances 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 Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
from Doors import create_doors from Doors import create_doors
from DoorShuffle import link_doors, connect_portal_copy from DoorShuffle import link_doors, connect_portal
from RoomData import create_rooms from RoomData import create_rooms
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
@@ -25,7 +25,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.2.0.17u' __version__ = '0.3.0.2-u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass
@@ -114,7 +114,7 @@ def main(args, seed=None, fish=None):
create_dungeons(world, player) create_dungeons(world, player)
adjust_locations(world, player) adjust_locations(world, player)
if any(world.potshuffle): if any(world.potshuffle.values()):
logger.info(world.fish.translate("cli", "cli", "shuffling.pots")) logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
if world.potshuffle[player]: if world.potshuffle[player]:
@@ -222,9 +222,6 @@ def main(args, seed=None, fish=None):
if use_enemizer: if use_enemizer:
base_patch = LocalRom(args.rom) # update base2current.json base_patch = LocalRom(args.rom) # update base2current.json
if use_enemizer:
base_patch = LocalRom(args.rom) # update base2current.json
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom) rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
if use_enemizer and (args.enemizercli or not args.jsonout): if use_enemizer and (args.enemizercli or not args.jsonout):
@@ -436,9 +433,7 @@ def copy_world(world):
copied_region.is_light_world = region.is_light_world copied_region.is_light_world = region.is_light_world
copied_region.is_dark_world = region.is_dark_world copied_region.is_dark_world = region.is_dark_world
copied_region.dungeon = region.dungeon copied_region.dungeon = region.dungeon
copied_region.locations = [copy.copy(location) for location in region.locations] copied_region.locations = [ret.get_location(location.name, location.player) for location in region.locations]
for location in copied_region.locations:
location.parent_region = copied_region
for entrance in region.entrances: for entrance in region.entrances:
ret.get_entrance(entrance.name, entrance.player).connect(copied_region) ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
@@ -482,7 +477,7 @@ def copy_world(world):
ret.dungeon_portals = world.dungeon_portals ret.dungeon_portals = world.dungeon_portals
for player, portals in world.dungeon_portals.items(): for player, portals in world.dungeon_portals.items():
for portal in portals: for portal in portals:
connect_portal_copy(portal, ret, player) connect_portal(portal, ret, player)
ret.sanc_portal = world.sanc_portal ret.sanc_portal = world.sanc_portal
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
+28 -9
View File
@@ -45,7 +45,7 @@ Doors are not shuffled.
#### Level 1 #### Level 1
Normal door and spiral staircases are shuffled Normal door and spiral staircases are shuffled
#### Level 2 #### Level 2
Same as Level 1 plus open edges and straight staircases are shuffled. Same as Level 1 plus open edges and both types of straight staircases are shuffled.
#### Level 3 #### Level 3
Same as Level 2 plus Dungeon Lobbies are shuffled Same as Level 2 plus Dungeon Lobbies are shuffled
@@ -60,9 +60,10 @@ Key drop location are added to the pool. The keys normally found there are added
### Mixed Travel (--mixed_travel value) ### 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 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 otherwise unconnected logically can be reached using these glitches. To prevent the player from unintentionally changing
dungeons while doing these tricks, you may use one of the following options.
#### Prevent #### Prevent (default)
Rails are added the 3 spots to prevent this tricks. This setting is recommend for those learning crossed dungeon mode to 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. learn what is dangerous and what is not. No logic seeds ignore this setting.
@@ -73,17 +74,17 @@ The rooms are left alone and it is up to the discretion of the player whether to
#### Force #### Force
The two disjointed sections are forced to be in the same dungeon but never logically required to complete that game. The two disjointed sections are forced to be in the same dungeon but the glitches are never logically required to complete that game.
### Standardize Palettes (--standardize_palettes) ### Standardize Palettes (--standardize_palettes)
No effect if door shuffle is not on crossed No effect if door shuffle is not on crossed
#### Standardize #### Standardize (default)
Rooms in the same dungeon have their palettes changed to match. Hyrule Castle is split between Sewer and HC palette. Rooms in the same dungeon have their palettes changed to match. Hyrule Castle is split between Sewer and HC palette.
Rooms adjacent to sanctuary get their coloring to match sanc. Rooms adjacent to sanctuary get their coloring to match the Sanctuary's original palette.
#### Original #### Original
Room keep their original palettes. Rooms/supertiles keep their original palettes.
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity) ## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)
@@ -121,13 +122,31 @@ Use to batch generate multiple seeds with same settings. If a seed number is pro
Show the help message and exit. Show the help message and exit.
``` ```
--door_shuffle --door_shuffle <mode>
``` ```
For specifying the door shuffle you want as above. (default: basic) For specifying the door shuffle you want as above. (default: basic)
``` ```
--intensity --intensity <number>
``` ```
For specifying the door shuffle intensity level you want as above. (default: 2) For specifying the door shuffle intensity level you want as above. (default: 2)
```
--keydropshuffle
```
Include mobs and pots drop in the item pool. (default: not enabled)
```
--mixed_travel <mode>
```
How to handle certain glitches in crossed dungeon mode. (default: prevent)
```
--standardize_palettes (mode)
```
Whether to standardize dungeon palettes in crossed dungeon mode. (default: standardize)
+12 -147
View File
@@ -1,157 +1,22 @@
# New Features # New Features
## Lobby shuffle added as Intensity level 3 ## In-Room Staircases/Ladders
* Standard notes: In intensity level 2 and higher the in-floor staircases/ladders that take you between tiles can now be shuffled with
* The sanctuary is vanilla, and will be missing the exit door until Zelda is rescued any N/S connections. (those that appear to go up one floor are North connection and those that go down are South ones)
* In entrance shuffle the hyrule castle left and right exit door will be missing until Zelda is rescued. This
replaces the rails that used to block those lobby exits
* In non-entrance shuffle, Agahnims tower can be in logic if you have cape and/or Master sword, but you are never
required to beat Agahnim 1 until Zelda is rescued.
* Open notes:
* The Sanctuary is limited to be in a LW dungeon unless you have ER Crossed or higher enabled
* Mirroring from the Sanctuary to the new "Sanctuary" lobby is now in logic, as is exiting there.
* In ER crossed or higher, if the Sanctuary is in the Dark World, Link starts as Bunny there until the Moon Pearl
is found. Nothing inside that dungeon is in logic until the Moon Pearl is found. (Unless it is a multi-entrance
dungeon that you can access from some LW entrance)
* Lobby list is found in the spoiler
* Exits for Multi-entrance dungeons after beating bosses now makes more sense. Generally you'll exit from a entrance
from which the boss can logically be reached. If there are multiple, ones that do not lead to regions only accessible
by connector are preferred. The exit is randomly chosen if there's no obvious preference. However, In certain poor
cases like Skull Woods in ER, sometimes an exit is chosen not because you can reach the boss from there, but to
prevent a potential forced S&Q.
* Palette changes:
* Certain doors/transition no longer have an effect on the palette choice (dead ends mostly or just bridges)
* Sanctuary palette used on the adjacent rooms to Sanctuary (Sanctuary stays the dungeon color for now)
* Sewer palette comes back for part of Hyrule Castle for areas "near" the sewer dropdown
* There is a setting to keep original palettes (--standardize_palettes original)
* Known issues:
* Palettes aren't perfect
* Some ugly colors
* Invisible floors can be see in many palettes
## Key Drop Shuffle Big thanks to Catobat for doing all the hard work.
--keydropshuffle added. 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
* Minor change: if a key is Universal or for that dungeon, then if will use the old mechanics of picking up the key without
an entire pose and should be obtainable with the hookshot or boomerang as before
## --mixed_travel setting
* 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.
## Keysanity menu redesign
Redesign of Keysanity Menu complete for crossed dungeon and moved out of experimental.
* First screen about Big Keys and Small Keys
* 1st Column: The map is required for information about the Big Key
* If you don't have the map, it'll be blank until you obtain the Big Key
* If have the map:
* 0 indicates there is no Big Key for that dungeon
* A red symbol indicates the Ball N Chain guard has the big key for that dungeon (does not apply in
--keydropshuffle)
* Blank if there a big key but you haven't found it yet
* 2nd Column displays the current number of keys for that dungeon. Suppressed in retro (always blank)
* 3rd Column only display if you have the map. It shows the number of keys left to collect for that dungeon. If
--keydropshuffle is off, this does not count key drops. If on, it does.
* (Note: the key columns can display up to 36 using the letters A-Z after 9)
* Second screen about Maps / Compass
* 1st Column: indicate if you have foudn the map of not for that dungeon
* 2nd and 3rd Column: You must have the compass to see these columns. A two-digit display that show you how
many chests are left in the dungeon. If -keydropshuffle is off, this does not count key drop. If on, it does.
## Potshuffle by compiling
Same flag as before but uses python logic written by compiling instead of the enemizer logic-less version. Needs some
testing to verify logic is all good.
## Other features
### Spoiler log improvements
* In crossed mode, the new dungeon is listed along with the location designated by a '@' sign
* Random gt crystals and ganon crystal are noted in the settings for better reproduction of seeds
### Experimental features
* Only the random bomb doors and the item counter are currently experimental
* Item counter is suppressed in Triforce Hunt
#### Temporary debug features
* Removed the red square in the upper right corner of the hud if the castle gate is closed
# Bug Fixes # Bug Fixes
* 2.0.17u * 0.3.0.1-u
* Generation improvements * Problem with lobbies on re-rolls corrected
* 2.0.16u * Potential playthrough problem addressed
* Prevent HUD from showing key counter when in the overworld. (Aga 2 doesn't always clear the dungeon indicator) * 0.3.0.0-u
* Fixed key logic regarding certain isolated "important" locations * Generation improvements. Basic >95% success. Crossed >80% success.
* Fixed a problem with keydropshuffle thinking certain progression items are keys * Possible increased generation times as certain generation problem tries a partial re-roll
* A couple of inverted rules fixed
* A more accurate count of which locations are blocked by teh big key in Ganon's Tower
* Updated base rom to 31.0.7 (includes potential hera basement cage fix)
* 2.0.15u
* Allow Aga Tower lobby door as a a paired keydoor (typo)
* Fix portal check for multi-entrance dungeons
* 2.0.14u
* Removal of key doors no longer messes up certain lobbies
* Fixed ER entrances when Desert Back is a connector
* 2.0.13u
* Minor portal re-work for certain logic and spoiler information
* Repaired certain exits wrongly affected by Sanctuary placement (ER crossed + intensity 3)
* Fix for inverted ER + intensity 3
* Fix for current small keys missing on keysanity menu
* Logic added for cases where you can flood Swamp Trench 1 before finding flippers and lock yourself out of getting
something behind the trench that leads to the flippers
* 2.0.12u
* Another fix for animated tiles (fairy fountains)
* GT Big Key stat fixed on credits
* Any denomination of rupee 20 or below can be removed to make room for Crossed Dungeon's extra dungeon items. This
helps retro generate more often.
* Fix for TR Lobbies in intensity 3 and ER shuffles that was causing a hardlock
* Standard ER logic revised for lobby shuffle and rain state considerations.
* 2.0.11u
* Fix output path setting in settings.json
* Fix trock entrances when intensity <= 2
* 2.0.10u
* Fix POD, TR, GT and SKULL 3 entrances if sanc ends up in that dungeon in crossed ER+
* TR Lobbies that need a bomb and can be entered before bombing should be pre-opened
* Animated tiles are loaded correctly in lobbies
* If a wallmaster grabs you and the lobby is dark, the lamp turns on now
* Certain key rules no longer override item requirements (e.g. Somaria behind TR Hub)
* Old Man Cave is correctly one way in the graph
* Some key logic fixes
* 2.0.9-u
* /missing command in MultiClient fixed
* 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 # Known Issues
* Multiworld = /missing command not working * Potential keylocks in multi-entrance dungeons
* Potenial keylocks in multi-entrance dungeons * Incorrect vanilla key logic for Mire
* Incorrect vanilla keylogic for Mire
* ER - Potential for Skull Woods West to be completely inaccessible in non-beatable logic
+10 -6
View File
@@ -511,8 +511,11 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'PoD Dark Basement', 'Palace of Darkness', ['Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'], ['PoD Dark Basement W Up Stairs', 'PoD Dark Basement E Up Stairs']), create_dungeon_region(player, 'PoD Dark Basement', 'Palace of Darkness', ['Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'], ['PoD Dark Basement W Up Stairs', 'PoD Dark Basement E Up Stairs']),
create_dungeon_region(player, 'PoD Harmless Hellway', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway'], ['PoD Harmless Hellway NE', 'PoD Harmless Hellway SE']), create_dungeon_region(player, 'PoD Harmless Hellway', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway'], ['PoD Harmless Hellway NE', 'PoD Harmless Hellway SE']),
create_dungeon_region(player, 'PoD Mimics 2', 'Palace of Darkness', None, ['PoD Mimics 2 SW', 'PoD Mimics 2 NW']), create_dungeon_region(player, 'PoD Mimics 2', 'Palace of Darkness', None, ['PoD Mimics 2 SW', 'PoD Mimics 2 NW']),
create_dungeon_region(player, 'PoD Bow Statue', 'Palace of Darkness', None, ['PoD Bow Statue SW', 'PoD Bow Statue Down Ladder']), create_dungeon_region(player, 'PoD Bow Statue', 'Palace of Darkness', None, ['PoD Bow Statue SW', 'PoD Bow Statue Crystal Path']),
create_dungeon_region(player, 'PoD Dark Pegs', 'Palace of Darkness', None, ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs WN']), create_dungeon_region(player, 'PoD Bow Statue Moving Wall', 'Palace of Darkness', None, ['PoD Bow Statue Moving Wall Path', 'PoD Bow Statue Down Ladder', 'PoD Bow Statue Moving Wall Cane Path']),
create_dungeon_region(player, 'PoD Dark Pegs', 'Palace of Darkness', None, ['PoD Dark Pegs Hammer Path', 'PoD Dark Pegs WN']),
create_dungeon_region(player, 'PoD Dark Pegs Ladder', 'Palace of Darkness', None, ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs Ladder Hammer Path', 'PoD Dark Pegs Ladder Cane Path']),
create_dungeon_region(player, 'PoD Dark Pegs Switch', 'Palace of Darkness', None, ['PoD Dark Pegs Switch Path']),
create_dungeon_region(player, 'PoD Lonely Turtle', 'Palace of Darkness', None, ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN']), create_dungeon_region(player, 'PoD Lonely Turtle', 'Palace of Darkness', None, ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN']),
create_dungeon_region(player, 'PoD Turtle Party', 'Palace of Darkness', None, ['PoD Turtle Party ES', 'PoD Turtle Party NW']), create_dungeon_region(player, 'PoD Turtle Party', 'Palace of Darkness', None, ['PoD Turtle Party ES', 'PoD Turtle Party NW']),
create_dungeon_region(player, 'PoD Dark Alley', 'Palace of Darkness', None, ['PoD Dark Alley NE']), create_dungeon_region(player, 'PoD Dark Alley', 'Palace of Darkness', None, ['PoD Dark Alley NE']),
@@ -555,9 +558,9 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Swamp Right Elbow', 'Swamp Palace', None, ['Swamp Right Elbow SE', 'Swamp Right Elbow Down Stairs']), create_dungeon_region(player, 'Swamp Right Elbow', 'Swamp Palace', None, ['Swamp Right Elbow SE', 'Swamp Right Elbow Down Stairs']),
create_dungeon_region(player, 'Swamp Drain Left', 'Swamp Palace', None, ['Swamp Drain Left Up Stairs', 'Swamp Drain WN']), create_dungeon_region(player, 'Swamp Drain Left', 'Swamp Palace', None, ['Swamp Drain Left Up Stairs', 'Swamp Drain WN']),
create_dungeon_region(player, 'Swamp Drain Right', 'Swamp Palace', ['Swamp Drain'], ['Swamp Drain Right Switch', 'Swamp Drain Right Up Stairs']), create_dungeon_region(player, 'Swamp Drain Right', 'Swamp Palace', ['Swamp Drain'], ['Swamp Drain Right Switch', 'Swamp Drain Right Up Stairs']),
# This is intentionally odd so I don't have to treat the WS door in the Flooded Room oddly (because of how it works when going backward) create_dungeon_region(player, 'Swamp Flooded Room', 'Swamp Palace', None, ['Swamp Flooded Room Up Stairs', 'Swamp Flooded Room Ladder']),
create_dungeon_region(player, 'Swamp Flooded Room', 'Swamp Palace', None, ['Swamp Flooded Room Up Stairs', 'Swamp Flooded Room Ladder', 'Swamp Flooded Room WS']), # this is more normal and allows getting the chests from doing this room backward in logic
create_dungeon_region(player, 'Swamp Flooded Spot', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right'], ['Swamp Flooded Spot Ladder']), create_dungeon_region(player, 'Swamp Flooded Spot', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right'], ['Swamp Flooded Room WS', 'Swamp Flooded Spot Ladder']),
create_dungeon_region(player, 'Swamp Basement Shallows', 'Swamp Palace', None, ['Swamp Basement Shallows NW', 'Swamp Basement Shallows EN', 'Swamp Basement Shallows ES']), create_dungeon_region(player, 'Swamp Basement Shallows', 'Swamp Palace', None, ['Swamp Basement Shallows NW', 'Swamp Basement Shallows EN', 'Swamp Basement Shallows ES']),
create_dungeon_region(player, 'Swamp Waterfall Room', 'Swamp Palace', ['Swamp Palace - Waterfall Room'], ['Swamp Waterfall Room SW', 'Swamp Waterfall Room NW', 'Swamp Waterfall Room NE']), create_dungeon_region(player, 'Swamp Waterfall Room', 'Swamp Palace', ['Swamp Palace - Waterfall Room'], ['Swamp Waterfall Room SW', 'Swamp Waterfall Room NW', 'Swamp Waterfall Room NE']),
create_dungeon_region(player, 'Swamp Refill', 'Swamp Palace', None, ['Swamp Refill SW']), create_dungeon_region(player, 'Swamp Refill', 'Swamp Palace', None, ['Swamp Refill SW']),
@@ -872,7 +875,8 @@ def create_dungeon_regions(world, player):
world.get_region('PoD Arena Main', player).crystal_switch = True world.get_region('PoD Arena Main', player).crystal_switch = True
world.get_region('PoD Arena Bridge', player).crystal_switch = True # RANGED Weapon Required world.get_region('PoD Arena Bridge', player).crystal_switch = True # RANGED Weapon Required
world.get_region('PoD Sexy Statue', player).crystal_switch = True world.get_region('PoD Sexy Statue', player).crystal_switch = True
world.get_region('PoD Bow Statue', player).crystal_switch = True # LADDER not accessible (maybe with cane) world.get_region('PoD Bow Statue', player).crystal_switch = True
world.get_region('PoD Dark Pegs Switch', player).crystal_switch = True
world.get_region('PoD Dark Pegs', player).crystal_switch = True world.get_region('PoD Dark Pegs', player).crystal_switch = True
world.get_region('Swamp Crystal Switch', player).crystal_switch = True world.get_region('Swamp Crystal Switch', player).crystal_switch = True
world.get_region('Thieves Spike Switch', player).crystal_switch = True world.get_region('Thieves Spike Switch', player).crystal_switch = True
+37 -22
View File
@@ -27,7 +27,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '185a0f74ab0e2ce8899c8d7d309ca68c' RANDOMIZERBASEHASH = 'bffd4e834049ca5f5295601436fc6009'
class JsonRom(object): class JsonRom(object):
@@ -284,14 +284,23 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_
with open(options_path, 'w') as f: with open(options_path, 'w') as f:
json.dump(options, f) json.dump(options, f)
subprocess.check_call([os.path.abspath(enemizercli), try:
subprocess.run([os.path.abspath(enemizercli),
'--rom', baserom_path, '--rom', baserom_path,
'--seed', str(world.rom_seeds[player]), '--seed', str(world.rom_seeds[player]),
'--base', basepatch_path, '--base', basepatch_path,
'--randomizer', randopatch_path, '--randomizer', randopatch_path,
'--enemizer', options_path, '--enemizer', options_path,
'--output', enemizer_output_path], '--output', enemizer_output_path],
cwd=os.path.dirname(enemizercli), stdout=subprocess.DEVNULL) cwd=os.path.dirname(enemizercli),
check=True,
capture_output=True)
except subprocess.CalledProcessError as e:
from Main import EnemizerError
enemizerMsg = world.fish.translate("cli","cli","Enemizer returned exit code: ") + str(e.returncode) + "\n"
enemizerMsg += world.fish.translate("cli","cli","enemizer.nothing.applied")
logging.error(f'Enemizer error output: {e.stderr.decode("utf-8")}\n')
raise EnemizerError(enemizerMsg)
with open(enemizer_basepatch_path, 'r') as f: with open(enemizer_basepatch_path, 'r') as f:
for patch in json.load(f): for patch in json.load(f):
@@ -676,7 +685,7 @@ def patch_rom(world, rom, player, team, enemized):
for door in world.doors: for door in world.doors:
if door.dest is not None and isinstance(door.dest, Door) and\ if door.dest is not None and isinstance(door.dest, Door) and\
door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs, door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs,
DoorType.Open, DoorType.StraightStairs]: DoorType.Open, DoorType.StraightStairs, DoorType.Ladder]:
rom.write_bytes(door.getAddress(), door.dest.getTarget(door)) rom.write_bytes(door.getAddress(), door.dest.getTarget(door))
for paired_door in world.paired_doors[player]: 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_a(world, player), paired_door.rom_data_a(world, player))
@@ -684,13 +693,15 @@ def patch_rom(world, rom, player, team, enemized):
if world.doorShuffle[player] != 'vanilla': if world.doorShuffle[player] != 'vanilla':
for builder in world.dungeon_layouts[player].values(): for builder in world.dungeon_layouts[player].values():
if builder.pre_open_stonewall: for stonewall in builder.pre_open_stonewalls:
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW': if stonewall.name == 'Desert Wall Slide NW':
dr_flags |= DROptions.Open_Desert_Wall dr_flags |= DROptions.Open_Desert_Wall
elif stonewall.name == 'PoD Bow Statue Down Ladder':
dr_flags |= DROptions.Open_PoD_Wall
for name, pair in boss_indicator.items(): for name, pair in boss_indicator.items():
dungeon_id, boss_door = pair dungeon_id, boss_door = pair
opposite_door = world.get_door(boss_door, player).dest opposite_door = world.get_door(boss_door, player).dest
if opposite_door and opposite_door.roomIndex > -1: if opposite_door and isinstance(opposite_door, Door) and opposite_door.roomIndex > -1:
dungeon_name = opposite_door.entrance.parent_region.dungeon.name dungeon_name = opposite_door.entrance.parent_region.dungeon.name
dungeon_id = boss_indicator[dungeon_name][0] dungeon_id = boss_indicator[dungeon_name][0]
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex) rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
@@ -1377,36 +1388,40 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0x18004E, 0) # Escape Fill (nothing) rom.write_byte(0x18004E, 0) # Escape Fill (nothing)
write_int16(rom, 0x180183, 300) # Escape fill rupee bow write_int16(rom, 0x180183, 300) # Escape fill rupee bow
rom.write_bytes(0x180185, [0,0,0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0, 0, 0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0,0,0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0,0,0]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
bow_max, bomb_max, magic_max = 0, 0, 0 bow_max, bomb_max, magic_max = 0, 0, 0
bow_small, magic_small = 0, 0
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']: if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']:
rom.write_byte(0x18004E, 1) # Escape Fill (arrows) rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
write_int16(rom, 0x180183, 300) # Escape fill rupee bow write_int16(rom, 0x180183, 300) # Escape fill rupee bow
rom.write_bytes(0x180185, [0,0,70]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0, 0, 70]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0,0,10]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0, 0, 10]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0,0,10]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 0, 10]) # Mantle respawn refills (magic, bombs, arrows)
bow_max = 70 bow_max, bow_small = 70, 10
elif uncle_location.item is not None and uncle_location.item.name in ['Bombs (10)']: elif uncle_location.item is not None and uncle_location.item.name in ['Bombs (10)']:
rom.write_byte(0x18004E, 2) # Escape Fill (bombs) rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
rom.write_bytes(0x180185, [0,50,0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0,3,0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0, 3, 0]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0,3,0]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 3, 0]) # Mantle respawn refills (magic, bombs, arrows)
bomb_max = 50 bomb_max = 50
elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']: elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']:
rom.write_byte(0x18004E, 4) # Escape Fill (magic) rom.write_byte(0x18004E, 4) # Escape Fill (magic)
rom.write_bytes(0x180185, [0x80,0,0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0x80, 0, 0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0x20,0,0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0x20, 0, 0]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0x20,0,0]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0x20, 0, 0]) # Mantle respawn refills (magic, bombs, arrows)
magic_max = 0x80 magic_max, magic_small = 0x80, 0x20
if world.doorShuffle[player] == 'crossed': if world.doorShuffle[player] == 'crossed':
# Uncle respawn refills (magic, bombs, arrows) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)]) rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)])
rom.write_bytes(0x180188, [0x20, 3, 10]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0x20, 3, 10]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0x20, 3, 10]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0x20, 3, 10]) # Mantle respawn refills (magic, bombs, arrows)
elif world.doorShuffle[player] == 'basic': # just in case a bomb is needed to get to a chest
rom.write_bytes(0x180185, [max(0x00, magic_max), max(3, bomb_max), max(0, bow_max)])
rom.write_bytes(0x180188, [magic_small, 3, bow_small]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [magic_small, 3, bow_small]) # Mantle respawn refills (magic, bombs, arrows)
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved # patch swamp: Need to enable permanent drain of water as dam or swamp were moved
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00) rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)
+6
View File
@@ -254,6 +254,12 @@ def create_rooms(world, player):
world.get_room(0xc0, player).change(0, DoorKind.Normal) # fix this kill room if enemizer is on world.get_room(0xc0, player).change(0, DoorKind.Normal) # fix this kill room if enemizer is on
def reset_rooms(world, player):
world.rooms = [x for x in world.rooms if x.player != player]
world._room_cache.clear()
create_rooms(world, player)
class Room(object): class Room(object):
def __init__(self, player, index, address): def __init__(self, player, index, address):
self.player = player self.player = player
+8 -5
View File
@@ -202,8 +202,10 @@ def global_rules(world, player):
set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Mimics 2 NW', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player)) set_rule(world.get_entrance('PoD Bow Statue Down Ladder', player), lambda state: state.can_shoot_arrows(player))
set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Map Balcony Drop Down', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('PoD Dark Pegs WN', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Dark Pegs Hammer Path', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('PoD Dark Pegs Up Ladder', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('PoD Dark Pegs Ladder Hammer Path', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('PoD Dark Pegs Ladder Cane Path', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('PoD Bow Statue Moving Wall Cane Path', player), lambda state: state.has('Cane of Somaria', player))
set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Boss', player))
set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Palace of Darkness - Prize', player))
@@ -231,10 +233,9 @@ def global_rules(world, player):
set_rule(world.get_entrance('Swamp Barrier Ledge Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Swamp Barrier Ledge Hook Path', player), lambda state: state.has('Hookshot', player))
set_rule(world.get_entrance('Swamp Drain Right Switch', player), lambda state: state.has('Drained Swamp', player)) set_rule(world.get_entrance('Swamp Drain Right Switch', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Drain WN', player), lambda state: state.has('Drained Swamp', player)) set_rule(world.get_entrance('Swamp Drain WN', player), lambda state: state.has('Drained Swamp', player))
# this might be unnecesssary for an insanity style shuffle
set_rule(world.get_entrance('Swamp Flooded Room WS', player), lambda state: state.has('Drained Swamp', player)) set_rule(world.get_entrance('Swamp Flooded Room WS', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Flooded Room Ladder', player), lambda state: state.has('Drained Swamp', player)) set_rule(world.get_entrance('Swamp Flooded Room Ladder', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_location('Swamp Palace - Flooded Room - Left', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_location('Swamp Palace - Flooded Room - Right', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Flooded Spot Ladder', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player)) set_rule(world.get_entrance('Swamp Flooded Spot Ladder', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Drain Left Up Stairs', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player)) set_rule(world.get_entrance('Swamp Drain Left Up Stairs', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Waterway NW', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Swamp Waterway NW', player), lambda state: state.has('Flippers', player))
@@ -854,7 +855,9 @@ def no_glitches_rules(world, player):
'PoD Callback': {'sewer': False, 'entrances': ['PoD Callback WS', 'PoD Callback Warp'], 'locations': []}, 'PoD Callback': {'sewer': False, 'entrances': ['PoD Callback WS', 'PoD Callback Warp'], 'locations': []},
'PoD Turtle Party': {'sewer': False, 'entrances': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], 'locations': []}, 'PoD Turtle Party': {'sewer': False, 'entrances': ['PoD Turtle Party ES', 'PoD Turtle Party NW'], 'locations': []},
'PoD Lonely Turtle': {'sewer': False, 'entrances': ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN'], 'locations': []}, 'PoD Lonely Turtle': {'sewer': False, 'entrances': ['PoD Lonely Turtle SW', 'PoD Lonely Turtle EN'], 'locations': []},
'PoD Dark Pegs': {'sewer': False, 'entrances': ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs WN'], 'locations': []}, 'PoD Dark Pegs': {'sewer': False, 'entrances': ['PoD Dark Pegs Hammer Path', 'PoD Dark Pegs WN'], 'locations': []},
'PoD Dark Pegs Ladder': {'sewer': False, 'entrances': ['PoD Dark Pegs Up Ladder', 'PoD Dark Pegs Ladder Hammer Path', 'PoD Dark Pegs Ladder Cane Path'], 'locations': []},
'PoD Dark Pegs Switch': {'sewer': False, 'entrances': ['PoD Dark Pegs Switch Path'], 'locations': []},
'PoD Dark Basement': {'sewer': False, 'entrances': ['PoD Dark Basement W Up Stairs', 'PoD Dark Basement E Up Stairs'], 'locations': ['Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right']}, 'PoD Dark Basement': {'sewer': False, 'entrances': ['PoD Dark Basement W Up Stairs', 'PoD Dark Basement E Up Stairs'], 'locations': ['Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right']},
'PoD Dark Maze': {'sewer': False, 'entrances': ['PoD Dark Maze EN', 'PoD Dark Maze E'], 'locations': ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom']}, 'PoD Dark Maze': {'sewer': False, 'entrances': ['PoD Dark Maze EN', 'PoD Dark Maze E'], 'locations': ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom']},
'Eastern Dark Square': {'sewer': False, 'entrances': ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN', 'Eastern Dark Square EN'], 'locations': []}, 'Eastern Dark Square': {'sewer': False, 'entrances': ['Eastern Dark Square NW', 'Eastern Dark Square Key Door WN', 'Eastern Dark Square EN'], 'locations': []},
+7 -10
View File
@@ -36,6 +36,9 @@ def is_bundled():
return getattr(sys, 'frozen', False) return getattr(sys, 'frozen', False)
def local_path(path): def local_path(path):
# just do stuff here and bail
return os.path.join(".", path)
if local_path.cached_path is not None: if local_path.cached_path is not None:
return os.path.join(local_path.cached_path, path) return os.path.join(local_path.cached_path, path)
@@ -51,6 +54,9 @@ def local_path(path):
local_path.cached_path = None local_path.cached_path = None
def output_path(path): def output_path(path):
# just do stuff here and bail
return os.path.join(".", path)
if output_path.cached_path is not None: if output_path.cached_path is not None:
return os.path.join(output_path.cached_path, path) return os.path.join(output_path.cached_path, path)
@@ -61,15 +67,7 @@ def output_path(path):
# has been packaged, so cannot use CWD for output. # has been packaged, so cannot use CWD for output.
if sys.platform == 'win32': if sys.platform == 'win32':
#windows #windows
import ctypes.wintypes documents = os.path.join(os.path.expanduser("~"),"Documents")
CSIDL_PERSONAL = 5 # My Documents
SHGFP_TYPE_CURRENT = 0 # Get current, not default value
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
ctypes.windll.shell32.SHGetFolderPathW(None, CSIDL_PERSONAL, None, SHGFP_TYPE_CURRENT, buf)
documents = buf.value
elif sys.platform == 'darwin': elif sys.platform == 'darwin':
from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error from AppKit import NSSearchPathForDirectoriesInDomains # pylint: disable=import-error
# http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains
@@ -655,4 +653,3 @@ if __name__ == '__main__':
# room_palette_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_us_rom(sys.argv[1])
extract_data_from_jp_rom(sys.argv[1]) extract_data_from_jp_rom(sys.argv[1])
+3 -1
View File
@@ -7,6 +7,8 @@
; Free RAM notes ; Free RAM notes
; Normal doors use $AB-AC for scrolling indicator ; Normal doors use $AB-AC for scrolling indicator
; Normal doors use $FE to store the trap door indicator ; Normal doors use $FE to store the trap door indicator
; Normal doors use $045e to store Y coordinate when transitioning to in-room stairs
; Normal doors use $045f to determine the order in which supertile quadrants are drawn
; Spiral doors use $045e to store stair type ; Spiral doors use $045e to store stair type
; Gfx uses $b1 to for sub-sub-sub-module thing ; Gfx uses $b1 to for sub-sub-sub-module thing
@@ -35,7 +37,7 @@ incsrc edges.asm
incsrc math.asm incsrc math.asm
incsrc hudadditions.asm incsrc hudadditions.asm
incsrc dr_lobby.asm incsrc dr_lobby.asm
warnpc $279700 warnpc $279C00
incsrc doortables.asm incsrc doortables.asm
warnpc $288000 warnpc $288000
+17 -3
View File
@@ -1,4 +1,4 @@
org $279700 org $279C00
KeyDoorOffset: KeyDoorOffset:
; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler ; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler
dw $0000,$0001,$0003,$0000,$0006,$0000,$000b,$0000,$0000,$0000,$000c,$000d,$0010,$0011,$0012,$0000 dw $0000,$0001,$0003,$0000,$0006,$0000,$000b,$0000,$0000,$0000,$000c,$000d,$0010,$0011,$0012,$0000
@@ -58,7 +58,7 @@ db $9f
org $27A000 org $27A000
DoorTable: DoorTable:
;; NW 00 N 01 N 02 WN 00 W 01 WS 02 SW 00 S 01 SE 02 EN 00 E 01 ES 02 - Door ruler ;; NW 00 N 01 NE 02 WN 00 W 01 WS 02 SW 00 S 01 SE 02 EN 00 E 01 ES 02 - Door ruler
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Default/Garbage row dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Default/Garbage row
dw $0003, $0003, $0003, $0450, $0003, $0003, $0003, $0003, $0003, $0452, $0003, $0003 ; HC Back Hall (x01) dw $0003, $0003, $0003, $0450, $0003, $0003, $0003, $0003, $0003, $0452, $0003, $0003 ; HC Back Hall (x01)
dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewer Switches (x02) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewer Switches (x02)
@@ -227,7 +227,7 @@ dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003,
;dw $0080, $1f50 ; ->zelda's cellblock ;dw $0080, $1f50 ; ->zelda's cellblock
org $27B000 org $27B000
SpiralTable: ;113 4 byte entries - should end at 27B44C SpiralTable: ;113 4 byte entries - should end at 27B1C4
dw $0203, $8080 ;null row dw $0203, $8080 ;null row
dw $0203, $8080 ;HC Backhallway dw $0203, $8080 ;HC Backhallway
dw $0203, $8080 ;Sewer Pull dw $0203, $8080 ;Sewer Pull
@@ -557,6 +557,20 @@ MultDivInfo: ; (1, 2, 3, 4, 5, 6, 10, 20)
db $01, $02, $03, $04, $05, $06, $0a, $14 db $01, $02, $03, $04, $05, $06, $0a, $14
; indices: 0-7 ; indices: 0-7
; In-room stairs in North/South pairs. From left to right:
; PoD, IP right side, IP Freezor chest and GT
org $27C700
InroomStairsTable:
dw $0003,$0003, $0003,$0003, $0003,$0003, $0003,$0003
org $27C720
InroomStairsRoom:
db $0B,$1B, $3F,$1F, $7E,$5E, $96,$3D
InroomStairsX:
dw $0190, $0160, $0040, $0178
InroomStairsY:
dw $0058, $0148, $0198, $0190
; dungeon tables ; dungeon tables
; HC HC EP DP AT SP PD MM SW IP TH TT TR GT ; HC HC EP DP AT SP PD MM SW IP TH TT TR GT
+11
View File
@@ -46,8 +46,19 @@ org $029396 ; <- 11396 - Bank02.asm : 3641 (LDA $01C322, X)
jsl StraightStairLayerFix jsl StraightStairLayerFix
org $02c06d ; <- Bank02.asm : 9874 (LDX $0418, CMP.b #$02) org $02c06d ; <- Bank02.asm : 9874 (LDX $0418, CMP.b #$02)
jsl DoorToStraight : nop jsl DoorToStraight : nop
org $02c092 ; STA $0020, Y : LDX #$00
jsl DoorToInroom : nop
org $02c0f8 ; CMP $02C034, X
jsl DoorToInroomEnd
org $02941a ; <- Bank02.asm : 3748 module 7.12.11 (LDA $0464 : BNE BRANCH_$11513 : INC $B0 : RTS) org $02941a ; <- Bank02.asm : 3748 module 7.12.11 (LDA $0464 : BNE BRANCH_$11513 : INC $B0 : RTS)
jsl StraightStairsTrapDoor : rts jsl StraightStairsTrapDoor : rts
org $028b54 ; <- Bank02.asm : 2200 (JSL UseImplicitRegIndexedLocalJumpTable)
jsl InroomStairsTrapDoor
org $0289a0 ; JSL $0091C4
jsl QuadrantLoadOrderBeforeScroll
org $02bd9c ; JSL $0091C4
jsl QuadrantLoadOrderAfterScroll
; Graphics fix ; Graphics fix
+7 -1
View File
@@ -52,14 +52,20 @@ LoadEdgeRoomVert:
lda $03 : sta $a0 lda $03 : sta $a0
sty $06 sty $06
and.b #$f0 : lsr #3 : !sub $21 : !add $06 : sta $02 and.b #$f0 : lsr #3 : !sub $21 : !add $06 : sta $02
ldy #$01 : jsr ShiftVariablesMainDir
lda $04 : and #$80 : bne .edge lda $04 : and #$80 : bne .edge
lda $04 : sta $01 ; load up flags in $01 lda $04 : sta $01 ; load up flags in $01
and #$03 : cmp #$03 : beq .inroom
ldy #$01 : jsr ShiftVariablesMainDir
jsr PrepScrollToNormal jsr PrepScrollToNormal
bra .scroll bra .scroll
.inroom
jsr ScrollToInroomStairs
rts
.edge .edge
ldy #$01 : jsr ShiftVariablesMainDir
lda $04 : and #$10 : beq + lda $04 : and #$10 : beq +
lda #$01 lda #$01
+ sta $ee ; layer stuff + sta $ee ; layer stuff
+114 -4
View File
@@ -31,6 +31,13 @@ WarpUp:
jsr Cleanup jsr Cleanup
rtl rtl
; Checks if $a0 is equal to <Room>. If it is, opens its stonewall if it's there
macro StonewallCheck(Room)
lda $a0 : cmp.b #<Room> : bne ?end
lda.l <Room>*2+$7ef000 : ora #$80 : sta.l <Room>*2+$7ef000
?end
endmacro
WarpDown: WarpDown:
lda.l DRMode : beq .end lda.l DRMode : beq .end
lda $040c : cmp.b #$ff : beq .end lda $040c : cmp.b #$ff : beq .end
@@ -38,6 +45,7 @@ WarpDown:
jsr CalcIndex jsr CalcIndex
!add #$0c : ldy #$ff ; offsets in A, Y !add #$0c : ldy #$ff ; offsets in A, Y
jsr LoadRoomVert jsr LoadRoomVert
%StonewallCheck($43)
.end .end
jsr Cleanup jsr Cleanup
rtl rtl
@@ -130,15 +138,20 @@ LoadRoomVert:
.gtg ;Good to Go! .gtg ;Good to Go!
pla ; Throw away normal room (don't fill up the stack) pla ; Throw away normal room (don't fill up the stack)
lda $a0 : and.b #$F0 : lsr #3 : !sub $21 : !add $06 : sta $02 lda $a0 : and.b #$F0 : lsr #3 : !sub $21 : !add $06 : sta $02
ldy #$01 : jsr ShiftVariablesMainDir
lda $01 : and #$80 : beq .normal lda $01 : and #$80 : beq .notEdge
ldy #$01 : jsr ShiftVariablesMainDir
ldy $06 : cpy #$ff : beq + ldy $06 : cpy #$ff : beq +
lda $01 : jsr LoadSouthMidpoint : bra ++ lda $01 : jsr LoadSouthMidpoint : bra ++
+ lda $01 : jsr LoadNorthMidpoint + lda $01 : jsr LoadNorthMidpoint
++ jsr PrepScrollToEdge : bra .scroll ++ jsr PrepScrollToEdge : bra .scroll
.notEdge
lda $01 : and #$03 : cmp #$03 : bne .normal
jsr ScrollToInroomStairs
bra .end
.normal .normal
ldy #$01 : jsr ShiftVariablesMainDir
jsr PrepScrollToNormal jsr PrepScrollToNormal
.scroll .scroll
lda $01 : and #$40 : pha lda $01 : and #$40 : pha
@@ -180,6 +193,68 @@ ShiftVariablesMainDir:
rts rts
} }
; Normal Flags should be in $01
ScrollToInroomStairs:
{
jsr PrepScrollToInroomStairs
ldy #$01 : jsr ShiftVariablesMainDir
jsr ScrollX
ldy #$00 : jsr ApplyScroll
lda $a0 : and #$0f : cmp #$0f : bne +
stz $e0 : stz $e2 ; special case camera fix
lda #$1f : sta $e1 : sta $e3
+
rts
}
; Direction should be in $06, Shift Value (see above) in $02 and other info in $01
; Sets $02, $04, $05, $ee, $045e, $045f and things related to Y coordinate
PrepScrollToInroomStairs:
{
lda $01 : and #$30 : lsr #3 : tay
lda.w InroomStairsX,y : sta $04
lda.w InroomStairsX+1,y : sta $05
lda $06 : cmp #$ff : beq .south
lda.w InroomStairsY+1,y : bne +
inc $045f ; flag indicating special screen transition
dec $02 ; shift variables further
stz $aa
lda $a8 : and #%11111101 : sta $a8
stz $0613 ; North scroll target
inc $0603 : inc $0607
dec $0619 : dec $061b
+
lda.w InroomStairsY,y : !add #$20 : sta $20
!sub #$38 : sta $045e
lda $01 : and #$40 : beq +
lda $20 : !add #$20 : sta $20
stz $045f
+
dec $21
%StonewallCheck($1b)
bra ++
.south
lda.w InroomStairsY+1,y : beq +
inc $045f ; flag indicating special screen transition
inc $02 ; shift variables further
lda #$02 : sta $aa
lda $a8 : ora #%00000010 : sta $a8
inc $0611 ; South scroll target
dec $0603 : dec $0607
inc $0619 : inc $061b
+
lda.w InroomStairsY,y : !sub #$20 : sta $20
!add #$38 : sta $045e
lda $01 : and #$40 : beq +
lda $20 : !sub #$20 : sta $20
stz $045f
+
inc $21
++
lda $01 : and #$04 : lsr #2 : sta $ee : bne +
stz $0476
+ rts
}
; Target pixel should be in A, other info in $01 ; Target pixel should be in A, other info in $01
; Sets $04 $05 and $ee ; Sets $04 $05 and $ee
@@ -214,6 +289,7 @@ StraightStairsAdj:
{ {
stx $0464 : sty $012e ; what we wrote over stx $0464 : sty $012e ; what we wrote over
lda.l DRMode : beq + lda.l DRMode : beq +
lda $045e : bne .toInroom
jsr GetTileAttribute : tax jsr GetTileAttribute : tax
lda $11 : cmp #$12 : beq .goingNorth lda $11 : cmp #$12 : beq .goingNorth
lda $a2 : cmp #$51 : bne ++ lda $a2 : cmp #$51 : bne ++
@@ -234,6 +310,9 @@ StraightStairsAdj:
pla : !add #$f6 : pha pla : !add #$f6 : pha
++ pla : !add $0464 : sta $0464 ++ pla : !add $0464 : sta $0464
+ rtl + rtl
.toInroom
lda #$32 : sta $0464 : stz $045e
rtl
} }
GetTileAttribute: GetTileAttribute:
@@ -283,11 +362,32 @@ DoorToStraight:
rtl rtl
} }
DoorToInroom:
{
ldx $045e : bne .end
sta $0020, y ; what we wrote over
.end
ldx #$00 ; what we wrote over
rtl
}
DoorToInroomEnd:
{
ldy $045e : beq .vanilla
cmp $045e : bne .return
stz $045e ; clear
.return
rtl
.vanilla
cmp $02c034, x ; what we wrote over
rtl
}
StraightStairsTrapDoor: StraightStairsTrapDoor:
{ {
lda $0464 : bne + lda $0464 : bne +
; reset function ; reset function
phk : pea.w .jslrtsreturn-1 .reset phk : pea.w .jslrtsreturn-1
pea.w $02802c pea.w $02802c
jml $028c73 ; $10D71 .reset label of Bank02 jml $028c73 ; $10D71 .reset label of Bank02
.jslrtsreturn .jslrtsreturn
@@ -300,5 +400,15 @@ StraightStairsTrapDoor:
inc $0468 : stz $068e : stz $0690 inc $0468 : stz $068e : stz $0690
++ rtl ++ rtl
+ jsl Dungeon_ApproachFixedColor ; what we wrote over + jsl Dungeon_ApproachFixedColor ; what we wrote over
.end rtl rtl
}
InroomStairsTrapDoor:
{
lda $0200 : cmp #$05 : beq .reset
lda $b0 : jml $008781 ; what we wrote over (essentially)
.reset
pla : pla : pla
jsl StraightStairsTrapDoor_reset
jml $028b15 ; just some RTS in bank 02
} }
+2
View File
@@ -37,6 +37,8 @@ OnFileLoadOverride:
jsl OnFileLoad ; what I wrote over jsl OnFileLoad ; what I wrote over
lda.l DRFlags : and #$80 : beq + ;flag is off lda.l DRFlags : and #$80 : beq + ;flag is off
lda $7ef086 : ora #$80 : sta $7ef086 lda $7ef086 : ora #$80 : sta $7ef086
+ lda.l DRFlags : and #$40 : beq + ;flag is off
lda $7ef036 : ora #$80 : sta $7ef036
+ lda.l DRFlags : and #$02 : beq + + lda.l DRFlags : and #$02 : beq +
lda $7ef353 : bne + lda $7ef353 : bne +
lda #$01 : sta $7ef353 lda #$01 : sta $7ef353
+14
View File
@@ -204,3 +204,17 @@ ApplyScroll:
sta $00e2, y sta $00e2, y
sta $00e0, y sta $00e0, y
stz $ab : sep #$30 : rts stz $ab : sep #$30 : rts
QuadrantLoadOrderBeforeScroll:
lda $045f : beq .end
lda #$08 : sta $045c ; start with opposite quadrant row
.end
jsl $0091c4 ; what we overwrote
rtl
QuadrantLoadOrderAfterScroll:
lda $045f : beq .end
stz $045c : stz $045f ; draw other row and clear flag
.end
jsl $0091c4 ; what we overwrote
rtl
+115 -7
View File
@@ -2,9 +2,10 @@ RecordStairType: {
pha pha
lda.l DRMode : beq .norm lda.l DRMode : beq .norm
lda $040c : cmp #$ff : beq .norm lda $040c : cmp #$ff : beq .norm
lda $0e : sta $045e lda $0e
cmp #$26 : beq .norm ; skipping in-floor staircases cmp #$25 : bcc ++ ; don't record straight staircases
pla : bra + sta $045e
++ pla : bra +
.norm pla : sta $a0 .norm pla : sta $a0
+ lda $063d, x + lda $063d, x
rtl rtl
@@ -15,8 +16,13 @@ SpiralWarp: {
lda $040c : cmp.b #$ff : beq .abort ; abort if not in dungeon lda $040c : cmp.b #$ff : beq .abort ; abort if not in dungeon
lda $045e : cmp #$5e : beq .gtg ; abort if not spiral - intended room is in A! lda $045e : cmp #$5e : beq .gtg ; abort if not spiral - intended room is in A!
cmp #$5f : beq .gtg cmp #$5f : beq .gtg
cmp #$26 : beq .inroom
.abort .abort
stz $045e : lda $a2 : and #$0f : rtl ; clear,run hijacked code and get out stz $045e : lda $a2 : and #$0f : rtl ; clear,run hijacked code and get out
.inroom
jsr InroomStairsWarp
lda $a2 : and #$0f ; this is the code we are hijacking
rtl
.gtg .gtg
phb : phk : plb : phx : phy ; push stuff phb : phk : plb : phx : phy ; push stuff
@@ -70,6 +76,13 @@ SpiralWarp: {
lda $01 : and #$20 : sta $07 ; zeroVtCam check lda $01 : and #$20 : sta $07 ; zeroVtCam check
ldy #$01 : jsr SetCamera ldy #$01 : jsr SetCamera
jsr StairCleanup
ply : plx : plb ; pull the stuff we pushed
lda $a2 : and #$0f ; this is the code we are hijacking
rtl
}
StairCleanup: {
stz $045e ; clear the staircase flag stz $045e ; clear the staircase flag
; animated tiles fix ; animated tiles fix
@@ -81,9 +94,7 @@ SpiralWarp: {
jsl DecompDungAnimatedTiles jsl DecompDungAnimatedTiles
+ +
stz $047a stz $047a
ply : plx : plb ; pull the stuff we pushed rts
lda $a2 : and #$0f ; this is the code we are hijacking
rtl
} }
;Sets the offset in A ;Sets the offset in A
@@ -105,7 +116,7 @@ LookupSpiralOffset: {
lda $a9 : ora $aa : and #$03 : beq .quad0 lda $a9 : ora $aa : and #$03 : beq .quad0
cmp #$01 : beq .quad1 cmp #$01 : beq .quad1
cmp #$02 : beq .quad2 cmp #$02 : beq .quad2
cmp #$03 : beq .quad3 bra .quad3
.quad0 .quad0
inc $01 : lda $a2 inc $01 : lda $a2
cmp #$0c : beq .q0diff ;gt ent cmp #$0c : beq .q0diff ;gt ent
@@ -138,6 +149,103 @@ LookupSpiralOffset: {
rts rts
} }
InroomStairsWarp: {
phb : phk : plb : phx : phy ; push stuff
; find stairs by room and store index in X
lda $a0 : ldx #$07
.loop
cmp.w InroomStairsRoom,x
beq .found
dex
bne .loop
.found
rep #$30
txa : and #$00ff : asl : tay
lda.w InroomStairsTable,y : sta $00
sep #$30
sta $a0
; set position and everything else based on target door type
txa : and #$01 : eor #$01 : sta $07
; should be the same as lda $0462 : and #$04 : lsr #2 : eor #$01 : sta $07
lda $01 : and #$80 : beq .notEdge
lda $07 : sta $03 : beq +
lda $01 : jsr LoadSouthMidpoint : sta $22 : lda #$e0
bra ++
+
lda $01 : jsr LoadNorthMidpoint : sta $22 : lda #$1b
++
sta $20
lda $01 : and #$20 : beq +
lda #$01
+
sta $02
stz $07
lda $01 : and #$10 : lsr #4
brl .layer
.notEdge
lda $01 : and #$03 : cmp #$03 : bne .normal
lda $01 : and #$30 : lsr #3 : tay
lda.w InroomStairsX,y : sta $22
lda.w InroomStairsX+1,y : sta $02
lda.w InroomStairsY+1,y : sta $03
lda.w InroomStairsY,y
ldy $07 : beq +
!add #$07
+
sta $20
%StonewallCheck($1b)
inc $07
lda $01 : and #$04 : lsr #2
bra .layer
.normal
lda $01 : sta $fe ; trap door
lda $07 : sta $03 : beq +
lda #$e0
ldy $a0 : cpy #$51 : bne ++ ; special fix for throne room
!sub #$18
bra ++
+
%StonewallCheck($43)
lda #$1b
++
sta $20
inc $07 : stz $02 : lda #$78 : sta $22
lda $01 : and #$03 : beq ++
cmp #$02 : !bge +
lda #$f8 : sta $22 : stz $07 : bra ++
+ inc $02
++
lda $01 : and #$04 : lsr #2
.layer
sta $ee
bne +
stz $0476
+
lda $02 : !sub $a9
beq .skipXQuad
sta $06 : !add $a9 : sta $a9
ldy #$00 : jsr ShiftQuadSimple
.skipXQuad
lda $aa : lsr : sta $06 : lda $03 : !sub $06
beq .skipYQuad
sta $06 : asl : !add $aa : sta $aa
ldy #$01 : jsr ShiftQuadSimple
.skipYQuad
ldy #$00 : jsr SetCamera ; horizontal camera
ldy #$01 : sty $07 : jsr SetCamera ; vertical camera
lda $20 : cmp #$e0 : bcc +
lda $e8 : bne +
lda #$10 : sta $e8 ; adjust vertical camera at bottom
+
jsr StairCleanup
ply : plx : plb ; pull the stuff we pushed
rts
}
ShiftQuadSimple: { ShiftQuadSimple: {
lda.w CoordIndex,y : tax lda.w CoordIndex,y : tax
lda $20,x : beq .skip lda $20,x : beq .skip
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More