Merged in DR v1.2.0.9

This commit is contained in:
codemann8
2023-02-24 16:14:00 -06:00
12 changed files with 152 additions and 63 deletions

View File

@@ -129,7 +129,7 @@ def link_doors_prep(world, player):
vanilla_key_logic(world, player)
def link_doors_main(world, player):
def create_dungeon_pool(world, player):
pool = None
if world.doorShuffle[player] == 'basic':
pool = [([name], regions) for name, regions in dungeon_regions.items()]
@@ -143,6 +143,11 @@ def link_doors_main(world, player):
elif world.doorShuffle[player] != 'vanilla':
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
raise Exception('Invalid door shuffle setting: %s' % world.doorShuffle[player])
return pool
def link_doors_main(world, player):
pool = create_dungeon_pool(world, player)
if pool:
main_dungeon_pool(pool, world, player)
if world.doorShuffle[player] != 'vanilla':
@@ -558,7 +563,9 @@ def customizer_portals(master_door_list, world, player):
custom_doors = world.customizer.get_doors()[player]
if custom_doors and 'lobbies' in custom_doors:
for portal, assigned_door in custom_doors['lobbies'].items():
door = next(x for x in master_door_list if x.name == assigned_door)
door = next((x for x in master_door_list if x.name == assigned_door), None)
if door is None:
raise Exception(f'{assigned_door} not found. Check for typos')
custom_portals[portal] = door
assigned_doors.add(door)
if custom_doors and 'doors' in custom_doors:
@@ -571,6 +578,24 @@ def customizer_portals(master_door_list, world, player):
elif 'dest' in dest:
door = world.get_door(dest['dest'], player)
assigned_doors.add(door)
# restricts connected doors to the customized portals
if assigned_doors:
pool = create_dungeon_pool(world, player)
if pool:
pool_map = {}
for pool, region_list in pool:
sector_pool = convert_to_sectors(region_list, world, player)
merge_sectors(sector_pool, world, player)
for p in pool:
pool_map[p] = sector_pool
for portal, assigned_door in custom_portals.items():
portal_region = world.get_door(assigned_door, player).entrance.parent_region
portal_dungeon = world.get_region(f'{portal} Portal', player).dungeon.name
sector_pool = pool_map[portal_dungeon]
sector = next((s for s in sector_pool if portal_region in s.regions), None)
for door in sector.outstanding_doors:
if door.portalAble:
door.dungeonLink = portal_dungeon
return custom_portals, assigned_doors
@@ -1719,7 +1744,6 @@ def setup_custom_door_types(world, player):
custom_doors = custom_doors[player]
if 'doors' not in custom_doors:
return
# todo: dash/bomb door pool specific
customizeable_types = ['Key Door', 'Dash Door', 'Bomb Door', 'Trap Door', 'Big Key Door']
world.custom_door_types[player] = type_map = {x: defaultdict(list) for x in customizeable_types}
for door, dest in custom_doors['doors'].items():
@@ -1732,7 +1756,7 @@ def setup_custom_door_types(world, player):
type_map[door_kind][dungeon.name].append(d)
else:
# check if the dest is paired
if d.dest.type in [DoorType.Interior, DoorType.Normal] and door_kind != 'Trap Door':
if d.dest and d.dest.type in [DoorType.Interior, DoorType.Normal] and door_kind != 'Trap Door':
type_map[door_kind][dungeon.name].append((d, d.dest))
else:
type_map[door_kind][dungeon.name].append(d)
@@ -1784,18 +1808,24 @@ def shuffle_door_types(door_type_pools, paths, world, player):
start_regions_map[name] = start_regions
builder.candidates = BuilderDoorCandidates()
all_custom = defaultdict(list)
if player in world.custom_door_types:
for custom_dict in world.custom_door_types[player].values():
for dungeon, doors in custom_dict.items():
all_custom[dungeon].extend(doors)
world.paired_doors[player].clear()
used_doors = shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player)
used_doors = shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, world, player)
# big keys
used_doors = shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world, player)
used_doors = shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player)
# small keys
used_doors = shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player)
used_doors = shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player)
# bombable / dashable
used_doors = shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, world, player)
used_doors = shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player)
# handle paired list
def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player):
def shuffle_trap_doors(door_type_pools, paths, start_regions_map, all_custom, world, player):
used_doors = set()
for pool, door_type_pool in door_type_pools:
if world.trap_door_mode[player] != 'oneway':
@@ -1806,15 +1836,14 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player)
custom_trap_doors = world.custom_door_types[player]['Trap Door']
else:
custom_trap_doors = defaultdict(list)
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
if 'Mire Warping Pool' in builder.master_sector.region_set():
custom_trap_doors[dungeon].append(world.get_door('Mire Warping Pool ES', player))
world.custom_door_types[player]['Trap Door'] = custom_trap_doors
find_trappable_candidates(builder, world, player)
if custom_trap_doors[dungeon]:
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, custom_trap_doors[dungeon])
if all_custom[dungeon]:
builder.candidates.trap = filter_key_door_pool(builder.candidates.trap, all_custom[dungeon])
remaining -= len(custom_trap_doors[dungeon])
ttl += len(builder.candidates.trap)
if ttl == 0:
@@ -1866,7 +1895,7 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player)
return used_doors
def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player):
for pool, door_type_pool in door_type_pools:
ttl = 0
suggestion_map, bk_map, flex_map = {}, {}, {}
@@ -1879,8 +1908,8 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
find_big_key_candidates(builder, start_regions_map[dungeon], used_doors, world, player)
if custom_bk_doors[dungeon]:
builder.candidates.big = filter_key_door_pool(builder.candidates.big, custom_bk_doors[dungeon])
if all_custom[dungeon]:
builder.candidates.big = filter_key_door_pool(builder.candidates.big, all_custom[dungeon])
remaining -= len(custom_bk_doors[dungeon])
ttl += len(builder.candidates.big)
if ttl == 0:
@@ -1926,7 +1955,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
return used_doors
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player):
max_computation = 11 # this is around 6 billion worse case factorial don't want to exceed this much
for pool, door_type_pool in door_type_pools:
ttl = 0
@@ -1944,8 +1973,8 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
builder.total_keys = total_keys
find_small_key_door_candidates(builder, start_regions_map[dungeon], used_doors, world, player)
custom_doors = 0
if custom_key_doors[dungeon]:
builder.candidates.small = filter_key_door_pool(builder.candidates.small, custom_key_doors[dungeon])
if all_custom[dungeon]:
builder.candidates.small = filter_key_door_pool(builder.candidates.small, all_custom[dungeon])
custom_doors = len(custom_key_doors[dungeon])
remaining -= custom_doors
builder.key_doors_num = max(0, len(builder.candidates.small) - builder.key_drop_cnt) + custom_doors
@@ -1961,13 +1990,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
suggested = min(calculated, limit)
key_door_num = min(suggested + builder.key_drop_cnt, max_computation)
combo_size = ncr(len(builder.candidates.small), key_door_num)
while combo_size > 500000 and suggested > 0:
suggested -= 1
combo_size = ncr(len(builder.candidates.small), key_door_num)
suggestion_map[dungeon] = builder.key_doors_num = key_door_num
remaining -= suggested + builder.key_drop_cnt
remaining -= key_door_num + builder.key_drop_cnt
builder.combo_size = combo_size
flex_map[dungeon] = (limit - suggested) if suggested < limit else 0
flex_map[dungeon] = (limit - key_door_num) if key_door_num < limit else 0
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
if total_adjustable:
@@ -2026,7 +2052,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
return used_doors
def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, world, player):
def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, all_custom, world, player):
for pool, door_type_pool in door_type_pools:
ttl = 0
suggestion_map, bd_map = {}, {}
@@ -2043,11 +2069,9 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, worl
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
find_bd_candidates(builder, start_regions_map[dungeon], used_doors, world, player)
if custom_bomb_doors[dungeon]:
builder.candidates.bomb_dash = filter_key_door_pool(builder.candidates.bomb_dash, custom_bomb_doors[dungeon])
if all_custom[dungeon]:
builder.candidates.bomb_dash = filter_key_door_pool(builder.candidates.bomb_dash, all_custom[dungeon])
remaining_bomb -= len(custom_bomb_doors[dungeon])
if custom_dash_doors[dungeon]:
builder.candidates.bomb_dash = filter_key_door_pool(builder.candidates.bomb_dash, custom_dash_doors[dungeon])
remaining_dash -= len(custom_dash_doors[dungeon])
ttl += len(builder.candidates.bomb_dash)
if ttl == 0:

View File

@@ -66,7 +66,7 @@ def create_doors(world, player):
create_door(player, 'Hyrule Castle Back Hall Down Stairs', Sprl).dir(Dn, 0x01, 0, HTL).ss(A, 0x2a, 0x00),
create_door(player, 'Hyrule Castle Throne Room Tapestry', Lgcl),
create_door(player, 'Hyrule Castle Tapestry Backwards', Lgcl),
create_door(player, 'Hyrule Castle Throne Room N', Nrml).dir(No, 0x51, Mid, High).pos(1),
create_door(player, 'Hyrule Castle Throne Room N', Nrml).dir(No, 0x51, Mid, High).pos(0),
create_door(player, 'Hyrule Castle Throne Room South Stairs', StrS).dir(So, 0x51, Mid, Low),
# hyrule dungeon level

View File

@@ -1824,6 +1824,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
for name, builder in dungeon_map.items():
if builder.c_switch_present and builder.c_switch_required and not builder.c_locked:
invalid_builders.append(builder)
random.shuffle(invalid_builders)
while len(invalid_builders) > 0:
valid_builders = []
for builder in invalid_builders:
@@ -1849,6 +1850,7 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
if eq.c_switch:
reachable_crystals[hook_from_door(eq.door)] = True
valid_ent_sectors = []
random.shuffle(entrance_sectors)
for entrance_sector in entrance_sectors:
other_sectors = [x for x in builder.sectors if x != entrance_sector]
reachable, access = is_c_switch_reachable(entrance_sector, reachable_crystals, other_sectors)
@@ -1866,7 +1868,12 @@ def ensure_crystal_switches_reachable(dungeon_map, crystal_switches, polarized_s
while not valid:
if len(candidates) <= 0:
raise GenerationException(f'need to provide more sophisticated crystal connection for {entrance_sector}')
sector, which_list = random.choice(list(candidates.items()))
# prioritize candidates
if any(x == 'Crystals' for x in candidates.values()):
cand_list = [x for x in candidates.items() if x[1] == 'Crystals']
else:
cand_list = list(candidates.items())
sector, which_list = random.choice(cand_list)
del candidates[sector]
valid = global_pole.is_valid_choice(dungeon_map, builder, [sector])
if which_list == 'Polarized':

View File

@@ -36,7 +36,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
from source.tools.BPS import create_bps_from_data
from source.classes.CustomSettings import CustomSettings
__version__ = '1.2.0.8-u'
__version__ = '1.2.0.9-u'
from source.classes.BabelFish import BabelFish

View File

@@ -108,6 +108,19 @@ These are now independent of retro mode and have three options: None, Random, an
* Bonk Fairy (Dark)
# Bug Fixes and Notes
* 1.2.0.9-u
* Disallowed standard exits (due to ER) are now graphically half blocked instead of missing
* Graphical issues with Sanctuary and Swamp Hub lobbies are fixed
* Fixes an issue surrounding door state and decoupled doors leading to blocked doors
* Customizer improvements:
* Better logic around customized lobbies
* Better logic around customized door types
* Fix to key doors that was causing extra key doors
* Generation improvement around crystal switches
* Fix bug in dungeon_only that wasn't using pot key locations (known issue still exists in pottery modes)
* Fixes for multiworld:
* Fixes an issue when keys are found in own dungeon for another player when using the bizhawk plugin.
* Fixes an issue with absorbables for another player also being received by the player picking it up.
* 1.2.0.8-u
* New Features: trap_door_mode and key_logic_algorithm
* Change S&Q in door shuffle + standard during escape to spawn as Uncle

4
Rom.py
View File

@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '1694ba41bf6ab7086f05e914e8d08433'
RANDOMIZERBASEHASH = 'e9a22882bb59523c19845274d76b3761'
class JsonRom(object):
@@ -573,7 +573,7 @@ class Sprite(object):
def handle_native_dungeon(location, itemid):
# Keys in their native dungeon should use the original item code for keys
if location.parent_region.dungeon:
if location.parent_region.dungeon and location.player == location.item.player:
if location.parent_region.dungeon.name == location.item.dungeon:
if location.item.bigkey:
return 0x32

View File

@@ -244,7 +244,9 @@ def create_rooms(world, player):
# Room(player, 0xff, 0x52c9a).door(Position.InteriorW, DoorKind.Bombable).door(Position.InteriorE, DoorKind.Bombable).door(Position.SouthE, DoorKind.CaveEntrance),
]
# fix some wonky things
world.get_room(0x51, player).change(1, DoorKind.Normal) # fix the dungeon changer
# should I put back the dungeon changer for certain logic - like no logic? maybe in basic
if world.doorShuffle[player] != 'vanilla':
world.get_room(0x51, player).delete(1) # remove the dungeon changer
world.get_room(0x60, player).swap(2, 4) # puts the exit at pos 2 - enables pos 3
world.get_room(0x61, player).swap(1, 6) # puts the WN door at pos 1 - enables it
world.get_room(0x61, player).swap(5, 6) # puts the Incognito Entrance at the end, so it can be deleted

View File

@@ -608,6 +608,7 @@ OWEdgeTransition:
}
OWSpecialExit:
{
LDA.l OWMode : ORA.l OWMode+1 : BEQ .vanilla
PHY
LDY.b #$00
LDA.w $0418 : LSR : BNE +
@@ -618,6 +619,7 @@ OWSpecialExit:
++
JSR OWWorldTerrainUpdate
PLY
.vanilla
LDA.l $7EFD40,X ; what we wrote over
RTL
}

Binary file not shown.

View File

@@ -163,7 +163,7 @@ def create_item_pool_config(world):
dungeon_set = (mode_grouping['Big Chests'] + mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
mode_grouping['Big Key Drops'])
mode_grouping['Pot Keys'] + mode_grouping['Big Key Drops'])
for player in range(1, world.players + 1):
config.item_pool[player] = determine_major_items(world, player)
config.location_groups[0].locations = set(dungeon_set)

View File

@@ -0,0 +1,23 @@
meta:
players: 2
settings:
1:
pottery: cavekeys
keysanity: True
2:
keysanity: True
placements:
1:
Sanctuary: Small Key (Escape)#2
'Links House Pot #3': Rupees (20)#2
start_inventory:
1:
- Pegasus Boots
- Ocarina (Activated)
- Magic Mirror
- Boss Heart Container
- Boss Heart Container
- Boss Heart Container
- Boss Heart Container
- Red Mail
- Golden Sword

View File

@@ -1,28 +1,46 @@
meta:
players: 1
race: true
algorithm: dungeon_only
settings:
1:
shopsanity: true
goal: dungeons
pseudoboots: true
goal: crystals
crystals_gt: random
keysanity: true
door_shuffle: crossed
decoupledoors: true
intensity: 3
door_type_mode: big
pottery: keys
dropshuffle: true
door_type_mode: all
experimental: true
dungeon_counters: 'on'
hints: true
msu_resume: true
collection_rate: true
quickswap: true
start_inventory:
compassshuffle: true
mapshuffle: true
keydropshuffle: true
doors:
1:
- Pegasus Boots
- Ocarina (Activated)
- Magic Mirror
- Boss Heart Container
- Blue Mail
lobbies:
Hyrule Castle South: Thieves Lobby S
doors:
Thieves Lobby NE Edge:
dest: Hyrule Castle Throne Room South Stairs
one-way: True
Hyrule Castle Throne Room South Stairs:
dest: Desert Beamos Hall NE
one-way: True
Desert Beamos Hall NE:
dest: Ice Spike Cross SE
type: Key Door
one-way: True
Ice Spike Cross SE:
dest: Thieves Lobby NE Edge
one-way: True
Hyrule Castle Throne Room N:
dest: Ice Firebar Down Ladder
type: Key Door
Swamp Lobby S:
dest: Thieves Lobby N Edge
type: Bomb Door
Thieves Lobby E: Thieves Compass Room W
Thieves Compass Room NW Edge: Ice Tall Hint SE
Thieves Compass Room N Edge: PoD Bow Statue Down Ladder