diff --git a/Adjuster.py b/Adjuster.py index d3a8239c..570bcef9 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -6,6 +6,7 @@ import textwrap import sys from AdjusterMain import adjust +from Rom import get_sprite_from_name class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -48,8 +49,8 @@ def main(): if not os.path.isfile(args.rom): input('Could not find valid rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) sys.exit(1) - if args.sprite is not None and not os.path.isfile(args.sprite): - input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) + if args.sprite is not None and not os.path.isfile(args.sprite) and not get_sprite_from_name(args.sprite): + input('Could not find link sprite sheet at given location. \nPress Enter to exit.') sys.exit(1) # set up logger diff --git a/AdjusterMain.py b/AdjusterMain.py index 5bf6eeb4..4bdfa50b 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -1,10 +1,9 @@ import os -import re import time import logging from Utils import output_path, parse_names_string -from Rom import LocalRom, Sprite, apply_rom_settings +from Rom import LocalRom, apply_rom_settings def adjust(args): @@ -12,14 +11,6 @@ def adjust(args): logger = logging.getLogger('') logger.info('Patching ROM.') - if args.sprite is not None: - if isinstance(args.sprite, Sprite): - sprite = args.sprite - else: - sprite = Sprite(args.sprite) - else: - sprite = None - outfilebase = os.path.basename(args.rom)[:-4] + '_adjusted' if os.stat(args.rom).st_size in (0x200000, 0x400000) and os.path.splitext(args.rom)[-1].lower() == '.sfc': @@ -30,7 +21,7 @@ def adjust(args): else: raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.') - apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, sprite, args.ow_palettes, args.uw_palettes, parse_names_string(args.names)) + apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, args.sprite, args.ow_palettes, args.uw_palettes, parse_names_string(args.names)) rom.write_to_file(output_path('%s.sfc' % outfilebase)) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index c315c962..58d6d097 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -9,6 +9,7 @@ import shlex import sys from Main import main +from Rom import get_sprite_from_name from Utils import is_bundled, close_console @@ -289,7 +290,7 @@ def parse_arguments(argv, no_defaults=False): 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'retro', 'accessibility', 'hints', 'shufflepots', 'beemizer', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', - 'ow_palettes', 'uw_palettes']: + 'ow_palettes', 'uw_palettes', 'sprite']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -315,9 +316,9 @@ def start(): if not args.jsonout and not os.path.isfile(args.rom): input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.rom) sys.exit(1) - if args.sprite is not None and not os.path.isfile(args.sprite): + if any([sprite is not None and not os.path.isfile(sprite) and not get_sprite_from_name(sprite) for sprite in args.sprite.values()]): if not args.jsonout: - input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) + input('Could not find link sprite sheet at given location. \nPress Enter to exit.') sys.exit(1) else: raise IOError('Cannot find sprite file at %s' % args.sprite) diff --git a/Main.py b/Main.py index f20940fb..836a4d65 100644 --- a/Main.py +++ b/Main.py @@ -13,7 +13,7 @@ from Items import ItemFactory from Regions import create_regions, mark_light_world_regions from InvertedRegions import create_inverted_regions, mark_dark_world_regions from EntranceShuffle import link_entrances, link_inverted_entrances -from Rom import patch_rom, get_race_rom_patches, get_enemizer_patch, apply_rom_settings, Sprite, LocalRom, JsonRom +from Rom import patch_rom, get_race_rom_patches, get_enemizer_patch, apply_rom_settings, LocalRom, JsonRom from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression @@ -135,14 +135,6 @@ def main(args, seed=None): logger.info('Patching ROM.') - if args.sprite is not None: - if isinstance(args.sprite, Sprite): - sprite = args.sprite - else: - sprite = Sprite(args.sprite) - else: - sprite = None - player_names = parse_names_string(args.names) outfilebase = 'ER_%s' % (args.outputname if args.outputname else world.seed) @@ -150,25 +142,20 @@ def main(args, seed=None): jsonout = {} if not args.suppress_rom: for player in range(1, world.players + 1): + sprite_random_on_hit = type(args.sprite[player]) is str and args.sprite[player].lower() == 'randomonhit' use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none' or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' - or args.shufflepots[player]) + or args.shufflepots[player] or sprite_random_on_hit) + + rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom) + local_rom = LocalRom(args.rom) if not args.jsonout and use_enemizer else None - local_rom = None - if args.jsonout: - rom = JsonRom() - else: - if use_enemizer: - local_rom = LocalRom(args.rom) - rom = JsonRom() - else: - rom = LocalRom(args.rom) patch_rom(world, player, rom, use_enemizer) rom_names.append((player, list(rom.name))) enemizer_patch = [] if use_enemizer and (args.enemizercli or not args.jsonout): - enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player]) + enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shufflepots[player], sprite_random_on_hit) if args.jsonout: jsonout[f'patch{player}'] = rom.patches @@ -185,7 +172,7 @@ def main(args, seed=None): for addr, values in get_race_rom_patches(rom).items(): rom.write_bytes(int(addr), values) - apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite, args.ow_palettes[player], args.uw_palettes[player], player_names) + apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, args.sprite[player], args.ow_palettes[player], args.uw_palettes[player], player_names) mcsb_name = '' if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]): diff --git a/Plando.py b/Plando.py index cf1fbf51..917d4649 100755 --- a/Plando.py +++ b/Plando.py @@ -10,7 +10,7 @@ import sys from BaseClasses import World from Regions import create_regions from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit -from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom, apply_rom_settings +from Rom import patch_rom, LocalRom, write_string_to_rom, apply_rom_settings, get_sprite_from_name from Rules import set_rules from Dungeons import create_dungeons from Items import ItemFactory @@ -68,15 +68,10 @@ def main(args): logger.info('Patching ROM.') - if args.sprite is not None: - sprite = Sprite(args.sprite) - else: - sprite = None - rom = LocalRom(args.rom) patch_rom(world, 1, rom, False) - apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite, args.ow_palettes, args.uw_palettes) + apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, args.sprite, args.ow_palettes, args.uw_palettes) for textname, texttype, text in text_patches: if texttype == 'text': @@ -226,8 +221,8 @@ def start(): if not os.path.isfile(args.plando): input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando) sys.exit(1) - if args.sprite is not None and not os.path.isfile(args.rom): - input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) + if args.sprite is not None and not os.path.isfile(args.sprite) and not get_sprite_from_name(args.sprite): + input('Could not find link sprite sheet at given location. \nPress Enter to exit.') sys.exit(1) # set up logger diff --git a/Rom.py b/Rom.py index cdb1a641..9d942511 100644 --- a/Rom.py +++ b/Rom.py @@ -37,8 +37,7 @@ class JsonRom(object): def write_bytes(self, startaddress, values): if not values: return - if type(values) is not list: - values = list(values) + values = list(values) pos = bisect.bisect_right(self.addresses, startaddress) intervalstart = self.addresses[pos-1] if pos else None @@ -164,7 +163,7 @@ def read_rom(stream): buffer = buffer[0x200:] return buffer -def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepots): +def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepots, random_sprite_on_hit): baserom_path = os.path.abspath(baserom_path) basepatch_path = os.path.abspath(local_path('data/base2current.json')) randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json')) @@ -224,7 +223,7 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepot 'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos', 'RandomizeTileTrapFloorTile': False, 'AllowKillableThief': bool(random.randint(0,1)) if world.enemy_shuffle[player] == 'chaos' else world.enemy_shuffle[player] != 'none', - 'RandomizeSpriteOnHit': False, + 'RandomizeSpriteOnHit': random_sprite_on_hit, 'DebugMode': False, 'DebugForceEnemy': False, 'DebugForceEnemyId': 0, @@ -260,7 +259,6 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepot options['ManualBosses']['GanonsTower2'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['middle'].enemizer_name options['ManualBosses']['GanonsTower3'] = world.get_dungeon('Inverted Ganons Tower', player).bosses['top'].enemizer_name - rom.write_to_file(randopatch_path) with open(options_path, 'w') as f: @@ -287,8 +285,41 @@ def get_enemizer_patch(world, player, rom, baserom_path, enemizercli, shufflepot if os.path.exists(enemizer_output_path): os.remove(enemizer_output_path) + if random_sprite_on_hit: + _populate_sprite_table() + sprites = list(_sprite_table.values()) + if sprites: + while len(sprites) < 32: + sprites.extend(sprites) + random.shuffle(sprites) + + for i, path in enumerate(sprites[:32]): + sprite = Sprite(path) + ret.append({"address": 0x300000 + (i * 0x8000), "patchData": list(sprite.sprite)}) + ret.append({"address": 0x307000 + (i * 0x8000), "patchData": list(sprite.palette)}) + ret.append({"address": 0x307078 + (i * 0x8000), "patchData": list(sprite.glove_palette)}) + return ret +_sprite_table = {} +def _populate_sprite_table(): + if not _sprite_table: + for dir in [local_path('data/sprites/official'), local_path('data/sprites/unofficial')]: + for file in os.listdir(dir): + filepath = os.path.join(dir, file) + if not os.path.isfile(filepath): + continue + sprite = Sprite(filepath) + if sprite.valid: + _sprite_table[sprite.name.lower()] = filepath + +def get_sprite_from_name(name): + _populate_sprite_table() + name = name.lower() + if name in ['random', 'randomonhit']: + return Sprite(random.choice(list(_sprite_table.values()))) + return Sprite(_sprite_table[name]) if name in _sprite_table else None + class Sprite(object): default_palette = [255, 127, 126, 35, 183, 17, 158, 54, 165, 20, 255, 1, 120, 16, 157, 89, 71, 54, 104, 59, 74, 10, 239, 18, 92, 42, 113, 21, 24, 122, @@ -1282,6 +1313,8 @@ def hud_format_text(text): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes, names = None): + if sprite and not isinstance(sprite, Sprite): + sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite) # enable instant item menu if fastmenu == 'instant':