Rom changes (see that commmit message or release notes)

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 an issue when keys are found in own dungeon for another player when using the bizhawk plugin
This commit is contained in:
aerinon
2023-02-24 14:25:04 -07:00
parent 9a71e56546
commit 409f7d50d5
12 changed files with 152 additions and 64 deletions

View File

@@ -128,7 +128,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()]
@@ -142,6 +142,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':
@@ -557,7 +562,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:
@@ -570,6 +577,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
@@ -1718,7 +1743,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():
@@ -1731,7 +1755,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)
@@ -1783,18 +1807,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':
@@ -1805,15 +1835,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:
@@ -1865,7 +1894,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 = {}, {}, {}
@@ -1878,8 +1907,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:
@@ -1925,7 +1954,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
@@ -1943,8 +1972,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
@@ -1960,13 +1989,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:
@@ -2025,7 +2051,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 = {}, {}
@@ -2042,11 +2068,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

@@ -34,7 +34,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

@@ -12,13 +12,15 @@ See https://alttpr.com/ for more details on the normal randomizer.
1. [Dungeon Door Shuffle](#door-shuffle)
2. [Intensity Level](#intensity---intensity-number)
3. [Key Drop Shuffle (Legacy)](#key-drop-shuffle-legacy---keydropshuffle)
4. [Door Type Shuffle](#door-type_shuffle)
5. [Decouple Doors](#decouple-doors)
6. [Pottery](#pottery)
7. [Small Key Shuffle](#small-key-shuffle)
8. [Shuffle Enemy Key Drops](#shuffle-enemy-key-drops)
9. [Experimental Features](#experimental-features)
10. [Crossed Dungeon Specific Settings](#crossed-dungeon-specific-settings)
4. [Door Type Shuffle](#door-type-shuffle)
5. [Trap Door Removal](#trap-door-removal)
6. [Key Logic Algorithm](#key-logic-algorithm)
7. [Decouple Doors](#decouple-doors)
8. [Pottery](#pottery)
9. [Small Key Shuffle](#small-key-shuffle)
10. [Shuffle Enemy Key Drops](#shuffle-enemy-key-drops)
11. [Experimental Features](#experimental-features)
12. [Crossed Dungeon Specific Settings](#crossed-dungeon-specific-settings)
2. [Item Randomization Changes](#item-randomization)
1. [New "Items"](#new-items)
2. [Shopsanity](#shopsanity)
@@ -40,7 +42,7 @@ See https://alttpr.com/ for more details on the normal randomizer.
### Feedback and Bug Reports
Please just DM me on discord for now. I (Aerinon) can be found at the [ALTTP Randomizer discord](https://discordapp.com/invite/alttprandomizer).
You can use the #bug-reports or #door-rando channel at the [ALTTP Randomizer discord](https://discordapp.com/invite/alttprandomizer) to provide feedback or bug reports.
### Installation
@@ -73,15 +75,14 @@ Most of these apply only when the door shuffle is not vanilla.
### Starting Item
You start with a “Mirror Scroll”, a dumbed-down mirror that only works in dungeons, not the overworld and cant erase blocks like the Mirror.
You start with a “Mirror Scroll” (it looks like a map), a dumbed-down mirror that only works in dungeons, not the overworld, and cant erase blocks like the Mirror.
### Navigation
* The Pinball Rooms trap door can be removed in the case where it is required to go through to get to the back of Skull Woods.
* Holes in Mire Torches Top and Mire Torches Bottom fall through to rooms below (you only need fire to get the chest)
* You can Hookshot from the left Mire wooden Bridge to the right one.
* In the PoD Arena, you can bonk with Boots between the two blue crystal barriers against the ladder to reach the Arena Bridge chest and door. (Bomb Jump also possible but not in logic - Boots are required)
* Flooded Rooms in Swamp can be traversed backward and may be required.
* Flooded Rooms in Swamp can be traversed backward and may be required. The flippers are needed to get out of the water.
### Other Logic

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

@@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '6f64fcea052e37b39d6b4bb24ae2f548'
RANDOMIZERBASEHASH = '67279b96a589f09e3ba8393a5bc5f071'
class JsonRom(object):
@@ -572,7 +572,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

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