Trap door refinement with "optional" value versus "vanilla"

Slight balance of chaos mode
Warping Pool trap no longer shuffled
This commit is contained in:
aerinon
2023-02-17 16:55:35 -07:00
parent d7c15ae22c
commit 9a71e56546
13 changed files with 123 additions and 53 deletions

View File

@@ -146,7 +146,7 @@ class World(object):
set_player_attr('pot_pool', {})
set_player_attr('decoupledoors', False)
set_player_attr('door_type_mode', 'original')
set_player_attr('trap_door_mode', 'vanilla')
set_player_attr('trap_door_mode', 'optional')
set_player_attr('key_logic_algorithm', 'default')
set_player_attr('shopsanity', False)
@@ -1905,6 +1905,9 @@ class Door(object):
return world.get_room(self.roomIndex, self.player).kind(self)
return None
def dungeon_name(self):
return self.entrance.parent_region.dungeon.name if self.entrance.parent_region.dungeon else 'Cave'
def __eq__(self, other):
return isinstance(other, self.__class__) and self.name == other.name
@@ -2962,7 +2965,7 @@ bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
# additions
# byte 12: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo)
overworld_map_mode = {'default': 0, 'compass': 1, 'map': 2}
trap_door_mode = {'vanilla': 0, 'boss': 1, 'oneway': 2}
trap_door_mode = {'vanilla': 0, 'optional': 1, 'boss': 2, 'oneway': 3}
key_logic_algo = {'default': 0, 'partial': 1, 'strict': 2}
# sfx_shuffle and other adjust items does not affect settings code

2
CLI.py
View File

@@ -215,7 +215,7 @@ def parse_settings():
'door_shuffle': 'vanilla',
'intensity': 2,
'door_type_mode': 'original',
'trap_door_mode': 'vanilla',
'trap_door_mode': 'optional',
'key_logic_algorithm': 'default',
'decoupledoors': False,
'experimental': False,

View File

@@ -1760,12 +1760,12 @@ class DoorTypePool:
self.tricky += counts[6]
def chaos_shuffle(self, counts):
weights = [1, 2, 4, 3, 2, 1]
weights = [1, 2, 4, 3, 2]
return [random.choices(self.get_choices(counts[i]), weights=weights)[0] for i, c in enumerate(counts)]
@staticmethod
def get_choices(number):
return [max(number+i, 0) for i in range(-1, 5)]
return [max(number+i, 0) for i in range(-1, 4)]
class BuilderDoorCandidates:
@@ -1801,14 +1801,17 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player)
ttl = 0
suggestion_map, trap_map, flex_map = {}, {}, {}
remaining = door_type_pool.traps
if player in world.custom_door_types:
if player in world.custom_door_types and 'Trap Door' in world.custom_door_types[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]
find_trappable_candidates(builder, world, player) # todo:
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])
remaining -= len(custom_trap_doors[dungeon])
@@ -1852,6 +1855,10 @@ def shuffle_trap_doors(door_type_pools, paths, start_regions_map, world, player)
# time to re-assign
else:
trap_map = {dungeon: [] for dungeon in pool}
for dungeon in pool:
builder = world.dungeon_layouts[player][dungeon]
if 'Mire Warping Pool' in builder.master_sector.region_set():
trap_map[dungeon].append(world.get_door('Mire Warping Pool ES', player))
reassign_trap_doors(trap_map, world, player)
for name, traps in trap_map.items():
used_doors.update(traps)
@@ -1863,7 +1870,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
ttl = 0
suggestion_map, bk_map, flex_map = {}, {}, {}
remaining = door_type_pool.bigs
if player in world.custom_door_types:
if player in world.custom_door_types and 'Big Key Door' in world.custom_door_types[player]:
custom_bk_doors = world.custom_door_types[player]['Big Key Door']
else:
custom_bk_doors = defaultdict(list)
@@ -1925,7 +1932,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
suggestion_map, small_map, flex_map = {}, {}, {}
remaining = door_type_pool.smalls
total_keys = remaining
if player in world.custom_door_types:
if player in world.custom_door_types and 'Key Door' in world.custom_door_types[player]:
custom_key_doors = world.custom_door_types[player]['Key Door']
else:
custom_key_doors = defaultdict(list)
@@ -2025,7 +2032,7 @@ def shuffle_bomb_dash_doors(door_type_pools, used_doors, start_regions_map, worl
remaining_bomb = door_type_pool.bombable
remaining_dash = door_type_pool.dashable
if player in world.custom_door_types:
if player in world.custom_door_types and 'Bomb Door' in world.custom_door_types[player]:
custom_bomb_doors = world.custom_door_types[player]['Bomb Door']
custom_dash_doors = world.custom_door_types[player]['Dash Door']
else:
@@ -2164,7 +2171,7 @@ def find_trappable_candidates(builder, world, player):
def find_valid_trap_combination(builder, suggested, start_regions, paths, world, player, drop=True):
trap_door_pool = builder.candidates.trap
trap_doors_needed = suggested
if player in world.custom_door_types:
if player in world.custom_door_types and 'Trap Door' in world.custom_door_types[player]:
custom_trap_doors = world.custom_door_types[player]['Trap Door'][builder.name]
else:
custom_trap_doors = []
@@ -2319,20 +2326,16 @@ def reassign_trap_doors(trap_map, world, player):
d.blocked = False
for d in traps:
change_door_to_trap(d, world, player)
world.spoiler.set_door_type(d.name, 'Trap Door', player)
logger.debug('Trap Door: %s', d.name)
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Trap Door', player)
logger.debug(f'Trap Door: {d.name} ({d.dungeon_name()})')
def exclude_boss_traps(d):
return ' Boss ' not in d.name and ' Agahnim ' not in d.name and d.name not in ['Skull Spike Corner SW',
'Mire Warping Pool ES']
def exclude_logic_traps(d):
return d.name != 'Mire Warping Pool ES'
return ' Boss ' not in d.name and ' Agahnim ' not in d.name and d.name not in ['Skull Spike Corner SW']
def find_current_trap_doors(builder, world, player):
checker = exclude_boss_traps if world.trap_door_mode[player] == 'vanilla' else exclude_logic_traps
checker = exclude_boss_traps if world.trap_door_mode[player] in ['vanilla', 'optional'] else (lambda x: True)
current_doors = []
for region in builder.master_sector.regions:
for ext in region.exits:
@@ -2452,7 +2455,7 @@ def find_big_key_door_candidates(region, checked, used, world, player):
def find_valid_bk_combination(builder, suggested, start_regions, world, player, drop=True):
bk_door_pool = builder.candidates.big
bk_doors_needed = suggested
if player in world.custom_door_types:
if player in world.custom_door_types and 'Big Key Door' in world.custom_door_types[player]:
custom_bk_doors = world.custom_door_types[player]['Big Key Door'][builder.name]
else:
custom_bk_doors = []
@@ -2527,8 +2530,8 @@ def reassign_big_key_doors(bk_map, world, player):
world.paired_doors[player].append(PairedDoor(d1.name, d2.name))
change_door_to_big_key(d1, world, player)
change_door_to_big_key(d2, world, player)
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Big Key Door', player)
logger.debug(f'Big Key Door: {d1.name} <-> {d2.name}')
world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', 'Big Key Door', player)
logger.debug(f'Big Key Door: {d1.name} <-> {d2.name} ({d1.dungeon_name()})')
else:
d = obj
if d.type is DoorType.Interior:
@@ -2545,8 +2548,8 @@ def reassign_big_key_doors(bk_map, world, player):
if stateful_door(d.dest, dest_room.kind(d.dest)):
change_door_to_big_key(d.dest, world, player)
add_pair(d, d.dest, world, player)
world.spoiler.set_door_type(d.name, 'Big Key Door', player)
logger.debug(f'Big Key Door: {d.name}')
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Big Key Door', player)
logger.debug(f'Big Key Door: {d.name} ({d.dungeon_name()})')
def change_door_to_big_key(d, world, player):
@@ -2596,7 +2599,7 @@ def find_valid_combination(builder, target, start_regions, world, player, drop_k
logger = logging.getLogger('')
key_door_pool = list(builder.candidates.small)
key_doors_needed = target
if player in world.custom_door_types:
if player in world.custom_door_types and 'Key Door' in world.custom_door_types[player]:
custom_key_doors = world.custom_door_types[player]['Key Door'][builder.name]
else:
custom_key_doors = []
@@ -2724,7 +2727,7 @@ def find_valid_bd_combination(builder, suggested, world, player):
bd_door_pool = builder.candidates.bomb_dash
bomb_doors_needed, dash_doors_needed = suggested
ttl_needed = bomb_doors_needed + dash_doors_needed
if player in world.custom_door_types:
if player in world.custom_door_types and 'Bomb Door' in world.custom_door_types[player]:
custom_bomb_doors = world.custom_door_types[player]['Bomb Door'][builder.name]
custom_dash_doors = world.custom_door_types[player]['Dash Door'][builder.name]
else:
@@ -2800,7 +2803,7 @@ def do_bombable_dashable(proposal, kind, world, player):
change_door_to_kind(d1, kind, world, player)
change_door_to_kind(d2, kind, world, player)
spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, spoiler_type, player)
world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', spoiler_type, player)
else:
d = obj
if d.type is DoorType.Interior:
@@ -2814,7 +2817,7 @@ def do_bombable_dashable(proposal, kind, world, player):
change_door_to_kind(d.dest, kind, world, player)
add_pair(d, d.dest, world, player)
spoiler_type = 'Bomb Door' if kind == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(d.name, spoiler_type, player)
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', spoiler_type, player)
def find_current_bd_doors(builder, world):
@@ -3017,8 +3020,8 @@ def reassign_key_doors(small_map, world, player):
world.paired_doors[player].append(PairedDoor(d1.name, d2.name))
change_door_to_small_key(d1, world, player)
change_door_to_small_key(d2, world, player)
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Key Door', player)
logger.debug('Key Door: %s', d1.name+' <-> '+d2.name)
world.spoiler.set_door_type(f'{d1.name} <-> {d2.name} ({d1.dungeon_name()})', 'Key Door', player)
logger.debug(f'Key Door: {d1.name} <-> {d2.name} ({d1.dungeon_name()})')
else:
d = obj
if d.type is DoorType.Interior:
@@ -3034,8 +3037,8 @@ def reassign_key_doors(small_map, world, player):
if stateful_door(d.dest, dest_room.kind(d.dest)):
change_door_to_small_key(d.dest, world, player)
add_pair(d, d.dest, world, player)
world.spoiler.set_door_type(d.name, 'Key Door', player)
logger.debug('Key Door: %s', d.name)
world.spoiler.set_door_type(f'{d.name} ({d.dungeon_name()})', 'Key Door', player)
logger.debug(f'Key Door: {d.name} ({d.dungeon_name()})')
def change_door_to_small_key(d, world, player):
@@ -3225,7 +3228,7 @@ def change_pair_type(door, new_type, world, player):
room_b.change(door.dest.doorListPos, new_type)
add_pair(door, door.dest, world, player)
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
world.spoiler.set_door_type(door.name + ' <-> ' + door.dest.name, spoiler_type, player)
world.spoiler.set_door_type(f'{door.name} <-> {door.dest.name} ({door.dungeon_name()})', spoiler_type, player)
def remove_pair_type_if_present(door, world, player):
@@ -4578,7 +4581,7 @@ door_type_counts = {
'Agahnims Tower': (4, 0, 1, 0, 0, 1, 0),
'Swamp Palace': (6, 0, 0, 2, 0, 0, 0),
'Palace of Darkness': (6, 1, 1, 3, 2, 0, 0),
'Misery Mire': (6, 3, 4, 2, 0, 0, 0),
'Misery Mire': (6, 3, 5, 2, 0, 0, 0),
'Skull Woods': (5, 0, 1, 2, 0, 1, 0),
'Ice Palace': (6, 1, 3, 0, 0, 0, 0),
'Tower of Hera': (1, 1, 0, 0, 0, 0, 0),

View File

@@ -3565,7 +3565,7 @@ def check_for_valid_layout(builder, sector_list, builder_info):
builder.exception_list = list(sector_list)
return True, {}, package
except (GenerationException, NeutralizingException, OtherGenException) as e:
logging.getLogger('').info(f'Bailing on this layout for', e)
logging.getLogger('').info(f'Bailing on this layout for {builder.name}', exc_info=1)
builder.split_dungeon_map = None
builder.valid_proposal = None
if temp_builder.name == 'Hyrule Castle' and temp_builder.throne_door:

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.7-u'
__version__ = '1.2.0.8-u'
from source.classes.BabelFish import BabelFish

View File

@@ -139,20 +139,25 @@ Four options here, and all of them only take effect if Dungeon Door Shuffle is n
* Small Key Doors, Bomb Doors, Dash Doors: This is what was normally shuffled previously
* Adds Big Keys Doors: Big key doors are now shuffled in addition to those above, and Big Key doors are enabled to be on in both vertical directions thanks to a graphic that ended up on the cutting room floor. This does change
* Adds Trap Doors: All trap doors that are permanently shut in vanilla are shuffled.
* Adds Trap Doors: All trap doors that are permanently shut in vanilla are shuffled, excluding those by bosses.
* Increases all Door Types: This is a chaos mode where each door type per dungeon is randomized between 1 less and 4 more.
CLI: `--door_type_mode [original|big|all|chaos]`
### Trap Door Removal
Three options here for making dungeon traversal nicer. Only applies if door shuffle is not vanilla.
Options here for making dungeon traversal nicer. Only applies if door shuffle is not vanilla.
* Normal: This does not remove any trap doors. Note that boss trap doors are never shuffled in this mode.
* Remove Boss Traps: Boss traps are removed this includes the one near Mothula.
* Remove All Annoying Traps: This removes all trap doors that are annoying, including boss traps. Note, that the trap door near the mire cutscene chest is left alone because it enforces the use of fire to get to the chest.
* No Removal: This does not remove any trap doors.
* Removed If Blocking Path: Dungeon generation is relaxed to allow annoying trap doors to be removed if necessary. Note that boss trap doors are never shuffled in this mode.
* Remove Boss Traps: Boss traps are removed, this includes the one near Mothula.
* Remove All Annoying Traps: This removes all trap doors that are annoying, including boss traps.
CLI: `--trap_door_mode [vanilla|boss|oneway]`
If trap doors are shuffled the first two option behave the same. The last option overrides the shuffle because there is nothing left to shuffle. Boss traps are never shuffled.
In all cases, that the trap door near the mire cutscene chest (Mire Warping Pool ES) is left alone because it enforces the use of fire to get to the chest.
CLI: `--trap_door_mode [vanilla|optional|boss|oneway]`
### Key Logic Algorithm

View File

@@ -109,6 +109,12 @@ These are now independent of retro mode and have three options: None, Random, an
# Bug Fixes and Notes
* 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
* Fix for vanilla doors + certain ER modes
* Fix for unintentional decoupled door in standard
* Fix a problem with BK doors being one-sided
* Change to how wilds keys are placed in standard, better randomization
* Removed a Triforce text
* Fix for Desert Tiles 1 key door
* 1.2.0.7-u

View File

@@ -7,12 +7,30 @@ algorithm:
district: 1
door_shuffle:
vanilla: 1
basic: 2
basic: 1
partitioned: 1
crossed: 3 # crossed yield more errors so is preferred
intensity:
1: 1
2: 1
3: 2 # intensity 3 usually yield more errors
door_type_mode:
original: 2
big: 2
all: 1
chaos: 1
trap_door_mode:
vanilla: 3 # more errors
optional: 1
boss: 1
oneway: 1
key_logic_algorithm:
default: 1
partial: 0
strict: 0
decoupledoors:
off: 9 # more strict
on: 1
dropshuffle:
on: 1
off: 1

View File

@@ -196,6 +196,7 @@
"trap_door_mode": {
"choices": [
"vanilla",
"optional",
"boss",
"oneway"
]

View File

@@ -242,8 +242,9 @@
"trap_door_mode" : [
"Trap Door Removal (default: %(default)s)",
"vanilla: No trap door removal",
"boss: Remove boss traps",
"oneway: Remove annoying trap doors"
"optional: Trap doors removed if blocking",
"boss: Also remove boss traps",
"oneway: Remove all annoying trap doors"
],
"key_logic_algorithm": [
"Key Logic Algorithm (default: %(default)s)",

View File

@@ -93,8 +93,9 @@
"randomizer.dungeon.trap_door_mode": "Trap Door Removal",
"randomizer.dungeon.trap_door_mode.vanilla": "No Removal",
"randomizer.dungeon.trap_door_mode.boss": "Remove Boss Traps",
"randomizer.dungeon.trap_door_mode.oneway": "Remove Annoying Traps",
"randomizer.dungeon.trap_door_mode.optional": "Removed If Blocking Path",
"randomizer.dungeon.trap_door_mode.boss": "Also Remove Boss Traps",
"randomizer.dungeon.trap_door_mode.oneway": "Remove All Annoying Traps",
"randomizer.dungeon.key_logic_algorithm": "Key Logic Algorithm",
"randomizer.dungeon.key_logic_algorithm.default": "Default",

View File

@@ -46,12 +46,16 @@
},
"trap_door_mode": {
"type": "selectbox",
"default": "vanilla",
"default": "optional",
"options": [
"vanilla",
"optional",
"boss",
"oneway"
]
],
"config": {
"width": 30
}
},
"key_logic_algorithm": {
"type": "selectbox",

View File

@@ -631,6 +631,7 @@ class ExplorationState(object):
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors, flag)
# same as above but traps are ignored, and flag is not used
def add_all_doors_check_proposed_2(self, region, proposed_map, valid_doors, world, player):
for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors:
@@ -651,6 +652,27 @@ class ExplorationState(object):
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors)
# same as above but traps are checked for
def add_all_doors_check_proposed_3(self, region, proposed_map, valid_doors, world, player):
for door in get_doors(world, region, player):
if door in proposed_map and door.name in valid_doors:
self.visited_doors.add(door)
if self.can_traverse(door):
if door.controller is not None:
door = door.controller
if door.dest is None and door not in proposed_map.keys() and door.name in valid_doors:
if not self.in_door_list_ic(door, self.unattached_doors):
self.append_door_to_list(door, self.unattached_doors)
else:
other = self.find_door_in_list(door, self.unattached_doors)
if self.crystal != other.crystal:
other.crystal = CrystalBarrier.Either
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door,
self.event_doors):
self.append_door_to_list(door, self.event_doors)
elif not self.in_door_list(door, self.avail_doors):
self.append_door_to_list(door, self.avail_doors)
def add_all_doors_check_proposed_traps(self, region, proposed_traps, world, player):
for door in get_doors(world, region, player):
if self.can_traverse_ignore_traps(door) and door not in proposed_traps:
@@ -837,6 +859,9 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi
local_state = state.copy()
for region in search_regions:
local_state.visit_region(region)
if world.trap_door_mode[player] == 'vanilla':
local_state.add_all_doors_check_proposed_3(region, proposed_map, valid_doors, world, player)
else:
local_state.add_all_doors_check_proposed_2(region, proposed_map, valid_doors, world, player)
while len(local_state.avail_doors) > 0:
explorable_door = local_state.next_avail_door()
@@ -848,6 +873,9 @@ def extend_reachable_state_lenient(search_regions, state, proposed_map, all_regi
if (valid_region_to_explore_in_regions(connect_region, all_regions, world, player)
and not local_state.visited(connect_region)):
local_state.visit_region(connect_region)
if world.trap_door_mode[player] == 'vanilla':
local_state.add_all_doors_check_proposed_3(connect_region, proposed_map, valid_doors, world, player)
else:
local_state.add_all_doors_check_proposed_2(connect_region, proposed_map, valid_doors, world, player)
return local_state