diff --git a/Gui.py b/Gui.py index 284be31b..a0796088 100755 --- a/Gui.py +++ b/Gui.py @@ -138,7 +138,10 @@ def guiMain(args=None): sprite = None def set_sprite(sprite_param): nonlocal sprite - if sprite_param is None or not sprite_param.valid: + if isinstance(sprite_param, str): + sprite = sprite_param + spriteNameVar.set(sprite_param) + elif sprite_param is None or not sprite_param.valid: sprite = None spriteNameVar.set('(unchanged)') else: @@ -1409,7 +1412,31 @@ class SpriteSelector(object): button = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite) button.pack(side=LEFT, padx=(0, 5)) - button = Button(frame, text="Random sprite", command=self.use_random_sprite) + self.randomButtonText = StringVar() + button = Button(frame, textvariable=self.randomButtonText, command=self.use_random_sprite) + button.pack(side=LEFT, padx=(0, 5)) + self.randomButtonText.set("Random") + + self.randomOnEventText = StringVar() + self.randomOnHitVar = IntVar() + self.randomOnEnterVar = IntVar() + self.randomOnExitVar = IntVar() + self.randomOnSlashVar = IntVar() + self.randomOnItemVar = IntVar() + + button = Checkbutton(frame, text="Hit", command=self.update_random_button, variable=self.randomOnHitVar) + button.pack(side=LEFT, padx=(0, 5)) + + button = Checkbutton(frame, text="Enter", command=self.update_random_button, variable=self.randomOnEnterVar) + button.pack(side=LEFT, padx=(0, 5)) + + button = Checkbutton(frame, text="Exit", command=self.update_random_button, variable=self.randomOnExitVar) + button.pack(side=LEFT, padx=(0, 5)) + + button = Checkbutton(frame, text="Slash", command=self.update_random_button, variable=self.randomOnSlashVar) + button.pack(side=LEFT, padx=(0, 5)) + + button = Checkbutton(frame, text="Item", command=self.update_random_button, variable=self.randomOnItemVar) button.pack(side=LEFT, padx=(0, 5)) if adjuster: @@ -1574,8 +1601,18 @@ class SpriteSelector(object): self.callback(Sprite.default_link_sprite()) self.window.destroy() + def update_random_button(self): + randomon = "-hit" if self.randomOnHitVar.get() else "" + randomon += "-enter" if self.randomOnEnterVar.get() else "" + randomon += "-exit" if self.randomOnExitVar.get() else "" + randomon += "-slash" if self.randomOnSlashVar.get() else "" + randomon += "-item" if self.randomOnItemVar.get() else "" + self.randomOnEventText.set(f"randomon{randomon}" if randomon else None) + self.randomButtonText.set("Random On Event" if randomon else "Random") + def use_random_sprite(self): - self.callback(random.choice(self.all_sprites) if self.all_sprites else None) + randomon = self.randomOnEventText.get() + self.callback(randomon if randomon else (random.choice(self.all_sprites) if self.all_sprites else None)) self.window.destroy() def select_sprite(self, spritename): diff --git a/Main.py b/Main.py index 8f1fcfa1..40cf81d0 100644 --- a/Main.py +++ b/Main.py @@ -177,10 +177,9 @@ def main(args, seed=None): rom_names = [] def _gen_rom(team: int, player: int): - 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] or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' - or world.shufflepots[player] or sprite_random_on_hit or world.bush_shuffle[player] + or world.shufflepots[player] or world.bush_shuffle[player] or world.killable_thieves[player] or world.tile_shuffle[player]) rom = LocalRom(args.rom) @@ -188,8 +187,7 @@ def main(args, seed=None): patch_rom(world, rom, player, team, use_enemizer) if use_enemizer: - patch_enemizer(world, player, rom, args.enemizercli, - sprite_random_on_hit) + patch_enemizer(world, player, rom, args.enemizercli) if args.race: patch_race_rom(rom) @@ -198,7 +196,7 @@ def main(args, seed=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], world, player) + args.ow_palettes[player], args.uw_palettes[player], world, player, True) mcsb_name = '' if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], diff --git a/Rom.py b/Rom.py index 6628bf81..df615bc5 100644 --- a/Rom.py +++ b/Rom.py @@ -1,5 +1,5 @@ JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '45a7732cfb056a251285fcb14e9bb8a7' +RANDOMIZERBASEHASH = 'f71376f57dfd3d69eaac96f2f391ff64' import io import json @@ -59,15 +59,6 @@ class LocalRom(object): self.buffer[startaddress:startaddress + len(values)] = values def write_to_file(self, file, hide_enemizer=False): - - if hide_enemizer: - extra_zeroes = 0x400000 - len(self.buffer) - if extra_zeroes > 0: - buffer = self.buffer + bytes([0x00] * extra_zeroes) - with open(file, 'wb') as outfile: - outfile.write(buffer) - return - with open(file, 'wb') as outfile: outfile.write(self.buffer) @@ -106,7 +97,7 @@ class LocalRom(object): 'Supplied Base Rom does not match known MD5 for JAP(1.0) release. Will try to patch anyway.') # extend to 2MB - self.buffer.extend(bytearray([0x00]) * (0x200000 - len(self.buffer))) + self.buffer.extend(bytearray([0x00]) * (0x400000 - len(self.buffer))) # load randomizer patches with open(local_path('data', 'base2current.json')) as stream: @@ -196,7 +187,50 @@ def check_enemizer(enemizercli): check_enemizer.done = True -def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite_on_hit: bool): +def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_random_on_event): + onevent = onhit = 0 + sprites = list() + if not allow_random_on_event: + allow_random_on_event = not rom.read_byte(0x186381) # Check if explicitly disabled in rom. If so, it stays that way. + if sprite and not isinstance(sprite, Sprite): + sprite = sprite.lower() + if sprite.startswith('randomon'): + onevent = onhit = 0x01 if 'hit' in sprite else 0x00 + onevent += 0x02 if 'enter' in sprite else 0x00 + onevent += 0x04 if 'exit' in sprite else 0x00 + onevent += 0x08 if 'slash' in sprite else 0x00 + onevent += 0x10 if 'item' in sprite else 0x00 + sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite, local_random) + + # write link sprite if required + if sprite: + sprite.write_to_rom(rom) + + if allow_random_on_event: + rom.write_int16(0x18637F, onevent) + rom.write_byte(0x186381, 0x00) # Enable usage of Random On Event. + if rom.read_byte(0x200000): + rom.write_byte(0x200103, onhit) + + _populate_sprite_table() + if onevent: + sprites = list(set(_sprite_table.values())) # convert to list and remove dupes + else: + sprites.append(sprite) + if sprites: + while len(sprites) < 32: + sprites.extend(sprites) + local_random.shuffle(sprites) + + for i, sprite in enumerate(sprites[:32]): + if not i and not onevent: + continue + rom.write_bytes(0x300000 + (i * 0x8000), sprite.sprite) + rom.write_bytes(0x307000 + (i * 0x8000), sprite.palette) + rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette) + + +def patch_enemizer(world, player: int, rom: LocalRom, enemizercli): check_enemizer(enemizercli) randopatch_path = os.path.abspath(output_path(f'enemizer_randopatch_{player}.sfc')) options_path = os.path.abspath(output_path(f'enemizer_options_{player}.json')) @@ -258,7 +292,7 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite 'RandomizeTileTrapPattern': world.tile_shuffle[player], 'RandomizeTileTrapFloorTile': False, 'AllowKillableThief': world.killable_thieves[player], - 'RandomizeSpriteOnHit': random_sprite_on_hit, + 'RandomizeSpriteOnHit': False, 'DebugMode': False, 'DebugForceEnemy': False, 'DebugForceEnemyId': 0, @@ -334,19 +368,6 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite rom.write_byte(0x04DE81, 6) rom.write_byte(0x200101, 0) # Do not close boss room door on entry. - if random_sprite_on_hit: - _populate_sprite_table() - sprites = list(set(_sprite_table.values())) # convert to list and remove dupes - if sprites: - while len(sprites) < 32: - sprites.extend(sprites) - world.rom_seeds[player].shuffle(sprites) - - for i, sprite in enumerate(sprites[:32]): - rom.write_bytes(0x300000 + (i * 0x8000), sprite.sprite) - rom.write_bytes(0x307000 + (i * 0x8000), sprite.palette) - rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette) - for used in (randopatch_path, options_path): try: os.remove(used) @@ -376,7 +397,7 @@ def _populate_sprite_table(): def get_sprite_from_name(name, local_random=random): _populate_sprite_table() name = name.lower() - if name in ['random', 'randomonhit']: + if name.startswith('random'): return local_random.choice(list(_sprite_table.values())) return _sprite_table.get(name, None) @@ -411,7 +432,7 @@ class Sprite(object): self.sprite = filedata[:0x7000] self.palette = filedata[0x7000:0x7078] self.glove_palette = filedata[0x7078:] - elif len(filedata) in [0x100000, 0x200000]: + elif len(filedata) in [0x100000, 0x200000, 0x400000]: # full rom with patched sprite, extract it self.sprite = filedata[0x80000:0x87000] self.palette = filedata[0xDD308:0xDD380] @@ -546,6 +567,9 @@ class Sprite(object): rom.write_bytes(0x80000, self.sprite) rom.write_bytes(0xDD308, self.palette) rom.write_bytes(0xDEDF5, self.glove_palette) + rom.write_bytes(0x300000, self.sprite) + rom.write_bytes(0x307000, self.palette) + rom.write_bytes(0x307078, self.glove_palette) def patch_rom(world, rom, player, team, enemized): local_random = world.rom_seeds[player] @@ -1460,10 +1484,9 @@ def hud_format_text(text): return output[:32] -def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes, world=None, player=1): +def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes, world=None, player=1, allow_random_on_event=False): local_random = random if not world else world.rom_seeds[player] - if sprite and not isinstance(sprite, Sprite): - sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite, local_random) + apply_random_sprite_on_event(rom, sprite, local_random, allow_random_on_event) # enable instant item menu if fastmenu == 'instant': @@ -1514,10 +1537,6 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr rom.write_byte(0x6FA30, {'red': 0x24, 'blue': 0x2C, 'green': 0x3C, 'yellow': 0x28}[color]) rom.write_byte(0x65561, {'red': 0x05, 'blue': 0x0D, 'green': 0x19, 'yellow': 0x09}[color]) - # write link sprite if required - if sprite: - sprite.write_to_rom(rom) - # reset palette if it was adjusted already default_ow_palettes(rom) default_uw_palettes(rom) diff --git a/data/basepatch.bmbp b/data/basepatch.bmbp index f4d822ae..070b34c4 100644 Binary files a/data/basepatch.bmbp and b/data/basepatch.bmbp differ diff --git a/playerSettings.yaml b/playerSettings.yaml index fed1b0b8..70e3411f 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -288,7 +288,12 @@ debug: # Only available if the host uses the doors branch, it is ignored otherwi rom: sprite: # Enter the name of your preferred sprite and weight it appropriately random: 0 - randomonhit: 0 + randomonhit: 0 # Random sprite on hit + randomonenter: 0 # Random sprite on entering the underworld. + randomonexit: 0 # Random sprite on exiting the underworld. + randomonslash: 0 # Random sprite on sword slashes + randomonitem: 0 # Random sprite on getting items. + # You can combine these events like this. randomonhit-enter-exit if you want it on hit, enter, exit. Link: 50 # To add other sprites: open the gui/Creator, go to adjust, select a sprite and write down the name the gui calls it disablemusic: # If "on", all in-game music will be disabled on: 0