Merged in DR v1.2.0.9
This commit is contained in:
@@ -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:
|
||||
|
||||
2
Doors.py
2
Doors.py
@@ -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
|
||||
|
||||
@@ -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':
|
||||
|
||||
2
Main.py
2
Main.py
@@ -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
|
||||
|
||||
|
||||
@@ -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
4
Rom.py
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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)
|
||||
|
||||
23
test/customizer/multi_test.yaml
Normal file
23
test/customizer/multi_test.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user