From d64a58f636601a49f7c2e03c37ebfbfa12a4b6cc Mon Sep 17 00:00:00 2001 From: Kara Alexandra Date: Sun, 3 May 2026 17:05:33 -0500 Subject: [PATCH] extra_keys setting for crossed door rando --- BaseClasses.py | 4 ++++ CLI.py | 5 +++-- DoorShuffle.py | 12 ++++++++++-- Main.py | 3 +++ Rom.py | 8 +++++--- data/base2current.bps | Bin 156429 -> 156474 bytes resources/app/cli/args.json | 3 +++ resources/app/cli/lang/en.json | 1 + 8 files changed, 29 insertions(+), 7 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 55bcd826..034bcd60 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -202,6 +202,7 @@ class World(object): set_player_attr('damage_challenge', 'normal') set_player_attr('shuffle_damage_table', 'vanilla') set_player_attr('crystal_book', False) + set_player_attr('extra_keys', 0) set_player_attr('collection_rate', False) set_player_attr('colorizepots', True) set_player_attr('pot_pool', {}) @@ -1978,6 +1979,7 @@ class Dungeon(object): self.prize = None self.big_key = big_key self.small_keys = small_keys + self.extra_small_keys = 0 self.dungeon_items = dungeon_items self.bosses = dict() self.player = player @@ -3174,6 +3176,7 @@ class Spoiler(object): 'damage_challenge': self.world.damage_challenge, 'shuffle_damage_table': self.world.shuffle_damage_table, 'crystal_book': self.world.crystal_book, + 'extra_keys': self.world.extra_keys, 'triforcegoal': self.world.treasure_hunt_count, 'triforcepool': self.world.treasure_hunt_total, '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 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('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('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race'])) diff --git a/CLI.py b/CLI.py index 8a1bcc70..6b9f416d 100644 --- a/CLI.py +++ b/CLI.py @@ -163,8 +163,8 @@ def parse_cli(argv, no_defaults=False): 'shuffletavern', 'skullwoods', 'linked_drops', 'pseudoboots', 'mirrorscroll', 'dark_rooms', 'damage_challenge', 'shuffle_damage_table', - 'crystal_book', 'retro', 'accessibility', 'hints', - 'beemizer', 'experimental', 'dungeon_counters', + 'crystal_book', 'extra_keys', 'retro', 'accessibility', + 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'triforce_gfx', 'disablemusic', @@ -254,6 +254,7 @@ def parse_settings(): "damage_challenge": "normal", "shuffle_damage_table": "vanilla", "crystal_book": False, + "extra_keys": 0, "shuffleenemies": "none", "shufflebosses": "none", diff --git a/DoorShuffle.py b/DoorShuffle.py index ab9bb32e..c84d2a3c 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -3,6 +3,7 @@ import time from collections import defaultdict, deque from enum import Flag, unique from itertools import chain +from math import ceil from typing import DefaultDict, Dict, List import RaceRandom as random @@ -1599,7 +1600,11 @@ def assign_cross_keys(dungeon_builders, world, player): if actual_chest_keys == 0: dungeon.small_keys = [] 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}') @@ -2171,7 +2176,10 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, all_ if actual_chest_keys == 0: dungeon.small_keys = [] 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(): used_doors.update(flatten_pair_list(small_list)) diff --git a/Main.py b/Main.py index ba3f7016..c7fabc3e 100644 --- a/Main.py +++ b/Main.py @@ -564,6 +564,7 @@ def init_world(args, fish): world.damage_challenge = args.damage_challenge.copy() world.shuffle_damage_table = args.shuffle_damage_table.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.take_any = args.take_any.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.shuffle_damage_table = world.shuffle_damage_table.copy() ret.crystal_book = world.crystal_book.copy() + ret.extra_keys = world.extra_keys.copy() ret.overworld_map = world.overworld_map.copy() ret.take_any = world.take_any.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.shuffle_damage_table = world.shuffle_damage_table.copy() ret.crystal_book = world.crystal_book.copy() + ret.extra_keys = world.extra_keys.copy() ret.overworld_map = world.overworld_map.copy() ret.take_any = world.take_any.copy() ret.boss_shuffle = world.boss_shuffle.copy() diff --git a/Rom.py b/Rom.py index 00542973..3f536a07 100644 --- a/Rom.py +++ b/Rom.py @@ -85,7 +85,7 @@ from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc from Versions import DRVersion, GKVersion, ORVersion JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '8945e9fdcefc02eb3ff3ad2a8892a180' +RANDOMIZERBASEHASH = '8a6d769751e2676e8d9da48871cb7634' 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) for name, layout in world.key_layout[player].items(): offset = compass_data[name][4]//2 + dungeon = world.get_dungeon(name, player) if world.keyshuffle[player] == 'universal': rom.write_byte(0x187010+offset, layout.max_chests + layout.max_drops) else: - rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used - rom.write_byte(0x187010+offset, layout.max_chests) + 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 + dungeon.extra_small_keys) + rom.write_byte(0x187000+offset, dungeon.extra_small_keys) builder = world.dungeon_layouts[player][name] bk_status = 1 if builder.bk_required else 0 bk_status = 2 if builder.bk_provided else bk_status diff --git a/data/base2current.bps b/data/base2current.bps index 02dcd32fb7d01fbec2d8deb41e1a833c079ca6bf..a43acf16a839eaa8c9ccfe5a64850707065b6f04 100644 GIT binary patch delta 867 zcmW-de`phT7{>4YCYhI{O;e09Ri{^*m_+L+6567^tuA_sI6}ohPy${dl zfrs~rE$Z(s>g7us?Pq;QZt+n~yKXuq?!3*HG+iL@wDwK<*R<%0CwHzE#QY85|5A-hpe_#w=hf;j2ZlU$=q+J2I7 zF<{&mlcMe1Oj7)mIV7aIt@rQe{8!W`F*Davty*; zy#(8kIeYq2Onr*^Lh=z)91a+|uVy-Ms`l5n#2*KXOLu6Qf4zS>!Yq;E1%7YeGONiL z`j^YQ9u>_;&w1D6mEckvV@{E4aoDz{lej*t4nHdXLBu?>RjA3!z5MQRe(M1FCJqi- zCx}&l7QI5&;^2Xoh=+sbu9GjStbc}M&HS~8{@NgA1C?kf*HH}Kxs!`SNb=bSD-xyZpYT26Q#lRF%=xJFZ=q^^ngrc|@^7vG8_w6wxq z2v?e=j*rqQNry!@Qm>Bch&BNSj3^@;nVV_3lbI)j32-*dr{yzQ(+MjMe`U!!Mo26H zO>NsotT=g|3FiK>+54pJ687bM>&V25Skv=GtH z8u+)k$)lS{WUAb*1c`nETK1q!Ro2W>!#RNAKo{lvEuF6qv9FS+hlG!*%0a2BifS|2 zcU<7#C4&=iW`l8jx0WSFj+a4g)&jAy>Sp8#xWi122kiFrj^FssjG7X}T>l)T4}ap}P8}x80@kT!f-4 iPm0skJGHs<#nnZ2E~8Z@`_S1<^q-N31OLA8xbHv6V{7aH delta 899 zcmW-eZERCz6vzAAc5v(H*11y3ByhVI)(&bmgtFm+CBZSfG9u#x4if7KhEkQ$B_^7s zdv0GAE`7OOFWuAI>beceUF*xeTCwOtN+w&GiOKz7V!{WbB={1(+?o+hoy@CW&N;tx z&i|j2oUd2ZkrlOYMX5Ud1~b9c8P$b>9t@ziFz9UlfZZ6dDmT4afW{=VJQ z>4T7!55I+SM(!q+Bldn14t-V+^YkIeymF$#G!IrN;vOnZGM60{Qkfn+URmA!prk!D z(Y+z8c^`HXKPJ&)6zpi)iA?WT-49CNAudn+gXHq6o$2r~ub)JfC}_ifMO@XH>9eDq zG}sT+Xov>JeWoK4>AX&pTIK`FY2Jw2Lw9NnuI+73gDci5HxyERdWPIa`zXE1NxNW; z4}C#{=T&ES+?_?g(4fWi63g+@2DdybY56}GyN|;8HF`2A8u_8|6wB8@&WuUZFF%XA zVxYfPObNBbdnuup2%$g>v`>Um!f2*rL=S`abeSV>=^?v`@aC3vnfUPStSzIG;Wu5z zhq~oaA|TUWx{uDw>;G#=JdU@)OgLyz^ML{msoeU9rLl=dxY#5IYVSfO{#r8(bwSqX z%~=*?8xI)?7`<+E>Fp$?&OXCzxk$%PZAH}Vs_o%B;ej1HkRFDI8y> z@)|#R8ugq>&MCwbd{yEkBN`k|G7Ts&1x7ay`8!l3x>Wpo3M@Bb7l+YO7Q6_?i%+v4 z?8nAmKw&f10xlETUIo~$1L$@) zb`*1~i%+|;=;pe>3c UE0&I9V_R|d^SJ};4^Ni<1=65`UjP6A diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 425fcb18..ae48628f 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -665,6 +665,9 @@ "action": "store_true", "type": "bool" }, + "extra_keys": { + "type": "int" + }, "calc_playthrough": { "action": "store_false", "type": "bool" diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index c63ef213..fdfd4a4b 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -425,6 +425,7 @@ "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)"], + "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)" ], "any_enemy_logic": [ "How to handle potential traversal between dungeon in Crossed door shuffle",