extra_keys setting for crossed door rando

This commit is contained in:
2026-05-03 17:05:33 -05:00
parent 3cb1e9d77b
commit d64a58f636
8 changed files with 29 additions and 7 deletions

View File

@@ -202,6 +202,7 @@ class World(object):
set_player_attr('damage_challenge', 'normal') set_player_attr('damage_challenge', 'normal')
set_player_attr('shuffle_damage_table', 'vanilla') set_player_attr('shuffle_damage_table', 'vanilla')
set_player_attr('crystal_book', False) set_player_attr('crystal_book', False)
set_player_attr('extra_keys', 0)
set_player_attr('collection_rate', False) set_player_attr('collection_rate', False)
set_player_attr('colorizepots', True) set_player_attr('colorizepots', True)
set_player_attr('pot_pool', {}) set_player_attr('pot_pool', {})
@@ -1978,6 +1979,7 @@ class Dungeon(object):
self.prize = None self.prize = None
self.big_key = big_key self.big_key = big_key
self.small_keys = small_keys self.small_keys = small_keys
self.extra_small_keys = 0
self.dungeon_items = dungeon_items self.dungeon_items = dungeon_items
self.bosses = dict() self.bosses = dict()
self.player = player self.player = player
@@ -3174,6 +3176,7 @@ class Spoiler(object):
'damage_challenge': self.world.damage_challenge, 'damage_challenge': self.world.damage_challenge,
'shuffle_damage_table': self.world.shuffle_damage_table, 'shuffle_damage_table': self.world.shuffle_damage_table,
'crystal_book': self.world.crystal_book, 'crystal_book': self.world.crystal_book,
'extra_keys': self.world.extra_keys,
'triforcegoal': self.world.treasure_hunt_count, 'triforcegoal': self.world.treasure_hunt_count,
'triforcepool': self.world.treasure_hunt_total, 'triforcepool': self.world.treasure_hunt_total,
'race': self.world.settings.world_rep['meta']['race'], 'race': self.world.settings.world_rep['meta']['race'],
@@ -3453,6 +3456,7 @@ class Spoiler(object):
outfile.write('Damage Challenge:'.ljust(line_width) + '%s\n' % self.metadata['damage_challenge'][player]) outfile.write('Damage Challenge:'.ljust(line_width) + '%s\n' % self.metadata['damage_challenge'][player])
outfile.write('Damage Table Randomization:'.ljust(line_width) + '%s\n' % self.metadata['shuffle_damage_table'][player]) outfile.write('Damage Table Randomization:'.ljust(line_width) + '%s\n' % self.metadata['shuffle_damage_table'][player])
outfile.write('Crystal Book:'.ljust(line_width) + '%s\n' % yn(self.metadata['crystal_book'][player])) outfile.write('Crystal Book:'.ljust(line_width) + '%s\n' % yn(self.metadata['crystal_book'][player]))
outfile.write('Extra Keys:'.ljust(line_width) + '%d%%\n' % self.metadata['extra_keys'][player])
outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player])) outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race'])) outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race']))

5
CLI.py
View File

@@ -163,8 +163,8 @@ def parse_cli(argv, no_defaults=False):
'shuffletavern', 'skullwoods', 'linked_drops', 'shuffletavern', 'skullwoods', 'linked_drops',
'pseudoboots', 'mirrorscroll', 'dark_rooms', 'pseudoboots', 'mirrorscroll', 'dark_rooms',
'damage_challenge', 'shuffle_damage_table', 'damage_challenge', 'shuffle_damage_table',
'crystal_book', 'retro', 'accessibility', 'hints', 'crystal_book', 'extra_keys', 'retro', 'accessibility',
'beemizer', 'experimental', 'dungeon_counters', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'shufflebosses', 'shuffleenemies', 'enemy_health',
'enemy_damage', 'shufflepots', 'ow_palettes', 'enemy_damage', 'shufflepots', 'ow_palettes',
'uw_palettes', 'sprite', 'triforce_gfx', 'disablemusic', 'uw_palettes', 'sprite', 'triforce_gfx', 'disablemusic',
@@ -254,6 +254,7 @@ def parse_settings():
"damage_challenge": "normal", "damage_challenge": "normal",
"shuffle_damage_table": "vanilla", "shuffle_damage_table": "vanilla",
"crystal_book": False, "crystal_book": False,
"extra_keys": 0,
"shuffleenemies": "none", "shuffleenemies": "none",
"shufflebosses": "none", "shufflebosses": "none",

View File

@@ -3,6 +3,7 @@ import time
from collections import defaultdict, deque from collections import defaultdict, deque
from enum import Flag, unique from enum import Flag, unique
from itertools import chain from itertools import chain
from math import ceil
from typing import DefaultDict, Dict, List from typing import DefaultDict, Dict, List
import RaceRandom as random import RaceRandom as random
@@ -1599,7 +1600,11 @@ def assign_cross_keys(dungeon_builders, world, player):
if actual_chest_keys == 0: if actual_chest_keys == 0:
dungeon.small_keys = [] dungeon.small_keys = []
else: else:
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys extra_keys = ceil(actual_chest_keys * world.extra_keys[player] / 100)
logger.debug(f'Adding {extra_keys} extra small keys to {name}')
dungeon.extra_small_keys = extra_keys
created_keys = actual_chest_keys + extra_keys
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * created_keys
logger.info(f'{world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed")}: {time.process_time()-start}') logger.info(f'{world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed")}: {time.process_time()-start}')
@@ -2171,7 +2176,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_
if actual_chest_keys == 0: if actual_chest_keys == 0:
dungeon.small_keys = [] dungeon.small_keys = []
else: else:
dungeon.small_keys = [ItemFactory(dungeon_keys[dungeon_name], player) for _ in range(actual_chest_keys)] extra_keys = ceil(actual_chest_keys * world.extra_keys[player] / 100)
dungeon.extra_small_keys = extra_keys
created_keys = actual_chest_keys + extra_keys
dungeon.small_keys = [ItemFactory(dungeon_keys[dungeon_name], player) for _ in range(created_keys)]
for name, small_list in small_map.items(): for name, small_list in small_map.items():
used_doors.update(flatten_pair_list(small_list)) used_doors.update(flatten_pair_list(small_list))

View File

@@ -564,6 +564,7 @@ def init_world(args, fish):
world.damage_challenge = args.damage_challenge.copy() world.damage_challenge = args.damage_challenge.copy()
world.shuffle_damage_table = args.shuffle_damage_table.copy() world.shuffle_damage_table = args.shuffle_damage_table.copy()
world.crystal_book = args.crystal_book.copy() world.crystal_book = args.crystal_book.copy()
world.extra_keys = {player: int(args.extra_keys[player]) for player in range(1, world.players + 1)}
world.overworld_map = args.overworld_map.copy() world.overworld_map = args.overworld_map.copy()
world.take_any = args.take_any.copy() world.take_any = args.take_any.copy()
world.restrict_boss_items = args.restrict_boss_items.copy() world.restrict_boss_items = args.restrict_boss_items.copy()
@@ -868,6 +869,7 @@ def copy_world(world):
ret.damage_challenge = world.damage_challenge.copy() ret.damage_challenge = world.damage_challenge.copy()
ret.shuffle_damage_table = world.shuffle_damage_table.copy() ret.shuffle_damage_table = world.shuffle_damage_table.copy()
ret.crystal_book = world.crystal_book.copy() ret.crystal_book = world.crystal_book.copy()
ret.extra_keys = world.extra_keys.copy()
ret.overworld_map = world.overworld_map.copy() ret.overworld_map = world.overworld_map.copy()
ret.take_any = world.take_any.copy() ret.take_any = world.take_any.copy()
ret.boss_shuffle = world.boss_shuffle.copy() ret.boss_shuffle = world.boss_shuffle.copy()
@@ -1100,6 +1102,7 @@ def copy_world_premature(world, player, create_flute_exits=True):
ret.damage_challenge = world.damage_challenge.copy() ret.damage_challenge = world.damage_challenge.copy()
ret.shuffle_damage_table = world.shuffle_damage_table.copy() ret.shuffle_damage_table = world.shuffle_damage_table.copy()
ret.crystal_book = world.crystal_book.copy() ret.crystal_book = world.crystal_book.copy()
ret.extra_keys = world.extra_keys.copy()
ret.overworld_map = world.overworld_map.copy() ret.overworld_map = world.overworld_map.copy()
ret.take_any = world.take_any.copy() ret.take_any = world.take_any.copy()
ret.boss_shuffle = world.boss_shuffle.copy() ret.boss_shuffle = world.boss_shuffle.copy()

8
Rom.py
View File

@@ -85,7 +85,7 @@ from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc
from Versions import DRVersion, GKVersion, ORVersion from Versions import DRVersion, GKVersion, ORVersion
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '8945e9fdcefc02eb3ff3ad2a8892a180' RANDOMIZERBASEHASH = '8a6d769751e2676e8d9da48871cb7634'
class JsonRom(object): class JsonRom(object):
@@ -818,11 +818,13 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
rom.write_byte(0x138002, 2) rom.write_byte(0x138002, 2)
for name, layout in world.key_layout[player].items(): for name, layout in world.key_layout[player].items():
offset = compass_data[name][4]//2 offset = compass_data[name][4]//2
dungeon = world.get_dungeon(name, player)
if world.keyshuffle[player] == 'universal': if world.keyshuffle[player] == 'universal':
rom.write_byte(0x187010+offset, layout.max_chests + layout.max_drops) rom.write_byte(0x187010+offset, layout.max_chests + layout.max_drops)
else: else:
rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops + dungeon.extra_small_keys) # not currently used
rom.write_byte(0x187010+offset, layout.max_chests) rom.write_byte(0x187010+offset, layout.max_chests + dungeon.extra_small_keys)
rom.write_byte(0x187000+offset, dungeon.extra_small_keys)
builder = world.dungeon_layouts[player][name] builder = world.dungeon_layouts[player][name]
bk_status = 1 if builder.bk_required else 0 bk_status = 1 if builder.bk_required else 0
bk_status = 2 if builder.bk_provided else bk_status bk_status = 2 if builder.bk_provided else bk_status

Binary file not shown.

View File

@@ -665,6 +665,9 @@
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
}, },
"extra_keys": {
"type": "int"
},
"calc_playthrough": { "calc_playthrough": {
"action": "store_false", "action": "store_false",
"type": "bool" "type": "bool"

View File

@@ -425,6 +425,7 @@
"AlwaysInLogic: Dark rooms are always considered to be in logic, even if the player cannot see" "AlwaysInLogic: Dark rooms are always considered to be in logic, even if the player cannot see"
], ],
"crystal_book": [ " Book can be used indoors to flip the state of colored pegs (default: %(default)s)"], "crystal_book": [ " Book can be used indoors to flip the state of colored pegs (default: %(default)s)"],
"extra_keys": [ " Percentage of extra small keys to create for each dungeon when door shuffle is enabled (default: %(default)s)"],
"bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ], "bombbag": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ],
"any_enemy_logic": [ "any_enemy_logic": [
"How to handle potential traversal between dungeon in Crossed door shuffle", "How to handle potential traversal between dungeon in Crossed door shuffle",