A lot of generation improvements and bug squashing
This commit is contained in:
@@ -153,9 +153,10 @@ class World(object):
|
||||
self._door_cache[(door.name, door.player)] = door
|
||||
|
||||
def remove_door(self, door, player):
|
||||
if (door, player) in self._door_cache.keys():
|
||||
del self._door_cache[(door, player)]
|
||||
self.doors.remove(door)
|
||||
if (door.name, player) in self._door_cache.keys():
|
||||
del self._door_cache[(door.name, player)]
|
||||
if door in self.doors:
|
||||
self.doors.remove(door)
|
||||
|
||||
def get_regions(self, player=None):
|
||||
return self.regions if player is None else self._region_cache[player].values()
|
||||
@@ -1219,6 +1220,7 @@ class Door(object):
|
||||
# self.connected = False # combine with Dest?
|
||||
self.dest = None
|
||||
self.blocked = False # Indicates if the door is normally blocked off as an exit. (Sanc door or always closed)
|
||||
self.blocked_orig = False
|
||||
self.stonewall = False # Indicate that the door cannot be enter until exited (Desert Torches, PoD Eye Statue)
|
||||
self.smallKey = False # There's a small key door on this side
|
||||
self.bigKey = False # There's a big key door on this side
|
||||
@@ -1230,7 +1232,7 @@ class Door(object):
|
||||
self.dead = False
|
||||
|
||||
self.entrance = entrance
|
||||
if entrance is not None:
|
||||
if entrance is not None and not entrance.door:
|
||||
entrance.door = self
|
||||
|
||||
def getAddress(self):
|
||||
@@ -1316,7 +1318,7 @@ class Door(object):
|
||||
return self
|
||||
|
||||
def no_exit(self):
|
||||
self.blocked = True
|
||||
self.blocked = self.blocked_orig = True
|
||||
return self
|
||||
|
||||
def no_entrance(self):
|
||||
|
||||
@@ -14,11 +14,33 @@ from Items import ItemFactory
|
||||
from RoomData import DoorKind, PairedDoor
|
||||
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths, drop_entrances
|
||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
||||
from DungeonGenerator import dungeon_portals, dungeon_drops
|
||||
from DungeonGenerator import dungeon_portals, dungeon_drops, GenerationException
|
||||
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout
|
||||
|
||||
|
||||
def link_doors(world, player):
|
||||
attempt, valid = 1, False
|
||||
while not valid:
|
||||
try:
|
||||
link_doors_main(world, player)
|
||||
valid = True
|
||||
except GenerationException as e:
|
||||
logging.getLogger('').debug(f'Irreconcilable generation. {str(e)} Starting a new attempt.')
|
||||
attempt += 1
|
||||
if attempt > 10:
|
||||
raise Exception('Could not create world in 10 attempts. Generation algorithms need more work', e)
|
||||
for door in world.doors:
|
||||
if door.player == player:
|
||||
door.dest = None
|
||||
ent = door.entrance
|
||||
if door.type != DoorType.Logical and ent.connected_region is not None:
|
||||
ent.connected_region.entrances = [x for x in ent.connected_region.entrances if x != ent]
|
||||
ent.connected_region = None
|
||||
for portal in world.dungeon_portals[player]:
|
||||
disconnect_portal(portal, world, player)
|
||||
|
||||
|
||||
def link_doors_main(world, player):
|
||||
|
||||
# Drop-down connections & push blocks
|
||||
for exitName, regionName in logical_connections:
|
||||
@@ -45,7 +67,8 @@ def link_doors(world, player):
|
||||
mirror_route = world.get_entrance('Sanctuary Mirror Route', player)
|
||||
mr_door = mirror_route.door
|
||||
sanctuary = mirror_route.parent_region
|
||||
sanctuary.exits.remove(mirror_route)
|
||||
if mirror_route in sanctuary.exits:
|
||||
sanctuary.exits.remove(mirror_route)
|
||||
world.remove_entrance(mirror_route, player)
|
||||
world.remove_door(mr_door, player)
|
||||
|
||||
@@ -388,7 +411,10 @@ def choose_portals(world, player):
|
||||
possible_portals = outstanding_portals if not info.sole_entrance else [x for x in outstanding_portals if x != info.sole_entrance]
|
||||
choice, portal = assign_portal(candidates, possible_portals, world, player)
|
||||
if choice.deadEnd:
|
||||
portal.deadEnd = True
|
||||
if choice.passage:
|
||||
portal.destination = True
|
||||
else:
|
||||
portal.deadEnd = True
|
||||
clean_up_portal_assignment(portal_assignment, dungeon, portal, master_door_list, outstanding_portals)
|
||||
the_rest = info.total - len(portal_assignment[dungeon])
|
||||
for i in range(0, the_rest):
|
||||
@@ -477,7 +503,6 @@ def connect_portal(portal, world, player):
|
||||
ent, ext, entrance_name = portal_map[portal.name]
|
||||
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
|
||||
ext = 'Inverted ' + ext
|
||||
# ent = 'Inverted ' + ent
|
||||
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying
|
||||
target_exit = world.get_entrance(ext, player)
|
||||
portal_entrance.connected_region = target_exit.parent_region
|
||||
@@ -491,22 +516,17 @@ def connect_portal(portal, world, player):
|
||||
portal_entrance.parent_region.entrances.append(edit_entrance)
|
||||
|
||||
|
||||
# todo: remove this?
|
||||
def connect_portal_copy(portal, world, player):
|
||||
def disconnect_portal(portal, world, player):
|
||||
ent, ext, entrance_name = portal_map[portal.name]
|
||||
if world.mode[player] == 'inverted' and portal.name in ['Ganons Tower', 'Agahnims Tower']:
|
||||
ext = 'Inverted ' + ext
|
||||
portal_entrance = world.get_entrance(portal.door.entrance.name, player) # ensures I get the right one for copying
|
||||
target_exit = world.get_entrance(ext, player)
|
||||
portal_entrance.connected_region = target_exit.parent_region
|
||||
portal_region = world.get_region(portal.name + ' Portal', player)
|
||||
portal_region.entrances.append(portal_entrance)
|
||||
portal_entrance = world.get_entrance(portal.door.entrance.name, player)
|
||||
# portal_region = world.get_region(portal.name + ' Portal', player)
|
||||
edit_entrance = world.get_entrance(entrance_name, player)
|
||||
edit_entrance.connected_region = portal_entrance.parent_region
|
||||
chosen_door = world.get_door(portal_entrance.name, player)
|
||||
chosen_door.blocked = False
|
||||
connect_door_only(world, chosen_door, portal_region, player)
|
||||
portal_entrance.parent_region.entrances.append(edit_entrance)
|
||||
|
||||
# reverse work
|
||||
if edit_entrance in portal_entrance.parent_region.entrances:
|
||||
portal_entrance.parent_region.entrances.remove(edit_entrance)
|
||||
chosen_door.blocked = chosen_door.blocked_orig
|
||||
|
||||
|
||||
def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allowed=False, crossed=False, bk_shuffle=False):
|
||||
@@ -710,6 +730,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
|
||||
continue
|
||||
origin_list = list(builder.entrance_list)
|
||||
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
|
||||
split_dungeon = treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player)
|
||||
if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
|
||||
if last_key == builder.name or loops > 1000:
|
||||
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
|
||||
@@ -857,6 +878,22 @@ def aga_tower_enabled(enabled):
|
||||
return False
|
||||
|
||||
|
||||
def treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player):
|
||||
# what about ER dungeons? - find an example? (bad key doors 0 keys not valid)
|
||||
if split_dungeon and name in multiple_portal_map:
|
||||
possible_entrances = []
|
||||
for portal_name in multiple_portal_map[name]:
|
||||
portal = world.get_portal(portal_name, player)
|
||||
portal_entrance = world.get_entrance(portal_map[portal_name][0], player)
|
||||
if not portal.destination and portal_entrance.parent_region.name not in world.inaccessible_regions[player]:
|
||||
possible_entrances.append(portal)
|
||||
if len(possible_entrances) == 1:
|
||||
single_portal = possible_entrances[0]
|
||||
if single_portal.door.entrance.parent_region.name in origin_list and len(origin_list) == 1:
|
||||
return False
|
||||
return split_dungeon
|
||||
|
||||
|
||||
# goals:
|
||||
# 1. have enough chests to be interesting (2 more than dungeon items)
|
||||
# 2. have a balanced amount of regions added (check)
|
||||
@@ -2828,6 +2865,14 @@ portal_map = {
|
||||
'Ganons Tower': ('Ganons Tower', 'Ganons Tower Exit', 'Enter Ganons Tower'),
|
||||
}
|
||||
|
||||
|
||||
multiple_portal_map = {
|
||||
'Hyrule Castle': ['Sanctuary', 'Hyrule Castle West', 'Hyrule Castle South', 'Hyrule Castle East'],
|
||||
'Desert Palace': ['Desert West', 'Desert South', 'Desert East', 'Desert Back'],
|
||||
'Skull Woods': ['Skull 1', 'Skull 2 West', 'Skull 2 East', 'Skull 3'],
|
||||
'Turtle Rock': ['Turtle Rock Lazy Eyes', 'Turtle Rock Eye Bridge', 'Turtle Rock Chest', 'Turtle Rock Main'],
|
||||
}
|
||||
|
||||
split_portals = {
|
||||
'Desert Palace': ['Back', 'Main'],
|
||||
'Skull Woods': ['1', '2', '3']
|
||||
|
||||
1
Doors.py
1
Doors.py
@@ -1301,6 +1301,7 @@ def create_doors(world, player):
|
||||
world.dungeon_portals[player] += dungeon_portals
|
||||
|
||||
world.get_door('Sanctuary S', player).dead_end(allowPassage=True)
|
||||
world.get_door('Eastern Hint Tile Blocked Path SE', player).passage = False
|
||||
world.get_door('TR Big Chest Entrance SE', player).passage = False
|
||||
world.get_door('Sewers Secret Room Key Door S', player).dungeonLink = 'Hyrule Castle'
|
||||
world.get_door('Desert Cannonball S', player).dead_end()
|
||||
|
||||
@@ -1225,15 +1225,15 @@ def simple_dungeon_builder(name, sector_list):
|
||||
def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
||||
dungeon_entrances=None, split_dungeon_entrances=None):
|
||||
logger = logging.getLogger('')
|
||||
logger.info('Shuffling Dungeon Sectors')
|
||||
|
||||
if dungeon_entrances is None:
|
||||
dungeon_entrances = default_dungeon_entrances
|
||||
if split_dungeon_entrances is None:
|
||||
split_dungeon_entrances = split_region_starts
|
||||
define_sector_features(all_sectors)
|
||||
finished, dungeon_map = False, {}
|
||||
finished, dungeon_map, attempts = False, {}, 0
|
||||
while not finished:
|
||||
logger.info('Shuffling Dungeon Sectors')
|
||||
candidate_sectors = dict.fromkeys(all_sectors)
|
||||
global_pole = GlobalPolarity(candidate_sectors)
|
||||
|
||||
@@ -1248,6 +1248,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
||||
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda
|
||||
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
|
||||
candidate_sectors, global_pole)
|
||||
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
|
||||
entrances_map, potentials, connections = connections_tuple
|
||||
accessible_sectors, reverse_d_map = set(), {}
|
||||
for key in dungeon_entrances.keys():
|
||||
@@ -1324,11 +1325,27 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
||||
assign_the_rest(dungeon_map, neutral_sectors, global_pole, builder_info)
|
||||
dungeon_map.update(complete_dungeons)
|
||||
finished = True
|
||||
except NeutralizingException:
|
||||
pass
|
||||
except (NeutralizingException, GenerationException) as e:
|
||||
attempts += 1
|
||||
logger.debug(f'Attempt {attempts} failed with {str(e)}')
|
||||
if attempts >= 10:
|
||||
raise Exception('Could not find a valid seed quickly, something is likely horribly wrong.', e)
|
||||
return dungeon_map
|
||||
|
||||
|
||||
def standard_stair_check(dungeon_map, dungeon, candidate_sectors, global_pole):
|
||||
# this is because there must be at least one non-dead stairway in hc to get out
|
||||
# this check may not be necessary
|
||||
filtered_sectors = [x for x in candidate_sectors if any(y for y in x.outstanding_doors if not y.dead and y.type == DoorType.SpiralStairs)]
|
||||
valid = False
|
||||
while not valid:
|
||||
chosen_sector = random.choice(filtered_sectors)
|
||||
filtered_sectors.remove(chosen_sector)
|
||||
valid = global_pole.is_valid_choice(dungeon_map, dungeon, [chosen_sector])
|
||||
if valid:
|
||||
assign_sector(chosen_sector, dungeon, candidate_sectors, global_pole)
|
||||
|
||||
|
||||
def identify_destination_sectors(accessible_sectors, reverse_d_map, dungeon_map, connections, dungeon_entrances, split_dungeon_entrances):
|
||||
accessible_overworld, found_connections, explored = set(), set(), False
|
||||
|
||||
@@ -1578,6 +1595,8 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
|
||||
if len(crystal_switches) == 0:
|
||||
raise GenerationException('No crystal switches to assign')
|
||||
sector_list = list(crystal_switches)
|
||||
if len(population) > len(sector_list):
|
||||
raise GenerationException('Not enough crystal switch sectors for those needed')
|
||||
choices = random.sample(sector_list, k=len(population))
|
||||
for i, choice in enumerate(choices):
|
||||
builder = dungeon_map[population[i]]
|
||||
@@ -1588,7 +1607,7 @@ def assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barrier
|
||||
def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_sectors, crystal_barriers, global_pole):
|
||||
invalid_builders = []
|
||||
for name, builder in dungeon_map.items():
|
||||
if builder.c_switch_present and not builder.c_locked:
|
||||
if builder.c_switch_present and builder.c_switch_required and not builder.c_locked:
|
||||
invalid_builders.append(builder)
|
||||
while len(invalid_builders) > 0:
|
||||
valid_builders = []
|
||||
@@ -1597,7 +1616,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
|
||||
reachable_crystals = defaultdict()
|
||||
for sector in builder.sectors:
|
||||
if sector.equations is None:
|
||||
sector.equations = calc_sector_equations(sector, builder)
|
||||
sector.equations = calc_sector_equations(sector)
|
||||
if sector.is_entrance_sector() and not sector.destination_entrance:
|
||||
need_switch = True
|
||||
for region in sector.get_start_regions():
|
||||
@@ -1631,7 +1650,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
|
||||
valid, sector, which_list = False, None, None
|
||||
while not valid:
|
||||
if len(candidates) <= 0:
|
||||
raise GenerationException(f'need to provide more sophisticatedted crystal connection for {entrance_sector}')
|
||||
raise GenerationException(f'need to provide more sophisticated crystal connection for {entrance_sector}')
|
||||
sector, which_list = random.choice(list(candidates.items()))
|
||||
del candidates[sector]
|
||||
valid = global_pole.is_valid_choice(dungeon_map, builder, [sector])
|
||||
@@ -1690,7 +1709,7 @@ def find_pol_cand_for_c_switch(access, reachable_crystals, polarized_candidates)
|
||||
|
||||
def pol_cand_matches_access_reach(sector, access, reachable_crystals):
|
||||
if sector.equations is None:
|
||||
sector.equations = calc_sector_equations(sector, None)
|
||||
sector.equations = calc_sector_equations(sector)
|
||||
for eq in sector.equations:
|
||||
key, cost_door = eq.cost
|
||||
if key in access.keys() and access[key]:
|
||||
@@ -1712,7 +1731,7 @@ def find_crystal_cand(access, crystal_switches):
|
||||
|
||||
def crystal_cand_matches_access(sector, access):
|
||||
if sector.equations is None:
|
||||
sector.equations = calc_sector_equations(sector, None)
|
||||
sector.equations = calc_sector_equations(sector)
|
||||
for eq in sector.equations:
|
||||
key, cost_door = eq.cost
|
||||
if key in access.keys() and access[key] and eq.c_switch and len(sector.outstanding_doors) > 1:
|
||||
@@ -1984,6 +2003,9 @@ def polarity_step_3(dungeon_map, polarized_sectors, global_pole):
|
||||
sample_target = 100 if combos > 10 else combos * 2
|
||||
while best_choices is None or samples < sample_target:
|
||||
samples += 1
|
||||
if len(odd_candidates) < len(odd_builders):
|
||||
raise GenerationException(f'Unable to fix dungeon parity - not enough candidates.'
|
||||
f' Ref: {next(iter(odd_builders)).name}')
|
||||
choices = random.sample(odd_candidates, k=len(odd_builders))
|
||||
valid = global_pole.is_valid_multi_choice(dungeon_map, odd_builders, choices)
|
||||
charge = calc_total_charge(dungeon_map, odd_builders, choices)
|
||||
@@ -3649,14 +3671,14 @@ def copy_door_equations(builder, sector_list):
|
||||
for sector in builder.sectors + sector_list:
|
||||
if sector.equations is None:
|
||||
# todo: sort equations?
|
||||
sector.equations = calc_sector_equations(sector, builder)
|
||||
sector.equations = calc_sector_equations(sector)
|
||||
curr_list = equations[sector] = []
|
||||
for equation in sector.equations:
|
||||
curr_list.append(equation.copy())
|
||||
return equations
|
||||
|
||||
|
||||
def calc_sector_equations(sector, builder):
|
||||
def calc_sector_equations(sector):
|
||||
equations = []
|
||||
is_entrance = sector.is_entrance_sector() and not sector.destination_entrance
|
||||
if is_entrance:
|
||||
@@ -3686,6 +3708,8 @@ def calc_door_equation(door, sector, look_for_entrance):
|
||||
eq.benefit[hook_from_door(door)].append(door)
|
||||
eq.required = True
|
||||
eq.c_switch = door.crystal == CrystalBarrier.Either
|
||||
# exceptions for long entrances ???
|
||||
# if door.name in ['PoD Dark Alley']:
|
||||
eq.entrance_flag = True
|
||||
return eq, flag
|
||||
eq = DoorEquation(door)
|
||||
|
||||
@@ -3181,7 +3181,7 @@ default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'),
|
||||
('Sanctuary Grave', 'Sewer Drop'),
|
||||
('Sanctuary Exit', 'Light World'),
|
||||
|
||||
('Old Man Cave (West)', 'Old Man Cave'),
|
||||
('Old Man Cave (West)', 'Old Man Cave Ledge'),
|
||||
('Old Man Cave (East)', 'Old Man Cave'),
|
||||
('Old Man Cave Exit (West)', 'Light World'),
|
||||
('Old Man Cave Exit (East)', 'Death Mountain'),
|
||||
@@ -3327,7 +3327,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
|
||||
('Two Brothers House (West)', 'Two Brothers House'),
|
||||
('Two Brothers House Exit (East)', 'Light World'),
|
||||
('Two Brothers House Exit (West)', 'Maze Race Ledge'),
|
||||
('Sanctuary', 'Sanctuary'),
|
||||
('Sanctuary', 'Sanctuary Portal'),
|
||||
('Sanctuary Grave', 'Sewer Drop'),
|
||||
('Sanctuary Exit', 'Light World'),
|
||||
('Old Man House (Bottom)', 'Old Man House'),
|
||||
@@ -3398,7 +3398,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
|
||||
('Old Man Cave Exit (West)', 'West Dark World'),
|
||||
('Old Man Cave Exit (East)', 'Dark Death Mountain'),
|
||||
('Dark Death Mountain Fairy', 'Old Man Cave'),
|
||||
('Bumper Cave (Bottom)', 'Old Man Cave'),
|
||||
('Bumper Cave (Bottom)', 'Old Man Cave Ledge'),
|
||||
('Bumper Cave (Top)', 'Dark Death Mountain Healer Fairy'),
|
||||
('Bumper Cave Exit (Top)', 'Death Mountain Return Ledge'),
|
||||
('Bumper Cave Exit (Bottom)', 'Light World'),
|
||||
|
||||
@@ -335,8 +335,9 @@ def adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_coun
|
||||
test_set = None
|
||||
needed = rule.needed_keys_w_bk
|
||||
if needed > 0:
|
||||
accessible_loc.update(key_counter.other_locations)
|
||||
blocked_loc = key_layout.all_locations-accessible_loc
|
||||
all_accessible = set(accessible_loc)
|
||||
all_accessible.update(key_counter.other_locations)
|
||||
blocked_loc = key_layout.all_locations-all_accessible
|
||||
for location in blocked_loc:
|
||||
if location not in key_logic.location_rules.keys():
|
||||
loc_rule = LocationRule()
|
||||
@@ -375,8 +376,12 @@ def refine_placement_rules(key_layout, max_ctr):
|
||||
rules_to_remove.append(rule)
|
||||
if rule.bk_relevant and len(rule.check_locations_w_bk) == rule.needed_keys_w_bk + 1:
|
||||
new_restricted = set(max_ctr.free_locations) - rule.check_locations_w_bk
|
||||
if len(new_restricted - key_logic.bk_restricted) > 0:
|
||||
key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations
|
||||
if len(new_restricted | key_logic.bk_restricted) < len(key_layout.all_chest_locations):
|
||||
if len(new_restricted - key_logic.bk_restricted) > 0:
|
||||
key_logic.bk_restricted.update(new_restricted) # bk must be in one of the check_locations
|
||||
changed = True
|
||||
else:
|
||||
rules_to_remove.append(rule)
|
||||
changed = True
|
||||
if rule.needed_keys_w_bk > key_layout.max_chests or len(rule.check_locations_w_bk) < rule.needed_keys_w_bk:
|
||||
logging.getLogger('').warning('Invalid rule - what went wrong here??')
|
||||
@@ -501,6 +506,8 @@ def find_bk_locked_sections(key_layout, world, player):
|
||||
key_layout.all_chest_locations.update(counter.free_locations)
|
||||
key_layout.item_locations.update(counter.free_locations)
|
||||
key_layout.item_locations.update(counter.key_only_locations)
|
||||
key_layout.all_locations.update(key_layout.item_locations)
|
||||
key_layout.all_locations.update(counter.other_locations)
|
||||
if counter.big_key_opened and counter.important_location:
|
||||
big_chest_allowed_big_key = False
|
||||
if not counter.big_key_opened:
|
||||
|
||||
6
Main.py
6
Main.py
@@ -17,7 +17,7 @@ from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||
from EntranceShuffle import link_entrances, link_inverted_entrances
|
||||
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
||||
from Doors import create_doors
|
||||
from DoorShuffle import link_doors, connect_portal_copy
|
||||
from DoorShuffle import link_doors, connect_portal
|
||||
from RoomData import create_rooms
|
||||
from Rules import set_rules
|
||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||
@@ -25,7 +25,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
||||
from ItemList import generate_itempool, difficulties, fill_prizes, fill_specific_items
|
||||
from Utils import output_path, parse_player_names
|
||||
|
||||
__version__ = '0.2.0-dev'
|
||||
__version__ = '0.3.0.0-u'
|
||||
|
||||
class EnemizerError(RuntimeError):
|
||||
pass
|
||||
@@ -477,7 +477,7 @@ def copy_world(world):
|
||||
ret.dungeon_portals = world.dungeon_portals
|
||||
for player, portals in world.dungeon_portals.items():
|
||||
for portal in portals:
|
||||
connect_portal_copy(portal, ret, player)
|
||||
connect_portal(portal, ret, player)
|
||||
ret.sanc_portal = world.sanc_portal
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
|
||||
163
RELEASENOTES.md
163
RELEASENOTES.md
@@ -1,165 +1,16 @@
|
||||
# New Features
|
||||
|
||||
## Lobby shuffle added as Intensity level 3
|
||||
None Yet
|
||||
|
||||
* Standard notes:
|
||||
* The sanctuary is vanilla, and will be missing the exit door until Zelda is rescued
|
||||
* In entrance shuffle the hyrule castle left and right exit door will be missing until Zelda is rescued. This
|
||||
replaces the rails that used to block those lobby exits
|
||||
* In non-entrance shuffle, Agahnims tower can be in logic if you have cape and/or Master sword, but you are never
|
||||
required to beat Agahnim 1 until Zelda is rescued.
|
||||
* Open notes:
|
||||
* The Sanctuary is limited to be in a LW dungeon unless you have ER Crossed or higher enabled
|
||||
* Mirroring from the Sanctuary to the new "Sanctuary" lobby is now in logic, as is exiting there.
|
||||
* In ER crossed or higher, if the Sanctuary is in the Dark World, Link starts as Bunny there until the Moon Pearl
|
||||
is found. Nothing inside that dungeon is in logic until the Moon Pearl is found. (Unless it is a multi-entrance
|
||||
dungeon that you can access from some LW entrance)
|
||||
* Lobby list is found in the spoiler
|
||||
* Exits for Multi-entrance dungeons after beating bosses now makes more sense. Generally you'll exit from a entrance
|
||||
from which the boss can logically be reached. If there are multiple, ones that do not lead to regions only accessible
|
||||
by connector are preferred. The exit is randomly chosen if there's no obvious preference. However, In certain poor
|
||||
cases like Skull Woods in ER, sometimes an exit is chosen not because you can reach the boss from there, but to
|
||||
prevent a potential forced S&Q.
|
||||
* Palette changes:
|
||||
* Certain doors/transition no longer have an effect on the palette choice (dead ends mostly or just bridges)
|
||||
* Sanctuary palette used on the adjacent rooms to Sanctuary (Sanctuary stays the dungeon color for now)
|
||||
* Sewer palette comes back for part of Hyrule Castle for areas "near" the sewer dropdown
|
||||
* There is a setting to keep original palettes (--standardize_palettes original)
|
||||
* Known issues:
|
||||
* Palettes are not perfect
|
||||
* Some ugly colors
|
||||
* Invisible floors can be see in many palettes
|
||||
|
||||
## Key Drop Shuffle
|
||||
|
||||
--keydropshuffle added. This add 33 new locations to the game where keys are found under pots
|
||||
and where enemies drop keys. This includes 32 small key location and the ball and chain guard who normally drop the HC
|
||||
Big Key.
|
||||
|
||||
* Overall location count updated
|
||||
* Setting mentioned in spoiler
|
||||
* Minor change: if a key is Universal or for that dungeon, then if will use the old mechanics of picking up the key without
|
||||
an entire pose and should be obtainable with the hookshot or boomerang as before
|
||||
|
||||
## --mixed_travel setting
|
||||
* Due to Hammerjump, Hovering in PoD Arena, and the Mire Big Key Chest bomb jump two sections of a supertile that are
|
||||
otherwise unconnected logically can be reach using these glitches. To prevent the player from unintentionally
|
||||
* prevent: Rails are added to the 3 spots to prevent these tricks. This setting is recommend for those learning
|
||||
crossed dungeon mode to learn what is dangerous and what is not. No logic seeds ignore this setting.
|
||||
* allow: The rooms are left alone and it is up to the discretion of the player whether to use these tricks or not.
|
||||
* force: The two disjointed sections are forced to be in the same dungeon but performing the glitches is never
|
||||
logically required to complete that game.
|
||||
|
||||
## Keysanity menu redesign
|
||||
|
||||
Redesign of Keysanity Menu complete for crossed dungeon and moved out of experimental.
|
||||
* First screen about Big Keys and Small Keys
|
||||
* 1st Column: The map is required for information about the Big Key
|
||||
* If you don't have the map, it'll be blank until you obtain the Big Key
|
||||
* If have the map:
|
||||
* 0 indicates there is no Big Key for that dungeon
|
||||
* A red symbol indicates the Ball N Chain guard has the big key for that dungeon (does not apply in
|
||||
--keydropshuffle)
|
||||
* Blank if there a big key but you haven't found it yet
|
||||
* 2nd Column displays the current number of keys for that dungeon. Suppressed in retro (always blank)
|
||||
* 3rd Column only displays if you have the map. It shows the number of keys left to collect for that dungeon. If
|
||||
--keydropshuffle is off, this does not count key drops. If on, it does.
|
||||
* (Note: the key columns can display up to 35 using the letters A-Z after 9)
|
||||
* Second screen about Maps / Compass
|
||||
* 1st Column: indicates if you have found the map or not for that dungeon
|
||||
* 2nd and 3rd Column: You must have the compass to see these columns. A two-digit display that shows you how
|
||||
many chests are left in the dungeon. If --keydropshuffle is off, this does not count key drops. If on, it does.
|
||||
|
||||
## Potshuffle by compiling
|
||||
|
||||
Same flag as before but uses python logic written by compiling instead of the enemizer logic-less version.
|
||||
|
||||
## Other features
|
||||
|
||||
### Spoiler log improvements
|
||||
|
||||
* In crossed mode, the new dungeon is listed along with the location designated by a '@' sign
|
||||
* Random gt crystals and ganon crystal are noted in the settings for better reproduction of seeds
|
||||
|
||||
### Experimental features
|
||||
|
||||
* Only the item counter is currently experimental
|
||||
* Item counter is suppressed in Triforce Hunt
|
||||
|
||||
#### Temporary debug features
|
||||
|
||||
* Removed the red square in the upper right corner of the hud if the castle gate is closed
|
||||
## ???
|
||||
|
||||
# Bug Fixes
|
||||
|
||||
* 2.0.20u
|
||||
* Problem with Desert Wall not being pre-opened in intensity 3 fixed
|
||||
* 2.0.19u
|
||||
* Generation improvement
|
||||
* Possible fix for shop vram corruption
|
||||
* The Cane of Byrna does not count as a chest key anymore
|
||||
* 2.0.18u
|
||||
* Generation improvements
|
||||
* Bombs/Dash doors more consistent with the amount in vanilla.
|
||||
* 2.0.17u
|
||||
* Generation improvements
|
||||
* 2.0.16u
|
||||
* Prevent HUD from showing key counter when in the overworld. (Aga 2 doesn't always clear the dungeon indicator)
|
||||
* Fixed key logic regarding certain isolated "important" locations
|
||||
* Fixed a problem with keydropshuffle thinking certain progression items are keys
|
||||
* A couple of inverted rules fixed
|
||||
* A more accurate count of which locations are blocked by teh big key in Ganon's Tower
|
||||
* Updated base rom to 31.0.7 (includes potential hera basement cage fix)
|
||||
* 2.0.15u
|
||||
* Allow Aga Tower lobby door as a a paired keydoor (typo)
|
||||
* Fix portal check for multi-entrance dungeons
|
||||
* 2.0.14u
|
||||
* Removal of key doors no longer messes up certain lobbies
|
||||
* Fixed ER entrances when Desert Back is a connector
|
||||
* 2.0.13u
|
||||
* Minor portal re-work for certain logic and spoiler information
|
||||
* Repaired certain exits wrongly affected by Sanctuary placement (ER crossed + intensity 3)
|
||||
* Fix for inverted ER + intensity 3
|
||||
* Fix for current small keys missing on keysanity menu
|
||||
* Logic added for cases where you can flood Swamp Trench 1 before finding flippers and lock yourself out of getting
|
||||
something behind the trench that leads to the flippers
|
||||
* 2.0.12u
|
||||
* Another fix for animated tiles (fairy fountains)
|
||||
* GT Big Key stat fixed on credits
|
||||
* Any denomination of rupee 20 or below can be removed to make room for Crossed Dungeon's extra dungeon items. This
|
||||
helps retro generate more often.
|
||||
* Fix for TR Lobbies in intensity 3 and ER shuffles that was causing a hardlock
|
||||
* Standard ER logic revised for lobby shuffle and rain state considerations.
|
||||
* 2.0.11u
|
||||
* Fix output path setting in settings.json
|
||||
* Fix trock entrances when intensity <= 2
|
||||
* 2.0.10u
|
||||
* Fix POD, TR, GT and SKULL 3 entrances if sanc ends up in that dungeon in crossed ER+
|
||||
* TR Lobbies that need a bomb and can be entered before bombing should be pre-opened
|
||||
* Animated tiles are loaded correctly in lobbies
|
||||
* If a wallmaster grabs you and the lobby is dark, the lamp turns on now
|
||||
* Certain key rules no longer override item requirements (e.g. Somaria behind TR Hub)
|
||||
* Old Man Cave is correctly one way in the graph
|
||||
* Some key logic fixes
|
||||
* 2.0.9-u
|
||||
* /missing command in MultiClient fixed
|
||||
* 2.0.8-u
|
||||
* Player sprite disappears after picking up a key drop in keydropshuffle
|
||||
* Sewers and Hyrule Castle compass problems
|
||||
* Double count of the Hera Basement Cage item (both overall and compass)
|
||||
* Unnecessary/inconsistent rug cutoff
|
||||
* TR Crystal Maze thought you get through backwards without Somaria
|
||||
* Ensure Thieves Attic Window area can always be reached
|
||||
* Fixed where HC big key was not counted
|
||||
* Prior fixes
|
||||
* Fixed a situation where logic did not account properly for Big Key doors in standard Hyrule Castle
|
||||
* Fixed a problem ER shuffle generation that did not account for lobbies moving around
|
||||
* Fixed a problem with camera unlock (GT Mimics and Mire Minibridge)
|
||||
* Fixed a problem with bad-pseudo layer at PoD map Balcony (unable to hit switch with Bomb)
|
||||
* Fixed a problem with the Ganon hint when hints are turned off
|
||||
* 0.3.0.0-u
|
||||
* Generation improvements. Basic >95% success. Crossed >80% success.
|
||||
* Possible increased generation times as certain generation problem tries a partial re-roll
|
||||
|
||||
# Known Issues
|
||||
|
||||
* Potenial keylocks in multi-entrance dungeons
|
||||
* Incorrect vanilla keylogic for Mire
|
||||
* ER - Potential for Skull Woods West to be completely inaccessible in non-beatable logic
|
||||
* Potential keylocks in multi-entrance dungeons
|
||||
* Incorrect vanilla key logic for Mire
|
||||
2
Rom.py
2
Rom.py
@@ -699,7 +699,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||
for name, pair in boss_indicator.items():
|
||||
dungeon_id, boss_door = pair
|
||||
opposite_door = world.get_door(boss_door, player).dest
|
||||
if opposite_door and opposite_door.roomIndex > -1:
|
||||
if opposite_door and isinstance(opposite_door, Door) and opposite_door.roomIndex > -1:
|
||||
dungeon_name = opposite_door.entrance.parent_region.dungeon.name
|
||||
dungeon_id = boss_indicator[dungeon_name][0]
|
||||
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
|
||||
|
||||
Reference in New Issue
Block a user