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 02dcd32f..a43acf16 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ 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",