Merge branch 'DoorDev' into EdgeWork
This commit is contained in:
@@ -2,7 +2,7 @@ import copy
|
|||||||
from enum import Enum, unique, Flag
|
from enum import Enum, unique, Flag
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict, deque
|
from collections import OrderedDict, deque, defaultdict
|
||||||
|
|
||||||
from EntranceShuffle import door_addresses
|
from EntranceShuffle import door_addresses
|
||||||
from _vendor.collections_extended import bag
|
from _vendor.collections_extended import bag
|
||||||
@@ -70,6 +70,7 @@ class World(object):
|
|||||||
self.inaccessible_regions = {}
|
self.inaccessible_regions = {}
|
||||||
self.key_logic = {}
|
self.key_logic = {}
|
||||||
self.pool_adjustment = {}
|
self.pool_adjustment = {}
|
||||||
|
self.key_layout = defaultdict(dict)
|
||||||
|
|
||||||
for player in range(1, players + 1):
|
for player in range(1, players + 1):
|
||||||
def set_player_attr(attr, val):
|
def set_player_attr(attr, val):
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ def vanilla_key_logic(world, player):
|
|||||||
world.key_logic[player] = {}
|
world.key_logic[player] = {}
|
||||||
analyze_dungeon(key_layout, world, player)
|
analyze_dungeon(key_layout, world, player)
|
||||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||||
|
log_key_logic(builder.name, key_layout.key_logic)
|
||||||
last_key = None
|
last_key = None
|
||||||
if world.shuffle[player] == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
validate_vanilla_key_logic(world, player)
|
validate_vanilla_key_logic(world, player)
|
||||||
@@ -784,6 +785,8 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
# Last Step: Adjust Small Key Dungeon Pool
|
# Last Step: Adjust Small Key Dungeon Pool
|
||||||
if not world.retro[player]:
|
if not world.retro[player]:
|
||||||
for name, builder in dungeon_builders.items():
|
for name, builder in dungeon_builders.items():
|
||||||
|
reassign_key_doors(builder, world, player)
|
||||||
|
log_key_logic(builder.name, world.key_logic[player][builder.name])
|
||||||
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0)
|
||||||
dungeon = world.get_dungeon(name, player)
|
dungeon = world.get_dungeon(name, player)
|
||||||
if actual_chest_keys == 0:
|
if actual_chest_keys == 0:
|
||||||
@@ -864,8 +867,11 @@ 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
|
||||||
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_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
|
||||||
|
|
||||||
@@ -898,9 +904,11 @@ def shuffle_key_doors(builder, world, player):
|
|||||||
builder.key_doors_num = num_key_doors
|
builder.key_doors_num = num_key_doors
|
||||||
find_small_key_door_candidates(builder, start_regions, world, player)
|
find_small_key_door_candidates(builder, start_regions, world, player)
|
||||||
find_valid_combination(builder, start_regions, world, player)
|
find_valid_combination(builder, start_regions, world, player)
|
||||||
|
reassign_key_doors(builder, world, player)
|
||||||
|
log_key_logic(builder.name, world.key_logic[player][builder.name])
|
||||||
|
|
||||||
|
|
||||||
def find_current_key_doors(builder, world, player):
|
def find_current_key_doors(builder):
|
||||||
current_doors = []
|
current_doors = []
|
||||||
for region in builder.master_sector.regions:
|
for region in builder.master_sector.regions:
|
||||||
for ext in region.exits:
|
for ext in region.exits:
|
||||||
@@ -987,9 +995,9 @@ def find_valid_combination(builder, start_regions, world, player, drop_keys=True
|
|||||||
if player not in world.key_logic.keys():
|
if player not in world.key_logic.keys():
|
||||||
world.key_logic[player] = {}
|
world.key_logic[player] = {}
|
||||||
analyze_dungeon(key_layout, world, player)
|
analyze_dungeon(key_layout, world, player)
|
||||||
reassign_key_doors(builder, proposal, world, player)
|
builder.key_door_proposal = proposal
|
||||||
log_key_logic(builder.name, key_layout.key_logic)
|
|
||||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||||
|
world.key_layout[player][builder.name] = key_layout
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -1011,6 +1019,13 @@ def log_key_logic(d_name, key_logic):
|
|||||||
if rule.alternate_small_key is not None:
|
if rule.alternate_small_key is not None:
|
||||||
for loc in rule.alternate_big_key_loc:
|
for loc in rule.alternate_big_key_loc:
|
||||||
logger.debug('---BK Loc %s', loc.name)
|
logger.debug('---BK Loc %s', loc.name)
|
||||||
|
logger.debug('Placement rules for %s', d_name)
|
||||||
|
for rule in key_logic.placement_rules:
|
||||||
|
logger.debug('*Rule for %s:', rule.door_reference)
|
||||||
|
if rule.bk_conditional_set:
|
||||||
|
logger.debug('**BK Checks %s', ','.join([x.name for x in rule.bk_conditional_set]))
|
||||||
|
logger.debug('**BK Blocked By Door (%s) : %s', rule.needed_keys_wo_bk, ','.join([x.name for x in rule.check_locations_wo_bk]))
|
||||||
|
logger.debug('**BK Elsewhere (%s) : %s', rule.needed_keys_w_bk, ','.join([x.name for x in rule.check_locations_w_bk]))
|
||||||
|
|
||||||
|
|
||||||
def build_pair_list(flat_list):
|
def build_pair_list(flat_list):
|
||||||
@@ -1045,7 +1060,9 @@ def find_key_door_candidates(region, checked, world, player):
|
|||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
current, last_door, last_region = queue.pop()
|
current, last_door, last_region = queue.pop()
|
||||||
for ext in current.exits:
|
for ext in current.exits:
|
||||||
d = world.check_for_door(ext.name, player)
|
d = ext.door
|
||||||
|
if d and d.controller:
|
||||||
|
d = d.controller
|
||||||
if d is not None and not d.blocked and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
|
if d is not None and not d.blocked and d.dest is not last_door and d.dest is not last_region and d not in checked_doors:
|
||||||
valid = False
|
valid = False
|
||||||
if 0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]:
|
if 0 <= d.doorListPos < 4 and d.type in [DoorType.Interior, DoorType.Normal, DoorType.SpiralStairs]:
|
||||||
@@ -1064,9 +1081,11 @@ def find_key_door_candidates(region, checked, world, player):
|
|||||||
okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable,
|
okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable,
|
||||||
DoorKind.Dashable, DoorKind.DungeonChanger]
|
DoorKind.Dashable, DoorKind.DungeonChanger]
|
||||||
valid = kind in okay_normals and kind_b in okay_normals
|
valid = kind in okay_normals and kind_b in okay_normals
|
||||||
|
if valid and 0 <= d2.doorListPos < 4:
|
||||||
|
candidates.append(d2)
|
||||||
else:
|
else:
|
||||||
valid = True
|
valid = True
|
||||||
if valid:
|
if valid and d not in candidates:
|
||||||
candidates.append(d)
|
candidates.append(d)
|
||||||
if ext.connected_region.type != RegionType.Dungeon or ext.connected_region.dungeon == dungeon:
|
if ext.connected_region.type != RegionType.Dungeon or ext.connected_region.dungeon == dungeon:
|
||||||
queue.append((ext.connected_region, d, current))
|
queue.append((ext.connected_region, d, current))
|
||||||
@@ -1097,10 +1116,12 @@ def ncr(n, r):
|
|||||||
return numerator / denominator
|
return numerator / denominator
|
||||||
|
|
||||||
|
|
||||||
def reassign_key_doors(builder, proposal, world, player):
|
def reassign_key_doors(builder, world, player):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
|
logger.debug('Key doors for %s', builder.name)
|
||||||
|
proposal = builder.key_door_proposal
|
||||||
flat_proposal = flatten_pair_list(proposal)
|
flat_proposal = flatten_pair_list(proposal)
|
||||||
queue = deque(find_current_key_doors(builder, world, player))
|
queue = deque(find_current_key_doors(builder))
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
d = queue.pop()
|
d = queue.pop()
|
||||||
if d.type is DoorType.SpiralStairs and d not in proposal:
|
if d.type is DoorType.SpiralStairs and d not in proposal:
|
||||||
|
|||||||
@@ -451,9 +451,12 @@ def stonewall_valid(stonewall):
|
|||||||
if bad_door.blocked:
|
if bad_door.blocked:
|
||||||
return True # great we're done with this one
|
return True # great we're done with this one
|
||||||
loop_region = stonewall.entrance.parent_region
|
loop_region = stonewall.entrance.parent_region
|
||||||
start_region = bad_door.entrance.parent_region
|
start_regions = [bad_door.entrance.parent_region]
|
||||||
queue = deque([start_region])
|
if bad_door.dependents:
|
||||||
visited = {start_region}
|
for dep in bad_door.dependents:
|
||||||
|
start_regions.append(dep.entrance.parent_region)
|
||||||
|
queue = deque(start_regions)
|
||||||
|
visited = set(start_regions)
|
||||||
while len(queue) > 0:
|
while len(queue) > 0:
|
||||||
region = queue.popleft()
|
region = queue.popleft()
|
||||||
if region == loop_region:
|
if region == loop_region:
|
||||||
@@ -1053,6 +1056,7 @@ class DungeonBuilder(object):
|
|||||||
self.key_doors_num = None
|
self.key_doors_num = None
|
||||||
self.combo_size = None
|
self.combo_size = None
|
||||||
self.flex = 0
|
self.flex = 0
|
||||||
|
self.key_door_proposal = None
|
||||||
|
|
||||||
if name in dungeon_dead_end_allowance.keys():
|
if name in dungeon_dead_end_allowance.keys():
|
||||||
self.allowance = dungeon_dead_end_allowance[name]
|
self.allowance = dungeon_dead_end_allowance[name]
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ def parse_arguments(argv, no_defaults=False):
|
|||||||
base game.
|
base game.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--experimental', default=defval(False), help='Enable experimental features', action='store_true')
|
parser.add_argument('--experimental', default=defval(False), help='Enable experimental features', action='store_true')
|
||||||
|
parser.add_argument('--dungeon_counters', default=defval(False), help='Enable dungeon chest counters', action='store_true')
|
||||||
parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
parser.add_argument('--crystals_ganon', default=defval('7'), const='7', nargs='?', choices=['random', '0', '1', '2', '3', '4', '5', '6', '7'],
|
||||||
help='''\
|
help='''\
|
||||||
How many crystals are needed to defeat ganon. Any other
|
How many crystals are needed to defeat ganon. Any other
|
||||||
@@ -302,7 +303,7 @@ def parse_arguments(argv, no_defaults=False):
|
|||||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||||
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental',
|
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||||
'remote_items']:
|
'remote_items']:
|
||||||
|
|||||||
34
Fill.py
34
Fill.py
@@ -201,7 +201,8 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
|
|||||||
else:
|
else:
|
||||||
test_state = maximum_exploration_state
|
test_state = maximum_exploration_state
|
||||||
if (not single_player_placement or location.player == item_to_place.player)\
|
if (not single_player_placement or location.player == item_to_place.player)\
|
||||||
and location.can_fill(test_state, item_to_place, perform_access_check):
|
and location.can_fill(test_state, item_to_place, perform_access_check)\
|
||||||
|
and valid_key_placement(item_to_place, location, itempool, world):
|
||||||
spot_to_fill = location
|
spot_to_fill = location
|
||||||
break
|
break
|
||||||
elif item_to_place.smallkey or item_to_place.bigkey:
|
elif item_to_place.smallkey or item_to_place.bigkey:
|
||||||
@@ -217,11 +218,42 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
|
|||||||
raise FillError('No more spots to place %s' % item_to_place)
|
raise FillError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
track_outside_keys(item_to_place, spot_to_fill, world)
|
||||||
locations.remove(spot_to_fill)
|
locations.remove(spot_to_fill)
|
||||||
spot_to_fill.event = True
|
spot_to_fill.event = True
|
||||||
|
|
||||||
itempool.extend(unplaced_items)
|
itempool.extend(unplaced_items)
|
||||||
|
|
||||||
|
|
||||||
|
def valid_key_placement(item, location, itempool, world):
|
||||||
|
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player]:
|
||||||
|
return True
|
||||||
|
dungeon = location.parent_region.dungeon
|
||||||
|
if dungeon:
|
||||||
|
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
|
||||||
|
return True
|
||||||
|
key_logic = world.key_logic[item.player][dungeon.name]
|
||||||
|
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name])
|
||||||
|
return key_logic.check_placement(unplaced_keys)
|
||||||
|
else:
|
||||||
|
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
|
||||||
|
or (item.bigkey and not world.bigkeyshuffle[item.player]))
|
||||||
|
return not inside_dungeon_item
|
||||||
|
|
||||||
|
|
||||||
|
def track_outside_keys(item, location, world):
|
||||||
|
if not item.smallkey:
|
||||||
|
return
|
||||||
|
item_dungeon = item.name.split('(')[1][:-1]
|
||||||
|
if item_dungeon == 'Escape':
|
||||||
|
item_dungeon = 'Hyrule Castle'
|
||||||
|
if location.player == item.player:
|
||||||
|
loc_dungeon = location.parent_region.dungeon
|
||||||
|
if loc_dungeon and loc_dungeon.name == item_dungeon:
|
||||||
|
return # this is an inside key
|
||||||
|
world.key_logic[item.player][item_dungeon].outside_keys += 1
|
||||||
|
|
||||||
|
|
||||||
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
|
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
|
||||||
# If not passed in, then get a shuffled list of locations to fill in
|
# If not passed in, then get a shuffled list of locations to fill in
|
||||||
if not fill_locations:
|
if not fill_locations:
|
||||||
|
|||||||
4
Gui.py
4
Gui.py
@@ -87,6 +87,8 @@ def guiMain(args=None):
|
|||||||
experimentCheckbutton = Checkbutton(checkBoxFrame, text="Enable Experimental Features", variable=experimentVar)
|
experimentCheckbutton = Checkbutton(checkBoxFrame, text="Enable Experimental Features", variable=experimentVar)
|
||||||
customVar = IntVar()
|
customVar = IntVar()
|
||||||
customCheckbutton = Checkbutton(checkBoxFrame, text="Use custom item pool", variable=customVar)
|
customCheckbutton = Checkbutton(checkBoxFrame, text="Use custom item pool", variable=customVar)
|
||||||
|
dungeonCounterVar = IntVar()
|
||||||
|
dungeonCounterbutton = Checkbutton(checkBoxFrame, text="Enable dungeon chest counters", variable=dungeonCounterVar)
|
||||||
|
|
||||||
createSpoilerCheckbutton.pack(expand=True, anchor=W)
|
createSpoilerCheckbutton.pack(expand=True, anchor=W)
|
||||||
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
||||||
@@ -102,6 +104,7 @@ def guiMain(args=None):
|
|||||||
hintsCheckbutton.pack(expand=True, anchor=W)
|
hintsCheckbutton.pack(expand=True, anchor=W)
|
||||||
experimentCheckbutton.pack(expand=True, anchor=W)
|
experimentCheckbutton.pack(expand=True, anchor=W)
|
||||||
customCheckbutton.pack(expand=True, anchor=W)
|
customCheckbutton.pack(expand=True, anchor=W)
|
||||||
|
dungeonCounterbutton.pack(expand=True, anchor=W)
|
||||||
|
|
||||||
romOptionsFrame = LabelFrame(rightHalfFrame, text="Rom options")
|
romOptionsFrame = LabelFrame(rightHalfFrame, text="Rom options")
|
||||||
romOptionsFrame.columnconfigure(0, weight=1)
|
romOptionsFrame.columnconfigure(0, weight=1)
|
||||||
@@ -472,6 +475,7 @@ def guiMain(args=None):
|
|||||||
int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()),
|
int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()),
|
||||||
int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(progbowVar.get()), int(bomb10Var.get()), int(triforcepieceVar.get()),
|
int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(progbowVar.get()), int(bomb10Var.get()), int(triforcepieceVar.get()),
|
||||||
int(triforcecountVar.get()), int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())]
|
int(triforcecountVar.get()), int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())]
|
||||||
|
guiargs.dungeon_counters = bool(dungeonCounterVar.get())
|
||||||
guiargs.rom = romVar.get()
|
guiargs.rom = romVar.get()
|
||||||
guiargs.sprite = sprite
|
guiargs.sprite = sprite
|
||||||
guiargs.outputpath = args.outputpath if args else None
|
guiargs.outputpath = args.outputpath if args else None
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class KeyLayout(object):
|
|||||||
self.max_drops = None
|
self.max_drops = None
|
||||||
self.all_chest_locations = {}
|
self.all_chest_locations = {}
|
||||||
self.big_key_special = False
|
self.big_key_special = False
|
||||||
|
self.all_locations = set()
|
||||||
|
|
||||||
# bk special?
|
# bk special?
|
||||||
# bk required? True if big chests or big doors exists
|
# bk required? True if big chests or big doors exists
|
||||||
@@ -44,6 +45,14 @@ class KeyLogic(object):
|
|||||||
self.bk_chests = set()
|
self.bk_chests = set()
|
||||||
self.logic_min = {}
|
self.logic_min = {}
|
||||||
self.logic_max = {}
|
self.logic_max = {}
|
||||||
|
self.placement_rules = []
|
||||||
|
self.outside_keys = 0
|
||||||
|
|
||||||
|
def check_placement(self, unplaced_keys):
|
||||||
|
for rule in self.placement_rules:
|
||||||
|
if not rule.is_satisfiable(self.outside_keys, unplaced_keys):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class DoorRules(object):
|
class DoorRules(object):
|
||||||
@@ -59,6 +68,38 @@ class DoorRules(object):
|
|||||||
self.small_location = None
|
self.small_location = None
|
||||||
|
|
||||||
|
|
||||||
|
class PlacementRule(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.door_reference = None
|
||||||
|
self.small_key = None
|
||||||
|
self.bk_conditional_set = None # the location that means
|
||||||
|
self.needed_keys_w_bk = None
|
||||||
|
self.needed_keys_wo_bk = None
|
||||||
|
self.check_locations_w_bk = None
|
||||||
|
self.check_locations_wo_bk = None
|
||||||
|
|
||||||
|
def is_satisfiable(self, outside_keys, unplaced_keys):
|
||||||
|
bk_blocked = False
|
||||||
|
if self.bk_conditional_set:
|
||||||
|
for loc in self.bk_conditional_set:
|
||||||
|
if loc.item and loc.item.bigkey:
|
||||||
|
bk_blocked = True
|
||||||
|
break
|
||||||
|
available_keys = outside_keys
|
||||||
|
empty_chests = 0
|
||||||
|
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
|
||||||
|
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||||
|
for loc in check_locations:
|
||||||
|
if not loc.item:
|
||||||
|
empty_chests += 1
|
||||||
|
elif loc.item and loc.item.name == self.small_key:
|
||||||
|
available_keys += 1
|
||||||
|
place_able_keys = min(empty_chests, unplaced_keys)
|
||||||
|
available_keys += place_able_keys
|
||||||
|
return available_keys >= threshold
|
||||||
|
|
||||||
|
|
||||||
class KeyCounter(object):
|
class KeyCounter(object):
|
||||||
|
|
||||||
def __init__(self, max_chests):
|
def __init__(self, max_chests):
|
||||||
@@ -108,6 +149,8 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
|
|
||||||
find_bk_locked_sections(key_layout, world)
|
find_bk_locked_sections(key_layout, world)
|
||||||
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
|
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
|
||||||
|
if world.retro[player] and world.mode[player] != 'standard':
|
||||||
|
return
|
||||||
|
|
||||||
original_key_counter = find_counter({}, False, key_layout)
|
original_key_counter = find_counter({}, False, key_layout)
|
||||||
queue = deque([(None, original_key_counter)])
|
queue = deque([(None, original_key_counter)])
|
||||||
@@ -147,13 +190,15 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
if not child.bigKey and child not in doors_completed:
|
if not child.bigKey and child not in doors_completed:
|
||||||
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False, empty_flag)
|
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False, empty_flag)
|
||||||
rule = create_rule(best_counter, key_counter, key_layout, world, player)
|
rule = create_rule(best_counter, key_counter, key_layout, world, player)
|
||||||
if not rule.is_valid:
|
# todo: seems to be caused by best_counter not opening the big key door when that's logically required. Re-evaluate usage of this
|
||||||
logging.getLogger('').warning('Key logic for door %s requires too many chests. Seed may be beatable anyway.', child.name)
|
# if not rule.is_valid:
|
||||||
|
# logging.getLogger('').warning('Key logic for door %s requires too many chests. Seed may be beatable anyway.', child.name)
|
||||||
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
if smallest_rule is None or rule.small_key_num < smallest_rule:
|
||||||
smallest_rule = rule.small_key_num
|
smallest_rule = rule.small_key_num
|
||||||
check_for_self_lock_key(rule, child, best_counter, key_layout, world, player)
|
check_for_self_lock_key(rule, child, best_counter, key_layout, world, player)
|
||||||
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world, player)
|
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world, player)
|
||||||
key_logic.door_rules[child.name] = rule
|
key_logic.door_rules[child.name] = rule
|
||||||
|
create_placement_rule(key_layout, child, odd_counter, key_counter, world, player)
|
||||||
doors_completed.add(child)
|
doors_completed.add(child)
|
||||||
next_counter = find_next_counter(child, key_counter, key_layout)
|
next_counter = find_next_counter(child, key_counter, key_layout)
|
||||||
ctr_id = cid(next_counter, key_layout)
|
ctr_id = cid(next_counter, key_layout)
|
||||||
@@ -176,6 +221,65 @@ def analyze_dungeon(key_layout, world, player):
|
|||||||
rule.small_key_num, rule.alternate_small_key = rule.alternate_small_key, rule.small_key_num
|
rule.small_key_num, rule.alternate_small_key = rule.alternate_small_key, rule.small_key_num
|
||||||
|
|
||||||
|
|
||||||
|
def create_placement_rule(key_layout, door, odd_ctr, current_ctr, world, player):
|
||||||
|
key_logic = key_layout.key_logic
|
||||||
|
worst_ctr = find_worst_counter(door, odd_ctr, current_ctr, key_layout, False)
|
||||||
|
sm_num = worst_ctr.used_keys + 1
|
||||||
|
accessible_loc = set()
|
||||||
|
accessible_loc.update(worst_ctr.free_locations)
|
||||||
|
accessible_loc.update(worst_ctr.key_only_locations)
|
||||||
|
worst_ctr_wo_bk, post_ctr, alt_num = find_worst_counter_wo_bk(sm_num, accessible_loc, door, odd_ctr, current_ctr, key_layout)
|
||||||
|
blocked_loc = key_layout.all_locations.difference(accessible_loc)
|
||||||
|
|
||||||
|
if len(blocked_loc) > 0:
|
||||||
|
rule = PlacementRule()
|
||||||
|
rule.door_reference = door
|
||||||
|
rule.small_key = key_logic.small_key_name
|
||||||
|
rule.needed_keys_w_bk = sm_num
|
||||||
|
placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player)
|
||||||
|
rule.check_locations_w_bk = accessible_loc
|
||||||
|
if worst_ctr_wo_bk:
|
||||||
|
accessible_wo_bk, post_set = set(), set()
|
||||||
|
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
|
||||||
|
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
|
||||||
|
post_set.update(post_ctr.free_locations)
|
||||||
|
post_set.update(post_ctr.key_only_locations)
|
||||||
|
blocked_wo_bk = post_set.difference(accessible_wo_bk)
|
||||||
|
if len(blocked_wo_bk) > 0:
|
||||||
|
rule.bk_conditional_set = blocked_wo_bk
|
||||||
|
rule.needed_keys_wo_bk = alt_num
|
||||||
|
# can this self lock a key if bk not avail? I'm thinking no.
|
||||||
|
# placement_self_lock_adjustment(rule, key_layout, ???, worst_ctr_wo_bk, world, player)
|
||||||
|
rule.check_locations_wo_bk = accessible_wo_bk
|
||||||
|
key_logic.placement_rules.append(rule)
|
||||||
|
if worst_ctr_wo_bk:
|
||||||
|
check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num)
|
||||||
|
|
||||||
|
|
||||||
|
def check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num):
|
||||||
|
avail_keys = len(worst_ctr_wo_bk.key_only_locations)
|
||||||
|
place_able_keys = min(key_layout.max_chests, len(worst_ctr_wo_bk.free_locations))
|
||||||
|
if avail_keys + place_able_keys < alt_num:
|
||||||
|
accessible_wo_bk, post_set = set(), set()
|
||||||
|
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
|
||||||
|
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
|
||||||
|
post_set.update(post_ctr.free_locations)
|
||||||
|
post_set.update(post_ctr.key_only_locations)
|
||||||
|
key_layout.key_logic.bk_restricted.update(post_set.difference(accessible_wo_bk))
|
||||||
|
|
||||||
|
|
||||||
|
def placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player):
|
||||||
|
if len(blocked_loc) == 1 and world.accessibility[player] != 'locations':
|
||||||
|
max_ctr = find_max_counter(key_layout)
|
||||||
|
blocked_others = set(max_ctr.other_locations).difference(set(worst_ctr.other_locations))
|
||||||
|
important_found = False
|
||||||
|
for loc in blocked_others:
|
||||||
|
if important_location(loc, world, player):
|
||||||
|
important_found = True
|
||||||
|
break
|
||||||
|
if not important_found:
|
||||||
|
rule.needed_keys_w_bk -= 1
|
||||||
|
|
||||||
|
|
||||||
def count_key_drops(sector):
|
def count_key_drops(sector):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
@@ -210,6 +314,8 @@ def find_bk_locked_sections(key_layout, world):
|
|||||||
big_chest_allowed_big_key = world.accessibility != 'locations'
|
big_chest_allowed_big_key = world.accessibility != 'locations'
|
||||||
for counter in key_counters.values():
|
for counter in key_counters.values():
|
||||||
key_layout.all_chest_locations.update(counter.free_locations)
|
key_layout.all_chest_locations.update(counter.free_locations)
|
||||||
|
key_layout.all_locations.update(counter.free_locations)
|
||||||
|
key_layout.all_locations.update(counter.key_only_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:
|
||||||
@@ -240,6 +346,28 @@ def relative_empty_counter(odd_counter, key_counter):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def relative_empty_counter_2(odd_counter, key_counter):
|
||||||
|
if len(set(odd_counter.key_only_locations).difference(key_counter.key_only_locations)) > 0:
|
||||||
|
return False
|
||||||
|
if len(set(odd_counter.free_locations).difference(key_counter.free_locations)) > 0:
|
||||||
|
return False
|
||||||
|
for child in odd_counter.child_doors:
|
||||||
|
if unique_child_door_2(child, key_counter):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def progressive_ctr(new_counter, last_counter):
|
||||||
|
if len(set(new_counter.key_only_locations).difference(last_counter.key_only_locations)) > 0:
|
||||||
|
return True
|
||||||
|
if len(set(new_counter.free_locations).difference(last_counter.free_locations)) > 0:
|
||||||
|
return True
|
||||||
|
for child in new_counter.child_doors:
|
||||||
|
if unique_child_door_2(child, last_counter):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def unique_child_door(child, key_counter):
|
def unique_child_door(child, key_counter):
|
||||||
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
|
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
|
||||||
return False
|
return False
|
||||||
@@ -250,6 +378,14 @@ def unique_child_door(child, key_counter):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def unique_child_door_2(child, key_counter):
|
||||||
|
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
|
||||||
|
return False
|
||||||
|
if child in key_counter.open_doors or child.dest in key_counter.child_doors:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def find_best_counter(door, odd_counter, key_counter, key_layout, world, player, skip_bk, empty_flag): # try to waste as many keys as possible?
|
def find_best_counter(door, odd_counter, key_counter, key_layout, world, player, skip_bk, empty_flag): # try to waste as many keys as possible?
|
||||||
ignored_doors = {door, door.dest} if door is not None else {}
|
ignored_doors = {door, door.dest} if door is not None else {}
|
||||||
finished = False
|
finished = False
|
||||||
@@ -279,7 +415,34 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, player,
|
|||||||
return last_counter
|
return last_counter
|
||||||
|
|
||||||
|
|
||||||
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
def find_worst_counter(door, odd_counter, key_counter, key_layout, skip_bk): # try to waste as many keys as possible?
|
||||||
|
ignored_doors = {door, door.dest} if door is not None else {}
|
||||||
|
finished = False
|
||||||
|
opened_doors = dict(key_counter.open_doors)
|
||||||
|
bk_opened = key_counter.big_key_opened
|
||||||
|
# new_counter = key_counter
|
||||||
|
last_counter = key_counter
|
||||||
|
while not finished:
|
||||||
|
door_set = find_potential_open_doors(last_counter, ignored_doors, key_layout, skip_bk, 0)
|
||||||
|
if door_set is None or len(door_set) == 0:
|
||||||
|
finished = True
|
||||||
|
continue
|
||||||
|
for new_door in door_set:
|
||||||
|
proposed_doors = {**opened_doors, **dict.fromkeys([new_door, new_door.dest])}
|
||||||
|
bk_open = bk_opened or new_door.bigKey
|
||||||
|
new_counter = find_counter(proposed_doors, bk_open, key_layout)
|
||||||
|
bk_open = new_counter.big_key_opened
|
||||||
|
if not new_door.bigKey and progressive_ctr(new_counter, last_counter) and relative_empty_counter_2(odd_counter, new_counter):
|
||||||
|
ignored_doors.add(new_door)
|
||||||
|
else:
|
||||||
|
last_counter = new_counter
|
||||||
|
opened_doors = proposed_doors
|
||||||
|
bk_opened = bk_open
|
||||||
|
# this means the new_door invalidates the door / leads to the same stuff
|
||||||
|
return last_counter
|
||||||
|
|
||||||
|
|
||||||
|
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk, reserve=1):
|
||||||
small_doors = []
|
small_doors = []
|
||||||
big_doors = []
|
big_doors = []
|
||||||
for other in key_counter.child_doors:
|
for other in key_counter.child_doors:
|
||||||
@@ -292,7 +455,7 @@ def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
|||||||
if key_layout.big_key_special:
|
if key_layout.big_key_special:
|
||||||
big_key_available = key_counter.big_key_opened
|
big_key_available = key_counter.big_key_opened
|
||||||
else:
|
else:
|
||||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0
|
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 0
|
||||||
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
|
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
|
||||||
return None
|
return None
|
||||||
return small_doors + big_doors
|
return small_doors + big_doors
|
||||||
@@ -366,7 +529,7 @@ def create_rule(key_counter, prev_counter, key_layout, world, player):
|
|||||||
|
|
||||||
|
|
||||||
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player):
|
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player):
|
||||||
if world.accessibility != 'locations':
|
if world.accessibility[player] != 'locations':
|
||||||
counter = find_inverted_counter(door, parent_counter, key_layout, world, player)
|
counter = find_inverted_counter(door, parent_counter, key_layout, world, player)
|
||||||
if not self_lock_possible(counter):
|
if not self_lock_possible(counter):
|
||||||
return
|
return
|
||||||
@@ -488,6 +651,27 @@ def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_la
|
|||||||
# key_layout.key_logic.bk_restricted.update(unique_loc)
|
# key_layout.key_logic.bk_restricted.update(unique_loc)
|
||||||
|
|
||||||
|
|
||||||
|
def find_worst_counter_wo_bk(small_key_num, accessible_set, door, odd_ctr, key_counter, key_layout):
|
||||||
|
if key_counter.big_key_opened:
|
||||||
|
return None, None, None
|
||||||
|
worst_counter = find_worst_counter(door, odd_ctr, key_counter, key_layout, True)
|
||||||
|
bk_rule_num = worst_counter.used_keys + 1
|
||||||
|
bk_access_set = set()
|
||||||
|
bk_access_set.update(worst_counter.free_locations)
|
||||||
|
bk_access_set.update(worst_counter.key_only_locations)
|
||||||
|
if bk_rule_num == small_key_num and len(bk_access_set ^ accessible_set) == 0:
|
||||||
|
return None, None, None
|
||||||
|
door_open = find_next_counter(door, worst_counter, key_layout)
|
||||||
|
ignored_doors = dict_intersection(worst_counter.child_doors, door_open.child_doors)
|
||||||
|
dest_ignored = []
|
||||||
|
for door in ignored_doors.keys():
|
||||||
|
if door.dest not in ignored_doors:
|
||||||
|
dest_ignored.append(door.dest)
|
||||||
|
ignored_doors = {**ignored_doors, **dict.fromkeys(dest_ignored)}
|
||||||
|
post_counter = open_some_counter(door_open, key_layout, ignored_doors.keys())
|
||||||
|
return worst_counter, post_counter, bk_rule_num
|
||||||
|
|
||||||
|
|
||||||
def open_a_door(door, child_state, flat_proposal):
|
def open_a_door(door, child_state, flat_proposal):
|
||||||
if door.bigKey:
|
if door.bigKey:
|
||||||
child_state.big_key_opened = True
|
child_state.big_key_opened = True
|
||||||
@@ -832,6 +1016,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
|||||||
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
||||||
if invalid_self_locking_key(state, prev_state, prev_avail, world, player):
|
if invalid_self_locking_key(state, prev_state, prev_avail, world, player):
|
||||||
return False
|
return False
|
||||||
|
# todo: allow more key shuffles - refine placement rules
|
||||||
|
# if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||||
if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@@ -1233,3 +1419,43 @@ def val_rule(rule, skn, allow=False, loc=None, askn=None, setCheck=None):
|
|||||||
assert len(setCheck) == len(rule.alternate_big_key_loc)
|
assert len(setCheck) == len(rule.alternate_big_key_loc)
|
||||||
for loc in rule.alternate_big_key_loc:
|
for loc in rule.alternate_big_key_loc:
|
||||||
assert loc.name in setCheck
|
assert loc.name in setCheck
|
||||||
|
|
||||||
|
|
||||||
|
# Soft lock stuff
|
||||||
|
def validate_key_placement(key_layout, world, player):
|
||||||
|
if world.retro[player] or world.accessibility[player] == 'none':
|
||||||
|
return True # Can't keylock in retro. Expected if beatable only.
|
||||||
|
max_counter = find_max_counter(key_layout)
|
||||||
|
keys_outside = 0
|
||||||
|
big_key_outside = False
|
||||||
|
dungeon = world.get_dungeon(key_layout.sector.name, player)
|
||||||
|
smallkey_name = 'Small Key (%s)' % (key_layout.sector.name if key_layout.sector.name != 'Hyrule Castle' else 'Escape')
|
||||||
|
if world.keyshuffle[player]:
|
||||||
|
keys_outside = key_layout.max_chests - sum(1 for i in max_counter.free_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player)
|
||||||
|
if world.bigkeyshuffle[player]:
|
||||||
|
max_counter = find_max_counter(key_layout)
|
||||||
|
big_key_outside = dungeon.big_key not in (l.item for l in max_counter.free_locations)
|
||||||
|
|
||||||
|
for counter in key_layout.key_counters.values():
|
||||||
|
if len(counter.child_doors) == 0:
|
||||||
|
continue
|
||||||
|
big_found = any(i.item == dungeon.big_key for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
|
||||||
|
if counter.big_key_opened and not big_found:
|
||||||
|
continue # Can't get to this state
|
||||||
|
found_locations = set(i for i in counter.free_locations if big_found or "- Big Chest" not in i.name)
|
||||||
|
found_keys = sum(1 for i in found_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player) + \
|
||||||
|
len(counter.key_only_locations) + keys_outside
|
||||||
|
can_progress = (not counter.big_key_opened and big_found and any(d.bigKey for d in counter.child_doors)) or \
|
||||||
|
found_keys > counter.used_keys and any(not d.bigKey for d in counter.child_doors)
|
||||||
|
if not can_progress:
|
||||||
|
missing_locations = set(max_counter.free_locations.keys()).difference(found_locations)
|
||||||
|
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name]
|
||||||
|
#missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations?
|
||||||
|
if len(missing_items) > 0: #world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0):
|
||||||
|
logging.getLogger('').error("Keylock - can't open locations: ")
|
||||||
|
for i in missing_locations:
|
||||||
|
logging.getLogger('').error(i)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|||||||
9
Main.py
9
Main.py
@@ -10,6 +10,7 @@ import zlib
|
|||||||
|
|
||||||
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
from KeyDoorShuffle import validate_key_placement
|
||||||
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions
|
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions
|
||||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
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
|
||||||
@@ -23,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
|||||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||||
from Utils import output_path, parse_player_names
|
from Utils import output_path, parse_player_names
|
||||||
|
|
||||||
__version__ = '0.0.14pre'
|
__version__ = '0.0.17pre'
|
||||||
|
|
||||||
|
|
||||||
def main(args, seed=None):
|
def main(args, seed=None):
|
||||||
@@ -56,6 +57,7 @@ def main(args, seed=None):
|
|||||||
world.enemy_health = args.enemy_health.copy()
|
world.enemy_health = args.enemy_health.copy()
|
||||||
world.enemy_damage = args.enemy_damage.copy()
|
world.enemy_damage = args.enemy_damage.copy()
|
||||||
world.beemizer = args.beemizer.copy()
|
world.beemizer = args.beemizer.copy()
|
||||||
|
world.dungeon_counters = args.dungeon_counters.copy()
|
||||||
world.experimental = args.experimental.copy()
|
world.experimental = args.experimental.copy()
|
||||||
|
|
||||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||||
@@ -133,6 +135,11 @@ def main(args, seed=None):
|
|||||||
else:
|
else:
|
||||||
fill_dungeons(world)
|
fill_dungeons(world)
|
||||||
|
|
||||||
|
for player in range(1, world.players+1):
|
||||||
|
for key_layout in world.key_layout[player].values():
|
||||||
|
if not validate_key_placement(key_layout, world, player):
|
||||||
|
raise RuntimeError("Keylock detected: %s (Player %d)" % (key_layout.sector.name, player))
|
||||||
|
|
||||||
logger.info('Fill the world.')
|
logger.info('Fill the world.')
|
||||||
|
|
||||||
if args.algorithm == 'flood':
|
if args.algorithm == 'flood':
|
||||||
|
|||||||
6
Rom.py
6
Rom.py
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '818b2c659a610cb4112804cf612ffd37'
|
RANDOMIZERBASEHASH = '5e01caffabb4509a0987ef2f2f0bcd56'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -618,6 +618,8 @@ def patch_rom(world, rom, player, team, enemized):
|
|||||||
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW':
|
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW':
|
||||||
dr_flags |= DROptions.Open_Desert_Wall
|
dr_flags |= DROptions.Open_Desert_Wall
|
||||||
rom.write_byte(0x139006, dr_flags.value)
|
rom.write_byte(0x139006, dr_flags.value)
|
||||||
|
if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted':
|
||||||
|
rom.write_byte(0x139008, 1)
|
||||||
|
|
||||||
# fix skull woods exit, if not fixed during exit patching
|
# fix skull woods exit, if not fixed during exit patching
|
||||||
if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla':
|
if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla':
|
||||||
@@ -1157,6 +1159,8 @@ def patch_rom(world, rom, player, team, enemized):
|
|||||||
# compasses showing dungeon count
|
# compasses showing dungeon count
|
||||||
if world.clock_mode != 'off':
|
if world.clock_mode != 'off':
|
||||||
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
|
rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location
|
||||||
|
elif world.dungeon_counters[player]:
|
||||||
|
rom.write_byte(0x18003C, 0x02) # show always
|
||||||
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla':
|
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla':
|
||||||
rom.write_byte(0x18003C, 0x01) # show on pickup
|
rom.write_byte(0x18003C, 0x01) # show on pickup
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ DRMode:
|
|||||||
dw 0
|
dw 0
|
||||||
DRFlags:
|
DRFlags:
|
||||||
dw 0
|
dw 0
|
||||||
|
DRScroll:
|
||||||
|
db 0
|
||||||
|
|
||||||
; Vert 0,6,0 Horz 2,0,8
|
; Vert 0,6,0 Horz 2,0,8
|
||||||
org $279010
|
org $279010
|
||||||
|
|||||||
@@ -72,6 +72,13 @@ jsl GtBossHeartCheckOverride : bcs .not_in_ganons_tower
|
|||||||
nop : stz $0dd0, X : rts
|
nop : stz $0dd0, X : rts
|
||||||
.not_in_ganons_tower
|
.not_in_ganons_tower
|
||||||
|
|
||||||
|
|
||||||
|
org $2081f2
|
||||||
|
jsl MirrorCheckOverride2
|
||||||
|
org $20825c
|
||||||
|
jsl MirrorCheckOverride2
|
||||||
|
|
||||||
|
|
||||||
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
|
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
|
||||||
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
|
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
|
||||||
; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds
|
; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds
|
||||||
|
|||||||
@@ -43,9 +43,11 @@ OnFileLoadOverride:
|
|||||||
+ rtl
|
+ rtl
|
||||||
|
|
||||||
MirrorCheckOverride:
|
MirrorCheckOverride:
|
||||||
lda $8A : and #$40 ; what I wrote over
|
|
||||||
beq +
|
|
||||||
lda DRFlags : and #$02 : beq ++
|
lda DRFlags : and #$02 : beq ++
|
||||||
lda $7ef353 : cmp #$01 : beq +
|
lda $7ef353 : cmp #$01 : beq +
|
||||||
++ lda #$01
|
++ lda $8A : and #$40 ; what I wrote over
|
||||||
+ rtl
|
rtl
|
||||||
|
+ lda DRScroll : rtl
|
||||||
|
|
||||||
|
MirrorCheckOverride2:
|
||||||
|
lda $7ef353 : and #$02 : rtl
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user