Separate doorShuffle for each player

Add doorShuffle to spoiler metadata
This commit is contained in:
compiling
2020-01-11 12:01:21 +11:00
parent a3a706d8c1
commit 4d35a02e99
6 changed files with 32 additions and 28 deletions

View File

@@ -16,7 +16,7 @@ class World(object):
def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints): def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints):
self.players = players self.players = players
self.shuffle = shuffle.copy() self.shuffle = shuffle.copy()
self.doorShuffle = doorShuffle self.doorShuffle = doorShuffle.copy()
self.logic = logic.copy() self.logic = logic.copy()
self.mode = mode.copy() self.mode = mode.copy()
self.swords = swords.copy() self.swords = swords.copy()
@@ -1510,6 +1510,7 @@ class Spoiler(object):
'weapons': self.world.swords, 'weapons': self.world.swords,
'goal': self.world.goal, 'goal': self.world.goal,
'shuffle': self.world.shuffle, 'shuffle': self.world.shuffle,
'door_shuffle': self.world.doorShuffle,
'item_pool': self.world.difficulty, 'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments, 'item_functionality': self.world.difficulty_adjustments,
'gt_crystals': self.world.crystals_needed_for_gt, 'gt_crystals': self.world.crystals_needed_for_gt,
@@ -1560,6 +1561,7 @@ class Spoiler(object):
outfile.write('Difficulty: %s\n' % self.metadata['item_pool']) outfile.write('Difficulty: %s\n' % self.metadata['item_pool'])
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality']) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'])
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle']) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'])
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'])
outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals']) outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'])
outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals']) outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'])
outfile.write('Pyramid hole pre-opened: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['open_pyramid'].items()}) outfile.write('Pyramid hole pre-opened: %s\n' % {k: 'Yes' if v else 'No' for k, v in self.metadata['open_pyramid'].items()})

View File

@@ -39,7 +39,7 @@ def link_doors(world, player):
for ent, ext in ladders: for ent, ext in ladders:
connect_two_way(world, ent, ext, player) connect_two_way(world, ent, ext, player)
if world.doorShuffle == 'vanilla': if world.doorShuffle[player] == 'vanilla':
for exitName, regionName in vanilla_logical_connections: for exitName, regionName in vanilla_logical_connections:
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:
@@ -49,14 +49,14 @@ def link_doors(world, player):
for ent, ext in default_one_way_connections: for ent, ext in default_one_way_connections:
connect_one_way(world, ent, ext, player) connect_one_way(world, ent, ext, player)
vanilla_key_logic(world, player) vanilla_key_logic(world, player)
elif world.doorShuffle == 'basic': elif world.doorShuffle[player] == 'basic':
within_dungeon(world, player) within_dungeon(world, player)
elif world.doorShuffle == 'crossed': elif world.doorShuffle[player] == 'crossed':
cross_dungeon(world, player) cross_dungeon(world, player)
elif world.doorShuffle == 'experimental': elif world.doorShuffle[player] == 'experimental':
experiment(world, player) experiment(world, player)
if world.doorShuffle != 'vanilla': if world.doorShuffle[player] != 'vanilla':
create_door_spoiler(world, player) create_door_spoiler(world, player)
@@ -83,7 +83,7 @@ def mark_regions(world, player):
def create_door_spoiler(world, player): def create_door_spoiler(world, player):
logger = logging.getLogger('') logger = logging.getLogger('')
queue = collections.deque(world.doors) queue = collections.deque((door for door in world.doors if door.player == player))
while len(queue) > 0: while len(queue) > 0:
door_a = queue.popleft() door_a = queue.popleft()
if door_a.type in [DoorType.Normal, DoorType.SpiralStairs]: if door_a.type in [DoorType.Normal, DoorType.SpiralStairs]:
@@ -283,7 +283,7 @@ def within_dungeon(world, player):
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map) handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player) main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
paths = determine_required_paths(world) paths = determine_required_paths(world, player)
check_required_paths(paths, world, player) check_required_paths(paths, world, player)
# shuffle_key_doors for dungeons # shuffle_key_doors for dungeons
@@ -637,7 +637,7 @@ def cross_dungeon(world, player):
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player) main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
paths = determine_required_paths(world) paths = determine_required_paths(world, player)
check_required_paths(paths, world, player) check_required_paths(paths, world, player)
start = time.process_time() start = time.process_time()
@@ -1097,7 +1097,7 @@ def change_door_to_small_key(d, world, player):
room.change(d.doorListPos, DoorKind.SmallKey) room.change(d.doorListPos, DoorKind.SmallKey)
def determine_required_paths(world): def determine_required_paths(world, player):
paths = { paths = {
'Hyrule Castle': [], 'Hyrule Castle': [],
'Eastern Palace': ['Eastern Boss'], 'Eastern Palace': ['Eastern Boss'],
@@ -1122,7 +1122,7 @@ def determine_required_paths(world):
paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock') paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock')
# noinspection PyTypeChecker # noinspection PyTypeChecker
paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary')) paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
if world.doorShuffle in ['basic', 'experimental']: # todo: crossed? if world.doorShuffle[player] in ['basic', 'experimental']: # todo: crossed?
paths['Thieves Town'].append('Thieves Attic Window') paths['Thieves Town'].append('Thieves Attic Window')
return paths return paths

View File

@@ -298,7 +298,7 @@ def parse_arguments(argv, no_defaults=False):
playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True) playerargs = parse_arguments(shlex.split(getattr(ret,f"p{player}")), True)
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'retro', 'accessibility', 'hints', 'beemizer', 'retro', 'accessibility', 'hints', 'beemizer',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',

View File

@@ -95,7 +95,7 @@ def build_key_layout(builder, start_regions, proposal, world, player):
def calc_max_chests(builder, key_layout, world, player): def calc_max_chests(builder, key_layout, world, player):
if world.doorShuffle != 'crossed': if world.doorShuffle[player] != 'crossed':
return len(world.get_dungeon(key_layout.sector.name, player).small_keys) return len(world.get_dungeon(key_layout.sector.name, player).small_keys)
return builder.key_doors_num - key_layout.max_drops return builder.key_doors_num - key_layout.max_drops
@@ -129,7 +129,7 @@ def analyze_dungeon(key_layout, world, player):
child_queue = collections.deque() child_queue = collections.deque()
for child in key_counter.child_doors.keys(): for child in key_counter.child_doors.keys():
if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened: if not child.bigKey or not key_layout.big_key_special or key_counter.big_key_opened:
odd_counter = create_odd_key_counter(child, key_counter, key_layout, world) odd_counter = create_odd_key_counter(child, key_counter, key_layout, world, player)
if not empty_counter(odd_counter) and child not in doors_completed: if not empty_counter(odd_counter) and child not in doors_completed:
child_queue.append((child, odd_counter)) child_queue.append((child, odd_counter))
while len(child_queue) > 0: while len(child_queue) > 0:
@@ -137,7 +137,7 @@ def analyze_dungeon(key_layout, world, player):
if not child.bigKey: if not child.bigKey:
best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False) best_counter = find_best_counter(child, odd_counter, key_counter, key_layout, world, player, False)
rule = create_rule(best_counter, key_counter, key_layout, world, player) rule = create_rule(best_counter, key_counter, key_layout, world, player)
check_for_self_lock_key(rule, child, best_counter, key_layout, world) check_for_self_lock_key(rule, child, best_counter, key_layout, world, player)
bk_restricted_rules(rule, child, odd_counter, key_counter, key_layout, world, player) bk_restricted_rules(rule, child, odd_counter, key_counter, key_layout, world, player)
key_logic.door_rules[child.name] = rule key_logic.door_rules[child.name] = rule
doors_completed.add(child) doors_completed.add(child)
@@ -330,9 +330,9 @@ def create_rule(key_counter, prev_counter, key_layout, world, player):
return DoorRules(rule_num) return DoorRules(rule_num)
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world): def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player):
if world.accessibility != 'locations': if world.accessibility != 'locations':
counter = find_inverted_counter(door, parent_counter, key_layout, world) 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
if len(counter.free_locations) == 1 and len(counter.key_only_locations) == 0 and not counter.important_location: if len(counter.free_locations) == 1 and len(counter.key_only_locations) == 0 and not counter.important_location:
@@ -340,7 +340,7 @@ def check_for_self_lock_key(rule, door, parent_counter, key_layout, world):
rule.small_location = next(iter(counter.free_locations)) rule.small_location = next(iter(counter.free_locations))
def find_inverted_counter(door, parent_counter, key_layout, world): def find_inverted_counter(door, parent_counter, key_layout, world, player):
# open all doors in counter # open all doors in counter
counter = open_all_counter(parent_counter, key_layout, door=door) counter = open_all_counter(parent_counter, key_layout, door=door)
max_counter = find_max_counter(key_layout) max_counter = find_max_counter(key_layout)
@@ -352,7 +352,7 @@ def find_inverted_counter(door, parent_counter, key_layout, world):
inverted_counter.open_doors = dict_difference(max_counter.open_doors, counter.open_doors) inverted_counter.open_doors = dict_difference(max_counter.open_doors, counter.open_doors)
inverted_counter.other_locations = dict_difference(max_counter.other_locations, counter.other_locations) inverted_counter.other_locations = dict_difference(max_counter.other_locations, counter.other_locations)
for loc in inverted_counter.other_locations: for loc in inverted_counter.other_locations:
if important_location(loc, world): if important_location(loc, world, player):
inverted_counter.important_location = True inverted_counter.important_location = True
return inverted_counter return inverted_counter
@@ -732,7 +732,7 @@ def create_key_counter(state, key_layout, world, player):
key_counter = KeyCounter(key_layout.max_chests) key_counter = KeyCounter(key_layout.max_chests)
key_counter.child_doors.update(dict.fromkeys(unique_doors(state.small_doors+state.big_doors))) key_counter.child_doors.update(dict.fromkeys(unique_doors(state.small_doors+state.big_doors)))
for loc in state.found_locations: for loc in state.found_locations:
if important_location(loc, world): if important_location(loc, world, player):
key_counter.important_location = True key_counter.important_location = True
key_counter.other_locations[loc] = None key_counter.other_locations[loc] = None
elif loc.event and 'Small Key' in loc.item.name: elif loc.event and 'Small Key' in loc.item.name:
@@ -755,14 +755,14 @@ def create_key_counter(state, key_layout, world, player):
return key_counter return key_counter
def important_location(loc, world): def important_location(loc, world, player):
important_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] important_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
if world.mode == 'standard' or world.doorShuffle == 'crossed': if world.mode[player] == 'standard' or world.doorShuffle[player] == 'crossed':
important_locations.append('Hyrule Dungeon Cellblock') important_locations.append('Hyrule Dungeon Cellblock')
return '- Prize' in loc.name or loc.name in important_locations return '- Prize' in loc.name or loc.name in important_locations
def create_odd_key_counter(door, parent_counter, key_layout, world): def create_odd_key_counter(door, parent_counter, key_layout, world, player):
odd_counter = KeyCounter(key_layout.max_chests) odd_counter = KeyCounter(key_layout.max_chests)
next_counter = find_next_counter(door, parent_counter, key_layout) next_counter = find_next_counter(door, parent_counter, key_layout)
odd_counter.free_locations = dict_difference(next_counter.free_locations, parent_counter.free_locations) odd_counter.free_locations = dict_difference(next_counter.free_locations, parent_counter.free_locations)
@@ -770,7 +770,7 @@ def create_odd_key_counter(door, parent_counter, key_layout, world):
odd_counter.child_doors = dict_difference(next_counter.child_doors, parent_counter.child_doors) odd_counter.child_doors = dict_difference(next_counter.child_doors, parent_counter.child_doors)
odd_counter.other_locations = dict_difference(next_counter.other_locations, parent_counter.other_locations) odd_counter.other_locations = dict_difference(next_counter.other_locations, parent_counter.other_locations)
for loc in odd_counter.other_locations: for loc in odd_counter.other_locations:
if important_location(loc, world): if important_location(loc, world, player):
odd_counter.important_location = True odd_counter.important_location = True
return odd_counter return odd_counter

View File

@@ -145,7 +145,7 @@ def main(args, seed=None):
logger.info('Patching ROM.') logger.info('Patching ROM.')
player_names = parse_names_string(args.names) player_names = parse_names_string(args.names)
outfilebase = 'ER_%s' % (args.outputname if args.outputname else world.seed) outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed)
rom_names = [] rom_names = []
jsonout = {} jsonout = {}
@@ -225,7 +225,7 @@ def main(args, seed=None):
def copy_world(world): def copy_world(world):
# ToDo: Not good yet # ToDo: Not good yet
ret = World(world.players, world.shuffle, world.door_shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints) ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
ret.required_medallions = world.required_medallions.copy() ret.required_medallions = world.required_medallions.copy()
ret.swamp_patch_required = world.swamp_patch_required.copy() ret.swamp_patch_required = world.swamp_patch_required.copy()
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
@@ -264,6 +264,8 @@ def copy_world(world):
else: else:
create_inverted_regions(ret, player) create_inverted_regions(ret, player)
create_shops(ret, player) create_shops(ret, player)
create_doors(ret, player)
create_rooms(ret, player)
create_dungeons(ret, player) create_dungeons(ret, player)
copy_dynamic_regions_and_locations(world, ret) copy_dynamic_regions_and_locations(world, ret)

4
Rom.py
View File

@@ -579,7 +579,7 @@ def patch_rom(world, player, rom, enemized):
patch_shuffled_dark_sanc(world, rom, player) patch_shuffled_dark_sanc(world, rom, player)
# patch doors # patch doors
if world.doorShuffle == 'crossed': if world.doorShuffle[player] == 'crossed':
rom.write_byte(0x151f1, 2) rom.write_byte(0x151f1, 2)
rom.write_byte(0x15270, 2) rom.write_byte(0x15270, 2)
rom.write_byte(0x1597b, 2) rom.write_byte(0x1597b, 2)
@@ -1137,7 +1137,7 @@ def patch_rom(world, player, rom, 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.compassshuffle[player] or world.doorShuffle != '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:
rom.write_byte(0x18003C, 0x00) rom.write_byte(0x18003C, 0x00)