Crossed Dungeon generation work
-Rewrite of step 3b for accuracy - verified acceptable speed --Can now detect an impossible balance situation and restart -Eliminated false negatives in step 4 (still need to rework false positives) -Minor key logic improvement
This commit is contained in:
@@ -1058,6 +1058,16 @@ class Polarity:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
h = 17
|
||||||
|
spot = self.vector[0]
|
||||||
|
h *= 31 + (spot if spot >= 0 else spot + 100)
|
||||||
|
spot = self.vector[1]
|
||||||
|
h *= 43 + (spot if spot >= 0 else spot + 100)
|
||||||
|
spot = self.vector[2]
|
||||||
|
h *= 73 + (spot if spot >= 0 else spot + 100)
|
||||||
|
return h
|
||||||
|
|
||||||
def is_neutral(self):
|
def is_neutral(self):
|
||||||
for i in range(len(self.vector)):
|
for i in range(len(self.vector)):
|
||||||
if self.vector[i] != 0:
|
if self.vector[i] != 0:
|
||||||
@@ -1116,6 +1126,7 @@ class PolSlot(Enum):
|
|||||||
EastWest = 1
|
EastWest = 1
|
||||||
Stairs = 2
|
Stairs = 2
|
||||||
|
|
||||||
|
|
||||||
class CrystalBarrier(FastEnum):
|
class CrystalBarrier(FastEnum):
|
||||||
Null = 0 # no special requirement
|
Null = 0 # no special requirement
|
||||||
Blue = 1 # blue must be down and explore state set to Blue
|
Blue = 1 # blue must be down and explore state set to Blue
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ 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
|
||||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate
|
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate
|
||||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder
|
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@@ -323,25 +323,35 @@ def within_dungeon(world, player):
|
|||||||
dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
|
dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
|
||||||
dungeon_builders[key].entrance_list = list(entrances_map[key])
|
dungeon_builders[key].entrance_list = list(entrances_map[key])
|
||||||
recombinant_builders = {}
|
recombinant_builders = {}
|
||||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish)
|
builder_info = None, None, world, player
|
||||||
|
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)
|
||||||
|
|
||||||
paths = determine_required_paths(world, player)
|
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
|
||||||
|
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
|
||||||
start = time.process_time()
|
start = time.process_time()
|
||||||
for builder in world.dungeon_layouts[player].values():
|
for builder in world.dungeon_layouts[player].values():
|
||||||
shuffle_key_doors(builder, world, player)
|
shuffle_key_doors(builder, world, player)
|
||||||
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time"), time.process_time()-start)
|
logging.getLogger('').info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time"), time.process_time()-start)
|
||||||
smooth_door_pairs(world, player)
|
smooth_door_pairs(world, player)
|
||||||
|
|
||||||
|
|
||||||
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, fish):
|
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info):
|
||||||
for name, split_list in split_region_starts.items():
|
dungeon_entrances, split_dungeon_entrances, world, player = builder_info
|
||||||
|
if dungeon_entrances is None:
|
||||||
|
dungeon_entrances = default_dungeon_entrances
|
||||||
|
if split_dungeon_entrances is None:
|
||||||
|
split_dungeon_entrances = split_region_starts
|
||||||
|
builder_info = dungeon_entrances, split_region_starts, world, player
|
||||||
|
|
||||||
|
for name, split_list in split_dungeon_entrances.items():
|
||||||
builder = dungeon_builders.pop(name)
|
builder = dungeon_builders.pop(name)
|
||||||
recombinant_builders[name] = builder
|
recombinant_builders[name] = builder
|
||||||
split_builders = split_dungeon_builder(builder, split_list, fish)
|
|
||||||
|
split_builders = split_dungeon_builder(builder, split_list, builder_info)
|
||||||
dungeon_builders.update(split_builders)
|
dungeon_builders.update(split_builders)
|
||||||
for sub_name, split_entrances in split_list.items():
|
for sub_name, split_entrances in split_list.items():
|
||||||
sub_builder = dungeon_builders[name+' '+sub_name]
|
sub_builder = dungeon_builders[name+' '+sub_name]
|
||||||
@@ -358,6 +368,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
|||||||
enabled_entrances = {}
|
enabled_entrances = {}
|
||||||
sector_queue = deque(dungeon_builders.values())
|
sector_queue = deque(dungeon_builders.values())
|
||||||
last_key, loops = None, 0
|
last_key, loops = None, 0
|
||||||
|
logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon"))
|
||||||
while len(sector_queue) > 0:
|
while len(sector_queue) > 0:
|
||||||
builder = sector_queue.popleft()
|
builder = sector_queue.popleft()
|
||||||
split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods')
|
split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods')
|
||||||
@@ -374,7 +385,6 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
|||||||
last_key = builder.name
|
last_key = builder.name
|
||||||
loops += 1
|
loops += 1
|
||||||
else:
|
else:
|
||||||
logging.getLogger('').info('%s: %s', world.fish.translate("cli", "cli", "generating.dungeon"), builder.name)
|
|
||||||
ds = generate_dungeon(builder, origin_list, split_dungeon, world, player)
|
ds = generate_dungeon(builder, origin_list, split_dungeon, world, player)
|
||||||
find_new_entrances(ds, entrances_map, connections, potentials, enabled_entrances, world, player)
|
find_new_entrances(ds, entrances_map, connections, potentials, enabled_entrances, world, player)
|
||||||
ds.name = name
|
ds.name = name
|
||||||
@@ -510,7 +520,8 @@ 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 = {}
|
||||||
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish)
|
builder_info = None, None, world, player
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -542,6 +553,7 @@ def cross_dungeon(world, player):
|
|||||||
|
|
||||||
|
|
||||||
def assign_cross_keys(dungeon_builders, world, player):
|
def assign_cross_keys(dungeon_builders, world, player):
|
||||||
|
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
|
||||||
start = time.process_time()
|
start = time.process_time()
|
||||||
total_keys = remaining = 29
|
total_keys = remaining = 29
|
||||||
total_candidates = 0
|
total_candidates = 0
|
||||||
@@ -594,7 +606,7 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
while len(queue) > 0 and remaining > 0:
|
while len(queue) > 0 and remaining > 0:
|
||||||
builder = queue.popleft()
|
builder = queue.popleft()
|
||||||
name = builder.name
|
name = builder.name
|
||||||
logger.info('Cross Dungeon: Increasing key count by 1 for %s', name)
|
logger.debug('Cross Dungeon: Increasing key count by 1 for %s', name)
|
||||||
builder.key_doors_num += 1
|
builder.key_doors_num += 1
|
||||||
result = find_valid_combination(builder, start_regions_map[name], world, player, drop_keys=False)
|
result = find_valid_combination(builder, start_regions_map[name], world, player, drop_keys=False)
|
||||||
if result:
|
if result:
|
||||||
@@ -605,10 +617,10 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
queue.append(builder)
|
queue.append(builder)
|
||||||
queue = deque(sorted(queue, key=lambda b: b.combo_size))
|
queue = deque(sorted(queue, key=lambda b: b.combo_size))
|
||||||
else:
|
else:
|
||||||
logger.info('Cross Dungeon: Increase failed for %s', name)
|
logger.debug('Cross Dungeon: Increase failed for %s', name)
|
||||||
builder.key_doors_num -= 1
|
builder.key_doors_num -= 1
|
||||||
builder.flex = 0
|
builder.flex = 0
|
||||||
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
logger.debug('Cross Dungeon: Keys unable to assign in pool %s', remaining)
|
||||||
|
|
||||||
# Last Step: Adjust Small Key Dungeon Pool
|
# Last Step: Adjust Small Key Dungeon Pool
|
||||||
if not world.retro[player]:
|
if not world.retro[player]:
|
||||||
@@ -621,7 +633,7 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
dungeon.small_keys = []
|
dungeon.small_keys = []
|
||||||
else:
|
else:
|
||||||
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
|
||||||
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time.crossed"), time.process_time()-start)
|
logger.info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed"), time.process_time()-start)
|
||||||
|
|
||||||
|
|
||||||
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
def reassign_boss(boss_region, boss_key, builder, gt, world, player):
|
||||||
@@ -778,7 +790,6 @@ def calc_used_dungeon_items(builder):
|
|||||||
|
|
||||||
def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
|
def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
logger.info('%s %s', world.fish.translate("cli","cli","shuffling.keydoors"), builder.name)
|
|
||||||
# find valid combination of candidates
|
# find valid combination of candidates
|
||||||
if len(builder.candidates) < builder.key_doors_num:
|
if len(builder.candidates) < builder.key_doors_num:
|
||||||
if not drop_keys:
|
if not drop_keys:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -975,23 +975,26 @@ def filter_big_chest(locations):
|
|||||||
def count_free_locations(state):
|
def count_free_locations(state):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for loc in state.found_locations:
|
for loc in state.found_locations:
|
||||||
if '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in key_only_locations and loc.name not in ['Agahnim 1', 'Agahnim 2', 'Hyrule Castle - Big Key Drop']:
|
if '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item:
|
||||||
cnt += 1
|
if loc.name not in ['Agahnim 1', 'Agahnim 2']:
|
||||||
|
cnt += 1
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
|
||||||
def count_locations_exclude_big_chest(state):
|
def count_locations_exclude_big_chest(state):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for loc in state.found_locations:
|
for loc in state.found_locations:
|
||||||
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in key_only_locations and loc.name not in ['Agahnim 1', 'Agahnim 2', 'Hyrule Castle - Big Key Drop']:
|
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events:
|
||||||
cnt += 1
|
if not loc.forced_item and loc.name not in ['Agahnim 1', 'Agahnim 2', "Hyrule Castle - Zelda's Chest",
|
||||||
|
"Thieves' Town - Blind's Cell"]:
|
||||||
|
cnt += 1
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
|
||||||
def count_key_only_locations(state):
|
def count_key_only_locations(state):
|
||||||
cnt = 0
|
cnt = 0
|
||||||
for loc in state.found_locations:
|
for loc in state.found_locations:
|
||||||
if loc.name in key_only_locations:
|
if loc.forced_item and loc.item.smallkey:
|
||||||
cnt += 1
|
cnt += 1
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
|||||||
@@ -730,7 +730,7 @@ def create_dungeon_regions(world, player):
|
|||||||
world.get_region('Hera Tridorm', player).crystal_switch = True
|
world.get_region('Hera Tridorm', player).crystal_switch = True
|
||||||
world.get_region('Hera Startile Wide', player).crystal_switch = True
|
world.get_region('Hera Startile Wide', player).crystal_switch = True
|
||||||
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
|
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 # LADDER not accessible (maybe with cane)
|
||||||
world.get_region('PoD Dark Pegs', player).crystal_switch = True
|
world.get_region('PoD Dark Pegs', player).crystal_switch = True
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"shuffling.world": "Shuffling the World about",
|
"shuffling.world": "Shuffling the World about",
|
||||||
"shuffling.dungeons": "Shuffling dungeons",
|
"shuffling.dungeons": "Shuffling dungeons",
|
||||||
"basic.traversal": "--Basic Traversal",
|
"basic.traversal": "--Basic Traversal",
|
||||||
"generating.dungeon": "Generating dungeon",
|
"generating.dungeon": "Generating dungeons",
|
||||||
"shuffling.keydoors": "Shuffling Key doors for",
|
"shuffling.keydoors": "Shuffling Key doors for",
|
||||||
"lowering.keys.candidates": "Lowering key door count because not enough candidates",
|
"lowering.keys.candidates": "Lowering key door count because not enough candidates",
|
||||||
"lowering.keys.layouts": "Lowering key door count because no valid layouts",
|
"lowering.keys.layouts": "Lowering key door count because no valid layouts",
|
||||||
|
|||||||
Reference in New Issue
Block a user