diff --git a/Adjuster.py b/Adjuster.py index c6f42e6e..a6e964a8 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -36,6 +36,7 @@ def main(): parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout']) parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true') + parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true') parser.add_argument('--sprite', help='''\ Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes, diff --git a/AdjusterMain.py b/AdjusterMain.py index bc463444..7d7e2f6e 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -25,7 +25,7 @@ def adjust(args): args.sprite = None apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, - args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing) + args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx) output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) diff --git a/BaseClasses.py b/BaseClasses.py index fda63d58..7e866f5d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -123,7 +123,7 @@ class World(object): set_player_attr('compassshuffle', False) set_player_attr('keyshuffle', False) set_player_attr('bigkeyshuffle', False) - set_player_attr('bomblogic', False) + set_player_attr('bombbag', False) set_player_attr('difficulty_requirements', None) set_player_attr('boss_shuffle', 'none') set_player_attr('enemy_shuffle', 'none') @@ -877,7 +877,7 @@ class CollectionState(object): # In the future, this can be used to check if the player starts without bombs def can_use_bombs(self, player): - return (not self.world.bomblogic[player] or self.has('Bomb Upgrade (+10)', player)) and self.can_farm_bombs(player) + return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player)) and self.can_farm_bombs(player) def can_hit_crystal(self, player): return (self.can_use_bombs(player) @@ -2304,7 +2304,7 @@ class Spoiler(object): 'logic': self.world.logic, 'mode': self.world.mode, 'retro': self.world.retro, - 'bomblogic': self.world.bomblogic, + 'bombbag': self.world.bombbag, 'weapons': self.world.swords, 'goal': self.world.goal, 'ow_shuffle': self.world.owShuffle, @@ -2416,7 +2416,7 @@ class Spoiler(object): outfile.write('Experimental:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['experimental'][player] else 'No')) outfile.write('Key Drops shuffled:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No')) outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shopsanity'][player] else 'No')) - outfile.write('Bomblogic:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bomblogic'][player] else 'No')) + outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bombbag'][player] else 'No')) if self.doors: outfile.write('\n\nDoors:\n\n') outfile.write('\n'.join( diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff444f7..33f08a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ ### 0.1.7.0 - Expanded new DR bomb logic to all modes (bomb usage in logic only if there is an unlimited supply of bombs available) -- ~~Merged DR v0.5.0.1 - Bomblogic mode / Enemizer fixes~~ +- ~~Merged DR v0.5.0.1 - Bombbag mode / Enemizer fixes~~ ### 0.1.6.9 - ~~Merged DR v0.4.0.12 - Secure random update / Credits fix~~ diff --git a/CLI.py b/CLI.py index 4ae29bd1..fb9367c3 100644 --- a/CLI.py +++ b/CLI.py @@ -97,14 +97,14 @@ def parse_cli(argv, no_defaults=False): 'ow_shuffle', 'ow_swap', 'ow_keepsimilar', 'ow_fluteshuffle', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', - 'bomblogic', + 'bombbag', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', 'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code', - 'reduce_flashing']: + 'reduce_flashing', 'shuffle_sfx']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -128,7 +128,7 @@ def parse_settings(): settings = { "lang": "en", "retro": False, - "bomblogic": False, + "bombbag": False, "mode": "open", "logic": "noglitches", "goal": "ganon", @@ -197,6 +197,7 @@ def parse_settings(): "ow_palettes": "default", "uw_palettes": "default", "reduce_flashing": False, + "shuffle_sfx": False, # Spoiler defaults to TRUE # Playthrough defaults to TRUE diff --git a/ItemList.py b/ItemList.py index e1d40b09..44b8edc0 100644 --- a/ItemList.py +++ b/ItemList.py @@ -37,7 +37,7 @@ Difficulty = namedtuple('Difficulty', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother', - 'retro', 'bomblogic', + 'retro', 'bombbag', 'extras', 'progressive_sword_limit', 'progressive_shield_limit', 'progressive_armor_limit', 'progressive_bottle_limit', 'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit']) @@ -61,7 +61,7 @@ difficulties = { timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10, - bomblogic = ['Bomb Upgrade (+10)'] * 2, + bombbag = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 4, progressive_shield_limit = 3, @@ -87,7 +87,7 @@ difficulties = { timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, - bomblogic = ['Bomb Upgrade (+10)'] * 2, + bombbag = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 3, progressive_shield_limit = 2, @@ -113,7 +113,7 @@ difficulties = { timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15, - bomblogic = ['Bomb Upgrade (+10)'] * 2, + bombbag = ['Bomb Upgrade (+10)'] * 2, extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], progressive_sword_limit = 2, progressive_shield_limit = 1, @@ -255,10 +255,10 @@ def generate_itempool(world, player): # set up item pool if world.custom: - (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bomblogic[player], world.customitemarray) + (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bombbag[player], world.customitemarray) world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999) else: - (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.owShuffle[player], world.owSwap[player], world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bomblogic[player], world.doorShuffle[player], world.logic[player]) + (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.owShuffle[player], world.owSwap[player], world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bombbag[player], world.doorShuffle[player], world.logic[player]) if player in world.pool_adjustment.keys(): amt = world.pool_adjustment[player] @@ -288,7 +288,7 @@ def generate_itempool(world, player): if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: if item not in possible_weapons: possible_weapons.append(item) - if not world.bomblogic[player] and item in ['Bombs (10)']: + if not world.bombbag[player] and item in ['Bombs (10)']: if item not in possible_weapons and world.doorShuffle[player] != 'crossed': possible_weapons.append(item) starting_weapon = random.choice(possible_weapons) @@ -319,6 +319,11 @@ def generate_itempool(world, player): p_item = next(item for item in items if item.name == potion and item.player == player) p_item.priority = True # don't beemize one of each potion + if world.bombbag[player]: + for item in items: + if item.name == 'Bomb Upgrade (+10)' and item.player == player: + item.advancement = True + world.lamps_needed_for_dark_rooms = lamps_needed_for_dark_rooms if clock_mode is not None: @@ -524,10 +529,7 @@ def set_up_shops(world, player): rss.locked = True cap_shop = world.get_region('Capacity Upgrade', player).shop cap_shop.inventory[1] = None # remove arrow capacity upgrades in retro - if world.bomblogic[player]: - for item in world.itempool: - if item.name == 'Bomb Upgrade (+10)' and item.player == player: - item.advancement = True + if world.bombbag[player]: if world.shopsanity[player]: removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player] for remove in removals: @@ -535,7 +537,7 @@ def set_up_shops(world, player): world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade else: cap_shop = world.get_region('Capacity Upgrade', player).shop - cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bomblogic + cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bombbag def customize_shops(world, player): @@ -577,7 +579,7 @@ def customize_shops(world, player): shop.shopkeeper_config = shopkeeper # handle capacity upgrades - randomly choose a bomb bunch or arrow bunch to become capacity upgrades if world.difficulty[player] == 'normal': - if not found_bomb_upgrade and len(possible_replacements) > 0 and not world.bomblogic[player]: + if not found_bomb_upgrade and len(possible_replacements) > 0 and not world.bombbag[player]: choices = [] for shop, idx, loc, item in possible_replacements: if item.name in ['Bombs (3)', 'Bombs (10)']: @@ -725,7 +727,7 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)' 'Rupees (100)': 100, 'Rupees (300)': 300} -def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bomblogic, door_shuffle, logic): +def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic): pool = [] placed_items = {} precollected_items = [] @@ -772,10 +774,10 @@ def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_ diff = difficulties[difficulty] pool.extend(diff.baseitems) - if bomblogic: + if bombbag: pool = [item.replace('Bomb Upgrade (+5)','Rupees (5)') for item in pool] pool = [item.replace('Bomb Upgrade (+10)','Rupees (5)') for item in pool] - pool.extend(diff.bomblogic) + pool.extend(diff.bombbag) # expert+ difficulties produce the same contents for # all bottles, since only one bottle is available @@ -871,7 +873,7 @@ def get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, treasure_ pool.extend(['Small Key (Universal)']) return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) -def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bomblogic, customitemarray): +def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag, customitemarray): if isinstance(customitemarray,dict) and 1 in customitemarray: customitemarray = customitemarray[1] pool = [] @@ -990,7 +992,7 @@ def test(): for door_shuffle in ['basic', 'crossed', 'vanilla']: for owShuffle in ['full', 'vanilla']: for owSwap in ['mixed', 'vanilla']: - out = get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bomblogic, door_shuffle, logic) + out = get_pool_core(progressive, owShuffle, owSwap, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic) count = len(out[0]) + len(out[1]) correct_count = total_items_to_place @@ -1000,7 +1002,7 @@ def test(): if retro: correct_count += 28 try: - assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bomblogic)) + assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag)) except AssertionError as e: print(e) diff --git a/Main.py b/Main.py index d59588f1..84d97fb9 100644 --- a/Main.py +++ b/Main.py @@ -29,7 +29,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.5.0.1-u' +__version__ = '0.5.0.2-u' from source.classes.BabelFish import BabelFish @@ -80,7 +80,7 @@ def main(args, seed=None, fish=None): world.compassshuffle = args.compassshuffle.copy() world.keyshuffle = args.keyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy() - world.bomblogic = args.bomblogic.copy() + world.bombbag = args.bombbag.copy() world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} world.crystals_ganon_orig = args.crystals_ganon.copy() @@ -307,7 +307,8 @@ def main(args, seed=None, fish=None): apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player], args.fastmenu[player], args.disablemusic[player], args.sprite[player], - args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player]) + args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player], + args.shuffle_sfx[player]) if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches @@ -397,7 +398,7 @@ def copy_world(world): ret.compassshuffle = world.compassshuffle.copy() ret.keyshuffle = world.keyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy() - ret.bomblogic = world.bomblogic.copy() + ret.bombbag = world.bombbag.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() diff --git a/Mystery.py b/Mystery.py index 8302398e..88f5d7f0 100644 --- a/Mystery.py +++ b/Mystery.py @@ -183,7 +183,7 @@ def roll_settings(weights): ret.retro = True ret.retro = get_choice('retro') == 'on' # this overrides world_state if used - ret.bomblogic = get_choice('bomblogic') == 'on' + ret.bombbag = get_choice('bombbag') == 'on' ret.hints = get_choice('hints') == 'on' @@ -241,6 +241,7 @@ def roll_settings(weights): ret.heartbeep = get_choice('heartbeep', romweights) ret.ow_palettes = get_choice('ow_palettes', romweights) ret.uw_palettes = get_choice('uw_palettes', romweights) + ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on' return ret diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c0fae696..76783862 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,14 +1,24 @@ # New Features -Bomb Logic added as an option. This removes your ability to use bombs until you find a "bomb bag", a +10 Bomb Capacity item. It is accounted for in the logic, so you aren't expected to get items behind bomb walls until you have found the bomb capacity item. The upgrades are removed from the upgrade fairy as well. +## Shuffle SFX + +Shuffles a large portion of the sounds effects. Can be used with the adjuster. + +CLI: ```--shuffle_sfx``` -``` ---bomblogic -``` +## Bomb Logic + +When enabling this option, you do not start with bomb capacity but rather you must find 1 of 2 bomb bags. (They are represented by the +10 capacity item.) Bomb capacity upgrades are otherwise unavailable. + +CLI: ```--bombbag``` + # Bug Fixes and Notes. + +* 0.5.0.2 + * --shuffle_sfx option added * 0.5.0.1 - * --bomblogic option added + * --bombbag option added * 0.5.0.0 * Handles headered roms for enemizer (Thanks compiling) * Warning added for earlier version of python (Thanks compiling) diff --git a/Rom.py b/Rom.py index 97bdfc3f..bc4a9f02 100644 --- a/Rom.py +++ b/Rom.py @@ -29,6 +29,8 @@ from Items import ItemFactory from EntranceShuffle import door_addresses, exit_ids from OverworldShuffle import default_flute_connections, flute_data +from source.classes.SFX import randomize_sfx + JAP10HASH = '03a63945398191337e896e5771f77173' RANDOMIZERBASEHASH = 'cc8fc59caa0bbe6d26ac64b9d2893709' @@ -1136,7 +1138,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x184000, [ # original_item, limit, replacement_item, filler 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees - 0x51, 0x00 if world.bomblogic[player] else 0x06, 0x31 if world.bomblogic[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bomblogic -> turns into Bombs (10) + 0x51, 0x00 if world.bombbag[player] else 0x06, 0x31 if world.bombbag[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bombbag -> turns into Bombs (10) 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade 0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 @@ -1273,7 +1275,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): equip[0x36C] = 0x18 equip[0x36D] = 0x18 equip[0x379] = 0x68 - if world.bomblogic[player]: + if world.bombbag[player]: starting_max_bombs = 0 else: starting_max_bombs = 10 @@ -1568,7 +1570,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x180188, [0, 0, 10]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 0, 10]) # Mantle respawn refills (magic, bombs, arrows) bow_max, bow_small = 70, 10 - elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bomblogic[player] else 'Bombs (10)']: + elif uncle_location.item is not None and uncle_location.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)']: rom.write_byte(0x18004E, 2) # Escape Fill (bombs) rom.write_bytes(0x180185, [0, 50, 0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0, 3, 0]) # Zelda respawn refills (magic, bombs, arrows) @@ -1732,7 +1734,7 @@ def hud_format_text(text): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, - ow_palettes, uw_palettes, reduce_flashing): + ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx): if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer: dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380], @@ -1834,6 +1836,9 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr elif uw_palettes == 'blackout': blackout_uw_palettes(rom) + if shuffle_sfx: + randomize_sfx(rom) + if isinstance(rom, LocalRom): rom.write_crc() diff --git a/Rules.py b/Rules.py index dc884638..3cd00a14 100644 --- a/Rules.py +++ b/Rules.py @@ -1553,7 +1553,7 @@ def standard_rules(world, player): def bomb_escape_rule(): loc = world.get_location("Link's Uncle", player) - return loc.item and loc.item.name in ['Bomb Upgrade (+10)' if world.bomblogic[player] else 'Bombs (10)'] + return loc.item and loc.item.name in ['Bomb Upgrade (+10)' if world.bombbag[player] else 'Bombs (10)'] def standard_escape_rule(state): return state.can_kill_most_things(player) or bomb_escape_rule() diff --git a/mystery_example.yml b/mystery_example.yml index b6b6f051..d569e6d8 100644 --- a/mystery_example.yml +++ b/mystery_example.yml @@ -31,6 +31,9 @@ pot_shuffle: on: 1 off: 3 + bombbag: + on: 1 + off: 4 entrance_shuffle: none: 15 dungeonssimple: 3 @@ -147,3 +150,6 @@ half: 0 quarter: 1 off: 0 + shuffle_sfx: + on: 1 + off: 1 diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 7d7dbe93..7724ae9e 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -243,6 +243,10 @@ "action": "store_true", "type": "bool" }, + "shuffle_sfx": { + "action": "store_true", + "type": "bool" + }, "mapshuffle": { "action": "store_true", "type": "bool" @@ -264,7 +268,7 @@ "type": "bool", "help": "suppress" }, - "bomblogic": { + "bombbag": { "action": "store_true", "type": "bool" }, diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index d12ac330..b1691e53 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -292,7 +292,7 @@ "and a few other little things make this more like Zelda-1. (default: %(default)s)" ], "pseudoboots": [ " Players starts with pseudo boots that allow dashing but no item checks (default: %(default)s"], - "bomblogic": ["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)" ], "startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ], "usestartinventory": [ "Toggle usage of Starting Inventory." ], "custom": [ "Not supported." ], @@ -325,6 +325,7 @@ "sprite that will be extracted." ], "reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ], + "shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ], "create_rom": [ "Create an output rom file. (default: %(default)s)" ], "gui": [ "Launch the GUI. (default: %(default)s)" ], "jsonout": [ diff --git a/resources/app/gui/adjust/overview/widgets.json b/resources/app/gui/adjust/overview/widgets.json index b61fff0e..34560c99 100644 --- a/resources/app/gui/adjust/overview/widgets.json +++ b/resources/app/gui/adjust/overview/widgets.json @@ -2,7 +2,8 @@ "checkboxes": { "nobgm": { "type": "checkbox" }, "quickswap": { "type": "checkbox" }, - "reduce_flashing": {"type": "checkbox"} + "reduce_flashing": {"type": "checkbox" }, + "shuffle_sfx": {"type": "checkbox" } }, "leftAdjustFrame": { "heartcolor": { diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index db9db7fe..beff86e3 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -3,6 +3,7 @@ "adjust.nobgm": "Disable Music & MSU-1", "adjust.quickswap": "L/R Quickswapping", "adjust.reduce_flashing": "Reduce Flashing", + "adjust.shuffle_sfx": "Shuffle Sound Effects", "adjust.heartcolor": "Heart Color", "adjust.heartcolor.red": "Red", @@ -151,6 +152,7 @@ "randomizer.gameoptions.nobgm": "Disable Music & MSU-1", "randomizer.gameoptions.quickswap": "L/R Quickswapping", "randomizer.gameoptions.reduce_flashing": "Reduce Flashing", + "randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects", "randomizer.gameoptions.heartcolor": "Heart Color", "randomizer.gameoptions.heartcolor.red": "Red", @@ -207,7 +209,7 @@ "randomizer.item.hints": "Include Helpful Hints", "randomizer.item.retro": "Retro mode (universal keys)", "randomizer.item.pseudoboots": "Start with Pseudo Boots", - "randomizer.item.bomblogic": "Bomblogic", + "randomizer.item.bombbag": "Bombbag", "randomizer.item.worldstate": "World State", "randomizer.item.worldstate.standard": "Standard", diff --git a/resources/app/gui/randomize/gameoptions/widgets.json b/resources/app/gui/randomize/gameoptions/widgets.json index bc9714e5..cca75d00 100644 --- a/resources/app/gui/randomize/gameoptions/widgets.json +++ b/resources/app/gui/randomize/gameoptions/widgets.json @@ -2,7 +2,8 @@ "checkboxes": { "nobgm": { "type": "checkbox" }, "quickswap": { "type": "checkbox" }, - "reduce_flashing": { "type": "checkbox" } + "reduce_flashing": { "type": "checkbox" }, + "shuffle_sfx": { "type": "checkbox" } }, "leftRomOptionsFrame": { "heartcolor": { diff --git a/resources/app/gui/randomize/item/widgets.json b/resources/app/gui/randomize/item/widgets.json index 15d049c8..038e668c 100644 --- a/resources/app/gui/randomize/item/widgets.json +++ b/resources/app/gui/randomize/item/widgets.json @@ -1,7 +1,7 @@ { "checkboxes": { "retro": { "type": "checkbox" }, - "bomblogic": { "type": "checkbox" }, + "bombbag": { "type": "checkbox" }, "shopsanity": { "type": "checkbox" }, "hints": { "type": "checkbox" diff --git a/source/classes/SFX.py b/source/classes/SFX.py new file mode 100644 index 00000000..d5de414d --- /dev/null +++ b/source/classes/SFX.py @@ -0,0 +1,183 @@ +import random +from Utils import int16_as_bytes + + +class SFX(object): + + def __init__(self, name, sfx_set, orig_id, addr, chain, accomp=False): + self.name = name + self.sfx_set = sfx_set + self.orig_id = orig_id + self.addr = addr + self.chain = chain + self.accomp = accomp + + self.target_set = None + self.target_id = None + self.target_chain = None + + +def init_sfx_data(): + sfx_pool = [SFX('Slash1', 0x02, 0x01, 0x2614, []), SFX('Slash2', 0x02, 0x02, 0x2625, []), + SFX('Slash3', 0x02, 0x03, 0x2634, []), SFX('Slash4', 0x02, 0x04, 0x2643, []), + SFX('Wall clink', 0x02, 0x05, 0x25DD, []), SFX('Bombable door clink', 0x02, 0x06, 0x25D7, []), + SFX('Fwoosh shooting', 0x02, 0x07, 0x25B7, []), SFX('Arrow hitting wall', 0x02, 0x08, 0x25E3, []), + SFX('Boomerang whooshing', 0x02, 0x09, 0x25AD, []), SFX('Hookshot', 0x02, 0x0A, 0x25C7, []), + SFX('Placing bomb', 0x02, 0x0B, 0x2478, []), + SFX('Bomb exploding/Quake/Bombos/Exploding wall', 0x02, 0x0C, 0x269C, []), + SFX('Powder', 0x02, 0x0D, 0x2414, [0x3f]), SFX('Fire rod shot', 0x02, 0x0E, 0x2404, []), + SFX('Ice rod shot', 0x02, 0x0F, 0x24C3, []), SFX('Hammer use', 0x02, 0x10, 0x23FA, []), + SFX('Hammering peg', 0x02, 0x11, 0x23F0, []), SFX('Digging', 0x02, 0x12, 0x23CD, []), + SFX('Flute use', 0x02, 0x13, 0x23A0, [0x3e]), SFX('Cape on', 0x02, 0x14, 0x2380, []), + SFX('Cape off/Wallmaster grab', 0x02, 0x15, 0x2390, []), SFX('Staircase', 0x02, 0x16, 0x232C, []), + SFX('Staircase', 0x02, 0x17, 0x2344, []), SFX('Staircase', 0x02, 0x18, 0x2356, []), + SFX('Staircase', 0x02, 0x19, 0x236E, []), SFX('Tall grass/Hammer hitting bush', 0x02, 0x1A, 0x2316, []), + SFX('Mire shallow water', 0x02, 0x1B, 0x2307, []), SFX('Shallow water', 0x02, 0x1C, 0x2301, []), + SFX('Lifting object', 0x02, 0x1D, 0x22BB, []), SFX('Cutting grass', 0x02, 0x1E, 0x2577, []), + SFX('Item breaking', 0x02, 0x1F, 0x22E9, []), SFX('Item falling in pit', 0x02, 0x20, 0x22DA, []), + SFX('Bomb hitting ground/General bang', 0x02, 0x21, 0x22CF, []), + SFX('Pushing object/Armos bounce', 0x02, 0x22, 0x2107, []), SFX('Boots dust', 0x02, 0x23, 0x22B1, []), + SFX('Splashing', 0x02, 0x24, 0x22A5, [0x3d]), SFX('Mire shallow water again?', 0x02, 0x25, 0x2296, []), + SFX('Link taking damage', 0x02, 0x26, 0x2844, []), SFX('Fainting', 0x02, 0x27, 0x2252, []), + SFX('Item splash', 0x02, 0x28, 0x2287, []), SFX('Rupee refill', 0x02, 0x29, 0x243F, [0x3b]), + SFX('Fire rod shot hitting wall/Bombos spell', 0x02, 0x2A, 0x2033, []), + SFX('Heart beep/Text box', 0x02, 0x2B, 0x1FF2, []), SFX('Sword up', 0x02, 0x2C, 0x1FD9, [0x3a]), + SFX('Magic drain', 0x02, 0x2D, 0x20A6, []), SFX('GT opening', 0x02, 0x2E, 0x1FCA, [0x39]), + SFX('GT opening/Water drain', 0x02, 0x2F, 0x1F47, [0x38]), SFX('Cucco', 0x02, 0x30, 0x1EF1, []), + SFX('Fairy', 0x02, 0x31, 0x20CE, []), SFX('Bug net', 0x02, 0x32, 0x1D47, []), + SFX('Teleport2', 0x02, 0x33, 0x1CDC, [], True), SFX('Teleport1', 0x02, 0x34, 0x1F6F, [0x33]), + SFX('Quake/Vitreous/Zora king/Armos/Pyramid/Lanmo', 0x02, 0x35, 0x1C67, [0x36]), + SFX('Mire entrance (extends above)', 0x02, 0x36, 0x1C64, [], True), + SFX('Spin charged', 0x02, 0x37, 0x1A43, []), SFX('Water sound', 0x02, 0x38, 0x1F6F, [], True), + SFX('GT opening thunder', 0x02, 0x39, 0x1F9C, [], True), SFX('Sword up', 0x02, 0x3A, 0x1FE7, [], True), + SFX('Quiet rupees', 0x02, 0x3B, 0x2462, [], True), SFX('Error beep', 0x02, 0x3C, 0x1A37, []), + SFX('Big splash', 0x02, 0x3D, 0x22AB, [], True), SFX('Flute again', 0x02, 0x3E, 0x23B5, [], True), + SFX('Powder paired', 0x02, 0x3F, 0x2435, [], True), + + SFX('Sword beam', 0x03, 0x01, 0x1A18, []), + SFX('TR opening', 0x03, 0x02, 0x254E, []), SFX('Pyramid hole', 0x03, 0x03, 0x224A, []), + SFX('Angry soldier', 0x03, 0x04, 0x220E, []), SFX('Lynel shot/Javelin toss', 0x03, 0x05, 0x25B7, []), + SFX('BNC swing/Phantom ganon/Helma tail/Arrghus swoosh', 0x03, 0x06, 0x21F5, []), + SFX('Cannon fire', 0x03, 0x07, 0x223D, []), SFX('Damage to enemy; $0BEX.4=1', 0x03, 0x08, 0x21E6, []), + SFX('Enemy death', 0x03, 0x09, 0x21C1, []), SFX('Collecting rupee', 0x03, 0x0A, 0x21A9, []), + SFX('Collecting heart', 0x03, 0x0B, 0x2198, []), + SFX('Non-blank text character', 0x03, 0x0C, 0x218E, []), + SFX('HUD heart (used explicitly by sanc heart?)', 0x03, 0x0D, 0x21B5, []), + SFX('Opening chest', 0x03, 0x0E, 0x2182, []), + SFX('♪Do do do doooooo♫', 0x03, 0x0F, 0x24B9, [0x3C, 0x3D, 0x3E, 0x3F]), + SFX('Opening/Closing map (paired)', 0x03, 0x10, 0x216D, [0x3b]), + SFX('Opening item menu/Bomb shop guy breathing', 0x03, 0x11, 0x214F, []), + SFX('Closing item menu/Bomb shop guy breathing', 0x03, 0x12, 0x215E, []), + SFX('Throwing object (sprites use it as well)/Stalfos jump', 0x03, 0x13, 0x213B, []), + SFX('Key door/Trinecks/Dash key landing/Stalfos Knight collapse', 0x03, 0x14, 0x246C, []), + SFX('Door closing/OW door opening/Chest opening (w/ $29 in $012E)', 0x03, 0x15, 0x212F, []), + SFX('Armos Knight thud', 0x03, 0x16, 0x2123, []), SFX('Rat squeak', 0x03, 0x17, 0x25A6, []), + SFX('Dragging/Mantle moving', 0x03, 0x18, 0x20DD, []), + SFX('Fireball/Laser shot; Somehow used by Trinexx???', 0x03, 0x19, 0x250A, []), + SFX('Chest reveal jingle ', 0x03, 0x1A, 0x1E8A, [0x38]), + SFX('Puzzle jingle', 0x03, 0x1B, 0x20B6, [0x3a]), SFX('Damage to enemy', 0x03, 0x1C, 0x1A62, []), + SFX('Potion refill/Magic drain', 0x03, 0x1D, 0x20A6, []), + SFX('Flapping (Duck/Cucco swarm/Ganon bats/Keese/Raven/Vulture)', 0x03, 0x1E, 0x2091, []), + SFX('Link falling', 0x03, 0x1F, 0x204B, []), SFX('Menu/Text cursor moved', 0x03, 0x20, 0x276C, []), + SFX('Damage to boss', 0x03, 0x21, 0x27E2, []), SFX('Boss dying/Deleting file', 0x03, 0x22, 0x26CF, []), + SFX('Spin attack/Medallion swoosh', 0x03, 0x23, 0x2001, [0x39]), + SFX('OW map perspective change', 0x03, 0x24, 0x2043, []), + SFX('Pressure switch', 0x03, 0x25, 0x1E9D, []), + SFX('Lightning/Game over/Laser/Ganon bat/Trinexx lunge', 0x03, 0x26, 0x1E7B, []), + SFX('Agahnim charge', 0x03, 0x27, 0x1E40, []), SFX('Agahnim/Ganon teleport', 0x03, 0x28, 0x26F7, []), + SFX('Agahnim shot', 0x03, 0x29, 0x1E21, []), + SFX('Somaria/Byrna/Ether spell/Helma fire ball', 0x03, 0x2A, 0x1E12, []), + SFX('Electrocution', 0x03, 0x2B, 0x1DF3, []), SFX('Bees', 0x03, 0x2C, 0x1DC0, []), + SFX('Milestone, also via text', 0x03, 0x2D, 0x1DA9, [0x37]), + SFX('Collecting heart container', 0x03, 0x2E, 0x1D5D, [0x35, 0x34]), + SFX('Collecting absorbable key', 0x03, 0x2F, 0x1D80, [0x33]), + SFX('Byrna spark/Item plop/Magic bat zap/Blob emerge', 0x03, 0x30, 0x1B53, []), + SFX('Sprite falling/Moldorm shuffle', 0x03, 0x31, 0x1ACA, []), + SFX('Bumper boing/Somaria punt/Blob transmutation/Sprite boings', 0x03, 0x32, 0x1A78, []), + SFX('Jingle (paired $2F→$33)', 0x03, 0x33, 0x1D93, [], True), + SFX('Depressing jingle (paired $2E→$35→$34)', 0x03, 0x34, 0x1D66, [], True), + SFX('Ugly jingle (paired $2E→$35→$34)', 0x03, 0x35, 0x1D73, [], True), + SFX('Wizzrobe shot/Helma fireball split/Mothula beam/Blue balls', 0x03, 0x36, 0x1AA7, []), + SFX('Dinky jingle (paired $2D→$37)', 0x03, 0x37, 0x1DB4, [], True), + SFX('Apathetic jingle (paired $1A→$38)', 0x03, 0x38, 0x1E93, [], True), + SFX('Quiet swish (paired $23→$39)', 0x03, 0x39, 0x2017, [], True), + SFX('Defective jingle (paired $1B→$3A)', 0x03, 0x3A, 0x20C0, [], True), + SFX('Petulant jingle (paired $10→$3B)', 0x03, 0x3B, 0x2176, [], True), + SFX('Triumphant jingle (paired $0F→$3C→$3D→$3E→$3F)', 0x03, 0x3C, 0x248A, [], True), + SFX('Less triumphant jingle ($0F→$3C→$3D→$3E→$3F)', 0x03, 0x3D, 0x2494, [], True), + SFX('"You tried, I guess" jingle (paired $0F→$3C→$3D→$3E→$3F)', 0x03, 0x3E, 0x249E, [], True), + SFX('"You didn\'t really try" jingle (paired $0F→$3C→$3D→$3E→$3F)', 0x03, 0x3F, 0x2480, [], True)] + return sfx_pool + + +def shuffle_sfx_data(): + sfx_pool = init_sfx_data() + sfx_map = {2: {}, 3: {}} + accompaniment_map = {2: set(), 3: set()} + candidates = [] + for sfx in sfx_pool: + sfx_map[sfx.sfx_set][sfx.orig_id] = sfx + if not sfx.accomp: + candidates.append((sfx.sfx_set, sfx.orig_id)) + else: + accompaniment_map[sfx.sfx_set].add(sfx.orig_id) + chained_sfx = [x for x in sfx_pool if len(x.chain) > 0] + + random.shuffle(candidates) + + # place chained sfx first + random.shuffle(chained_sfx) # todo: sort largest to smallest + chained_sfx = sorted(chained_sfx, key=lambda x: len(x.chain), reverse=True) + for chained in chained_sfx: + chosen_slot = next(x for x in candidates if len(accompaniment_map[x[0]]) - len(chained.chain) >= 0) + if chosen_slot is None: + raise Exception('Something went wrong with sfx chains') + chosen_set, chosen_id = chosen_slot + chained.target_set, chained.target_id = chosen_slot + chained.target_chain = [] + for downstream in chained.chain: + next_slot = accompaniment_map[chosen_set].pop() + ds_acc = sfx_map[chained.sfx_set][downstream] + ds_acc.target_set, ds_acc.target_id = chosen_set, next_slot + chained.target_chain.append(next_slot) + candidates.remove(chosen_slot) + sfx_pool.remove(chained) + + unchained_sfx = [x for x in sfx_pool if not x.accomp] + # do the rest + for sfx in unchained_sfx: + chosen_slot = candidates.pop() + sfx.target_set, sfx.target_id = chosen_slot + + return sfx_map + + +sfx_table = { + 2: 0x1a8c29, + 3: 0x1A8D25 +} + +# 0x1a8c29 +# d8059 + +sfx_accompaniment_table = { + 2: 0x1A8CA7, + 3: 0x1A8DA3 +} + + +def randomize_sfx(rom): + sfx_map = shuffle_sfx_data() + + for shuffled_sfx in sfx_map.values(): + for sfx in shuffled_sfx.values(): + base_address = sfx_table[sfx.target_set] + rom.write_bytes(base_address + (sfx.target_id * 2) - 2, int16_as_bytes(sfx.addr)) + ac_base = sfx_accompaniment_table[sfx.target_set] + last = sfx.target_id + if sfx.target_chain: + for chained in sfx.target_chain: + rom.write_byte(ac_base + last - 1, chained) + last = chained + rom.write_byte(ac_base + last - 1, 0) + diff --git a/source/classes/constants.py b/source/classes/constants.py index 14818cd5..65e11e2a 100644 --- a/source/classes/constants.py +++ b/source/classes/constants.py @@ -57,7 +57,7 @@ SETTINGSTOPROCESS = { "item": { "hints": "hints", "retro": "retro", - "bomblogic": "bomblogic", + "bombbag": "bombbag", "shopsanity": "shopsanity", "pseudoboots": "pseudoboots", "worldstate": "mode", @@ -104,7 +104,7 @@ SETTINGSTOPROCESS = { "experimental": "experimental", "dungeon_counters": "dungeon_counters", "mixed_travel": "mixed_travel", - "standardize_palettes": "standardize_palettes", + "standardize_palettes": "standardize_palettes" }, "gameoptions": { "nobgm": "disablemusic", @@ -114,7 +114,8 @@ SETTINGSTOPROCESS = { "menuspeed": "fastmenu", "owpalettes": "ow_palettes", "uwpalettes": "uw_palettes", - "reduce_flashing": "reduce_flashing" + "reduce_flashing": "reduce_flashing", + "shuffle_sfx": "shuffle_sfx" }, "generation": { "createspoiler": "create_spoiler", diff --git a/source/gui/adjust/overview.py b/source/gui/adjust/overview.py index 55d72e8d..7e97b2b4 100644 --- a/source/gui/adjust/overview.py +++ b/source/gui/adjust/overview.py @@ -102,7 +102,8 @@ def adjust_page(top, parent, settings): "uwpalettes": "uw_palettes", "quickswap": "quickswap", "nobgm": "disablemusic", - "reduce_flashing": "reduce_flashing" + "reduce_flashing": "reduce_flashing", + "shuffle_sfx": "shuffle_sfx" } guiargs = Namespace() for option in options: