diff --git a/CLI.py b/CLI.py index 46d375ce..0565f570 100644 --- a/CLI.py +++ b/CLI.py @@ -140,7 +140,7 @@ def get_settings(): "bigkeyshuffle": False, "keysanity": False, "door_shuffle": "basic", - "experimental": 0, + "experimental": False, "dungeon_counters": "default", "multi": 1, diff --git a/DoorShuffle.py b/DoorShuffle.py index 47e2384b..d32da6c8 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -50,9 +50,9 @@ def link_doors(world, player): connect_one_way(world, ent, ext, player) vanilla_key_logic(world, player) elif world.doorShuffle[player] == 'basic': - if not world.experimental[player]: - for entrance, ext in open_edges: - connect_two_way(world, entrance, ext, player) + # if not world.experimental[player]: + for entrance, ext in open_edges: + connect_two_way(world, entrance, ext, player) within_dungeon(world, player) elif world.doorShuffle[player] == 'crossed': for entrance, ext in open_edges: @@ -139,7 +139,7 @@ def vanilla_key_logic(world, player): enabled_entrances = {} sector_queue = deque(builders) - last_key = None + last_key, loops = None, 0 while len(sector_queue) > 0: builder = sector_queue.popleft() @@ -147,12 +147,14 @@ def vanilla_key_logic(world, player): find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name) origin_list_sans_drops = remove_drop_origins(origin_list) if len(origin_list_sans_drops) <= 0: - if last_key == builder.name: - raise Exception('Infinte loop detected %s' % builder.name) + if last_key == builder.name or loops > 1000: + origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name + raise Exception('Infinite loop detected for "%s" located at %s' % builder.name, origin_name) sector_queue.append(builder) last_key = builder.name + loops += 1 else: - find_new_entrances(builder.master_sector, connections, potentials, enabled_entrances, world, player) + find_new_entrances(builder.master_sector, entrances_map, connections, potentials, enabled_entrances, world, player) start_regions = convert_regions(origin_list, world, player) doors = convert_key_doors(default_small_key_doors[builder.name], world, player) key_layout = build_key_layout(builder, start_regions, doors, world, player) @@ -165,7 +167,7 @@ def vanilla_key_logic(world, player): world.key_logic[player][builder.name] = key_layout.key_logic log_key_logic(builder.name, key_layout.key_logic) last_key = None - if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items': + if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items' and not world.retro[player]: validate_vanilla_key_logic(world, player) @@ -350,7 +352,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_ entrances_map, potentials, connections = connections_tuple enabled_entrances = {} sector_queue = deque(dungeon_builders.values()) - last_key = None + last_key, loops = None, 0 while len(sector_queue) > 0: builder = sector_queue.popleft() split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods') @@ -361,14 +363,16 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_ find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name) origin_list_sans_drops = remove_drop_origins(origin_list) if len(origin_list_sans_drops) <= 0 or name == "Turtle Rock" and not validate_tr(builder, origin_list_sans_drops, world, player): - if last_key == builder.name: - raise Exception('Infinte loop detected %s' % builder.name) + if last_key == builder.name or loops > 1000: + origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name + raise Exception('Infinite loop detected for "%s" located at %s' % builder.name, origin_name) sector_queue.append(builder) last_key = builder.name + loops += 1 else: logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","generating.dungeon"), builder.name) ds = generate_dungeon(builder, origin_list_sans_drops, split_dungeon, world, player) - find_new_entrances(ds, connections, potentials, enabled_entrances, world, player) + find_new_entrances(ds, entrances_map, connections, potentials, enabled_entrances, world, player) ds.name = name builder.master_sector = ds builder.layout_starts = origin_list if len(builder.entrance_list) <= 0 else builder.entrance_list @@ -432,27 +436,50 @@ def remove_drop_origins(entrance_list): return [x for x in entrance_list if x not in drop_entrances] -def find_new_entrances(sector, connections, potentials, enabled, world, player): +def find_new_entrances(sector, entrances_map, connections, potentials, enabled, world, player): for region in sector.regions: if region.name in connections.keys() and (connections[region.name] in potentials.keys() or connections[region.name].name in world.inaccessible_regions[player]): - new_region = connections[region.name] - if new_region in potentials.keys(): - for potential in potentials.pop(new_region): - enabled[potential] = (region.name, region.dungeon) - # see if this unexplored region connects elsewhere - queue = deque(new_region.exits) - visited = set() - while len(queue) > 0: - ext = queue.popleft() - visited.add(ext) - region_name = ext.connected_region.name - if region_name in connections.keys() and connections[region_name] in potentials.keys(): - for potential in potentials.pop(connections[region_name]): - enabled[potential] = (region.name, region.dungeon) - if ext.connected_region.name in world.inaccessible_regions[player]: - for new_exit in ext.connected_region.exits: - if new_exit not in visited: - queue.append(new_exit) + enable_new_entrances(region, connections, potentials, enabled, world, player) + inverted_aga_check(entrances_map, connections, potentials, enabled, world, player) + + +def enable_new_entrances(region, connections, potentials, enabled, world, player): + new_region = connections[region.name] + if new_region in potentials.keys(): + for potential in potentials.pop(new_region): + enabled[potential] = (region.name, region.dungeon) + # see if this unexplored region connects elsewhere + queue = deque(new_region.exits) + visited = set() + while len(queue) > 0: + ext = queue.popleft() + visited.add(ext) + region_name = ext.connected_region.name + if region_name in connections.keys() and connections[region_name] in potentials.keys(): + for potential in potentials.pop(connections[region_name]): + enabled[potential] = (region.name, region.dungeon) + if ext.connected_region.name in world.inaccessible_regions[player]: + for new_exit in ext.connected_region.exits: + if new_exit not in visited: + queue.append(new_exit) + + +def inverted_aga_check(entrances_map, connections, potentials, enabled, world, player): + if world.mode[player] == 'inverted': + if 'Agahnims Tower' in entrances_map.keys() or aga_tower_enabled(enabled): + for region in list(potentials.keys()): + if region.name == 'Hyrule Castle Ledge': + for r_name in potentials[region]: + new_region = world.get_region(r_name, player) + enable_new_entrances(new_region, connections, potentials, enabled, world, player) + + +def aga_tower_enabled(enabled): + for region_name, enabled_tuple in enabled.items(): + entrance, dungeon = enabled_tuple + if dungeon.name == 'Agahnims Tower': + return True + return False def within_dungeon_legacy(world, player): diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 2c009437..98c03006 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -836,6 +836,8 @@ class ExplorationState(object): def add_all_doors_check_keys(self, region, key_door_proposal, world, player): for door in get_doors(world, region, player): if self.can_traverse(door): + if door.controller: + door = door.controller if door in key_door_proposal and door not in self.opened_doors: if not self.in_door_list(door, self.small_doors): self.append_door_to_list(door, self.small_doors) diff --git a/Dungeons.py b/Dungeons.py index 1d4c898c..d7a408f4 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -132,7 +132,6 @@ def fill_dungeons_restrictive(world, shuffled_locations): # with shuffled dungeon items they are distributed as part of the normal item pool for item in world.get_items(): if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]): - all_state_base.collect(item, True) item.advancement = True elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]): item.priority = True @@ -146,7 +145,8 @@ def fill_dungeons_restrictive(world, shuffled_locations): sort_order = {"BigKey": 3, "SmallKey": 2} dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1)) - fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items, True) + fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items, + keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)}, single_player_placement=True) dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A], diff --git a/Fill.py b/Fill.py index d7cb5d7a..69a774bb 100644 --- a/Fill.py +++ b/Fill.py @@ -161,7 +161,7 @@ def distribute_items_staleness(world): logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in itempool], [location.name for location in fill_locations]) -def fill_restrictive(world, base_state, locations, itempool, single_player_placement = False): +def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = None, single_player_placement = False): def sweep_from_pool(): new_state = base_state.copy() for item in itempool: @@ -202,7 +202,7 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place test_state = maximum_exploration_state 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 valid_key_placement(item_to_place, location, itempool, world): + and valid_key_placement(item_to_place, location, itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool, world): spot_to_fill = location break elif item_to_place.smallkey or item_to_place.bigkey: @@ -233,7 +233,7 @@ def valid_key_placement(item, location, itempool, world): 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+world.itempool if x.name == key_logic.small_key_name and x.player == item.player]) + unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player]) return key_logic.check_placement(unplaced_keys) else: inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player]) @@ -290,7 +290,8 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # todo: crossed progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0) - fill_restrictive(world, world.state, fill_locations, progitempool) + fill_restrictive(world, world.state, fill_locations, progitempool, + keys_in_itempool={player: world.keyshuffle[player] for player in range(1, world.players+1)}) random.shuffle(fill_locations) diff --git a/ItemList.py b/ItemList.py index 1cec5aee..ffb6579e 100644 --- a/ItemList.py +++ b/ItemList.py @@ -444,7 +444,7 @@ def fill_prizes(world, attempts=15): prize_locs = list(empty_crystal_locations) random.shuffle(prizepool) random.shuffle(prize_locs) - fill_restrictive(world, all_state, prize_locs, prizepool, True) + fill_restrictive(world, all_state, prize_locs, prizepool, single_player_placement=True) except FillError as e: logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts - attempt - 1) for location in empty_crystal_locations: diff --git a/Main.py b/Main.py index 8a22e47b..d8b73011 100644 --- a/Main.py +++ b/Main.py @@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute from ItemList import generate_itempool, difficulties, fill_prizes from Utils import output_path, parse_player_names, print_wiki_doors_by_region, print_wiki_doors_by_room -__version__ = '0.0.18.4d' +__version__ = '0.0.18.5d' def main(args, seed=None, fish=None): diff --git a/Utils.py b/Utils.py index 43cae912..72d02161 100644 --- a/Utils.py +++ b/Utils.py @@ -3,6 +3,7 @@ import os import re import subprocess import sys +import xml.etree.ElementTree as ET def int16_as_bytes(value): value = value & 0xFFFF @@ -286,6 +287,46 @@ def print_wiki_doors_by_room(d_regions, world, player): with open(os.path.join(".","resources", "user", "rooms-" + d + ".txt"),"w+") as f: f.write(toprint) +def print_xml_doors(d_regions, world, player): + root = ET.Element('root') + for d, region_list in d_regions.items(): + tile_map = {} + for region in region_list: + tile = None + r = world.get_region(region, player) + for ext in r.exits: + door = world.check_for_door(ext.name, player) + if door is not None and door.roomIndex != -1: + tile = door.roomIndex + break + if tile is not None: + if tile not in tile_map: + tile_map[tile] = [] + tile_map[tile].append(r) + dungeon = ET.SubElement(root, 'dungeon', {'name': d}) + for tile, r_list in tile_map.items(): + supertile = ET.SubElement(dungeon, 'supertile', {'id': str(tile)}) + for region in r_list: + room = ET.SubElement(supertile, 'room', {'name': region.name}) + for ext in region.exits: + ET.SubElement(room, 'door', {'name': ext.name}) + ET.dump(root) + + +def print_graph(world): + root = ET.Element('root') + for region in world.regions: + r = ET.SubElement(root, 'region', {'name': region.name}) + for ext in region.exits: + attribs = {'name': ext.name} + if ext.connected_region: + attribs['connected_region'] = ext.connected_region.name + if ext.door and ext.door.dest: + attribs['dest'] = ext.door.dest.name + ET.SubElement(r, 'exit', attribs) + ET.dump(root) + + if __name__ == '__main__': pass # make_new_base2current() diff --git a/asm/doorrando.asm b/asm/doorrando.asm index 646580eb..f14d67c1 100644 --- a/asm/doorrando.asm +++ b/asm/doorrando.asm @@ -19,8 +19,8 @@ incsrc spiral.asm incsrc gfx.asm incsrc keydoors.asm incsrc overrides.asm -incsrc edges.asm -incsrc math.asm +;incsrc edges.asm +;incsrc math.asm warnpc $279000 ; Data Section diff --git a/asm/normal.asm b/asm/normal.asm index fde06011..fda3ed31 100644 --- a/asm/normal.asm +++ b/asm/normal.asm @@ -76,7 +76,8 @@ LoadRoomHorz: sty $06 : sta $07 : lda $a0 : pha ; Store normal room on stack lda $07 : jsr LookupNewRoom ; New room is in A, Room Data is in $00 lda $01 : and.b #$80 : cmp #$80 : bne .gtg - jsr HorzEdge : pla : bcs .end + ; jsr HorzEdge : pla : bcs .end + pla sta $a0 : bra .end ; Restore normal room, abort (straight staircases and open edges can get in this routine) .gtg ;Good to Go! @@ -106,7 +107,8 @@ LoadRoomVert: sty $06 : sta $07 : lda $a0 : pha ; Store normal room on stack lda $07 : jsr LookupNewRoom ; New room is in A, Room Data is in $00 lda $01 : and.b #$80 : cmp #$80 : bne .gtg - jsr VertEdge : pla : bcs .end + ; jsr VertEdge : pla : bcs .end + pla sta $a0 : bra .end ; Restore normal room, abort (straight staircases and open edges can get in this routine) .gtg ;Good to Go! pla ; Throw away normal room (don't fill up the stack)