From 433a1dfc3545c4ee523042f3113f55ac83cd6cbc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 4 Jul 2021 11:23:08 -0500 Subject: [PATCH] Doing GitHub's work for them and ACTUALLY merging in DR v0.4.0.7 changes --- Adjuster.py | 1 + AdjusterMain.py | 4 +- CLI.py | 4 +- DoorShuffle.py | 15 +-- Doors.py | 23 +++-- Dungeons.py | 3 +- Main.py | 4 +- Regions.py | 5 +- Rom.py | 92 ++++++++++++++++++- Rules.py | 13 ++- resources/app/cli/args.json | 4 + resources/app/cli/lang/en.json | 3 +- .../app/gui/adjust/overview/widgets.json | 3 +- resources/app/gui/lang/en.json | 1 + .../gui/randomize/gameoptions/widgets.json | 3 +- source/classes/SpriteSelector.py | 5 +- source/classes/constants.py | 3 +- source/gui/adjust/overview.py | 4 +- 18 files changed, 153 insertions(+), 37 deletions(-) diff --git a/Adjuster.py b/Adjuster.py index 570bcef9..c6f42e6e 100755 --- a/Adjuster.py +++ b/Adjuster.py @@ -35,6 +35,7 @@ def main(): help='Select the color of Link\'s heart meter. (default: %(default)s)') 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('--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 b1d5cadf..bc463444 100644 --- a/AdjusterMain.py +++ b/AdjusterMain.py @@ -24,8 +24,10 @@ def adjust(args): if not hasattr(args,"sprite"): 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) + 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) + output_path.cached_path = args.outputpath rom.write_to_file(output_path('%s.sfc' % outfilebase)) logger.info('Done. Enjoy.') diff --git a/CLI.py b/CLI.py index 3e13f790..467ccf0d 100644 --- a/CLI.py +++ b/CLI.py @@ -102,7 +102,8 @@ def parse_cli(argv, no_defaults=False): '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']: + 'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code', + 'reduce_flashing']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) if player == 1: setattr(ret, name, {1: value}) @@ -192,6 +193,7 @@ def parse_settings(): "fastmenu": "normal", "ow_palettes": "default", "uw_palettes": "default", + "reduce_flashing": False, # Spoiler defaults to TRUE # Playthrough defaults to TRUE diff --git a/DoorShuffle.py b/DoorShuffle.py index 3478d027..5634efce 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -99,6 +99,7 @@ def link_doors_main(world, player): for portal in world.dungeon_portals[player]: connect_portal(portal, world, player) + fix_big_key_doors_with_ugly_smalls(world, player) if world.doorShuffle[player] == 'vanilla': for entrance, ext in open_edges: connect_two_way(world, entrance, ext, player) @@ -674,7 +675,6 @@ def find_entrance_region(portal): # paired_door.pair = False def within_dungeon(world, player): - fix_big_key_doors_with_ugly_smalls(world, player) add_inaccessible_doors(world, player) entrances_map, potentials, connections = determine_entrance_list(world, player) connections_tuple = (entrances_map, potentials, connections) @@ -930,7 +930,6 @@ def treat_split_as_whole_dungeon(split_dungeon, name, origin_list, world, player def cross_dungeon(world, player): - fix_big_key_doors_with_ugly_smalls(world, player) add_inaccessible_doors(world, player) entrances_map, potentials, connections = determine_entrance_list(world, player) connections_tuple = (entrances_map, potentials, connections) @@ -1617,13 +1616,15 @@ def reassign_key_doors(builder, world, player): else: room.delete(d.doorListPos) d.smallKey = False - elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal and not d.entranceFlag: - world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) + elif d.type is DoorType.Interior and d not in flat_proposal and d.dest not in flat_proposal: + if not d.entranceFlag: + world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False d.dest.smallKey = False queue.remove(d.dest) - elif d.type is DoorType.Normal and d not in flat_proposal and not d.entranceFlag: - world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) + elif d.type is DoorType.Normal and d not in flat_proposal: + if not d.entranceFlag: + world.get_room(d.roomIndex, player).change(d.doorListPos, DoorKind.Normal) d.smallKey = False for dp in world.paired_doors[player]: if dp.door_a == d.name or dp.door_b == d.name: @@ -2299,6 +2300,8 @@ logical_connections = [ ('GT Double Switch Entry to Left Barrier - Orange', 'GT Double Switch Left'), ('GT Double Switch Entry to Ranged Switches', 'GT Double Switch Entry - Ranged Switches'), ('GT Double Switch Entry Ranged Switches Exit', 'GT Double Switch Entry'), + ('GT Double Switch Left to Crystal', 'GT Double Switch Left - Crystal'), + ('GT Double Switch Left Crystal Exit', 'GT Double Switch Left'), ('GT Double Switch Left to Entry Barrier - Orange', 'GT Double Switch Entry'), ('GT Double Switch Left to Entry Bypass', 'GT Double Switch Entry'), ('GT Double Switch Left to Pot Corners Bypass', 'GT Double Switch Pot Corners'), diff --git a/Doors.py b/Doors.py index cc13826e..b0979a5d 100644 --- a/Doors.py +++ b/Doors.py @@ -397,10 +397,10 @@ def create_doors(world, player): create_door(player, 'PoD Arena Landing to Right Barrier - Blue', Lgcl), create_door(player, 'PoD Arena Landing to North Barrier - Orange', Lgcl), create_door(player, 'PoD Arena Right to Landing Barrier - Blue', Lgcl), - create_door(player, 'PoD Arena Right to Ranged Crystal', Lgcl), - create_door(player, 'PoD Arena Right Ranged Crystal Exit', Lgcl), - create_door(player, 'PoD Arena Ledge to Ranged Crystal', Lgcl), - create_door(player, 'PoD Arena Ledge Ranged Crystal Exit', Lgcl), + create_door(player, 'PoD Arena Right to Ranged Crystal', Lgcl), # considered out of logic + create_door(player, 'PoD Arena Right Ranged Crystal Exit', Lgcl).no_exit(), # blocked here for pre-validate + create_door(player, 'PoD Arena Ledge to Ranged Crystal', Lgcl), # considered out of logic + create_door(player, 'PoD Arena Ledge Ranged Crystal Exit', Lgcl).no_exit(), # blocked here for pre-validate create_door(player, 'PoD Arena Ledge ES', Nrml).dir(Ea, 0x2a, Bot, High).pos(2), create_door(player, 'PoD Sexy Statue W', Nrml).dir(We, 0x2b, Mid, High).pos(3), create_door(player, 'PoD Sexy Statue NW', Nrml).dir(No, 0x2b, Left, High).trap(0x1).pos(2), @@ -875,7 +875,7 @@ def create_doors(world, player): create_door(player, 'Mire Left Bridge Down Stairs', Sprl).dir(Dn, 0xa2, 0, HTL).ss(A, 0x12, 0x00), create_door(player, 'Mire Fishbone E', Nrml).dir(Ea, 0xa1, Mid, High).pos(1), create_door(player, 'Mire Fishbone Blue Barrier', Lgcl), - create_door(player, 'Mire Fishbone Blue Barrier Bypass', Lgcl), + create_door(player, 'Mire Fishbone Blue Barrier Bypass', Lgcl).no_exit(), # considered out of logic create_door(player, 'Mire South Fish Blue Barrier', Lgcl), create_door(player, 'Mire Fishbone SE', Nrml).dir(So, 0xa1, Right, High).small_key().pos(0), create_door(player, 'Mire Spike Barrier NE', Nrml).dir(No, 0xb1, Right, High).small_key().pos(1), @@ -1116,16 +1116,18 @@ def create_doors(world, player): create_door(player, 'GT Double Switch NW', Nrml).dir(No, 0x9b, Left, High).pos(1).kill(), create_door(player, 'GT Double Switch Entry to Pot Corners Barrier - Orange', Lgcl), create_door(player, 'GT Double Switch Entry to Left Barrier - Orange', Lgcl), - create_door(player, 'GT Double Switch Entry to Ranged Switches', Lgcl), - create_door(player, 'GT Double Switch Entry Ranged Switches Exit', Lgcl), + create_door(player, 'GT Double Switch Entry to Ranged Switches', Lgcl), # considered out of logic + create_door(player, 'GT Double Switch Entry Ranged Switches Exit', Lgcl).no_exit(), # blocked here for reasons + create_door(player, 'GT Double Switch Left to Crystal', Lgcl), + create_door(player, 'GT Double Switch Left Crystal Exit', Lgcl), create_door(player, 'GT Double Switch Left to Entry Barrier - Orange', Lgcl), create_door(player, 'GT Double Switch Left to Pot Corners Bypass', Lgcl), create_door(player, 'GT Double Switch Left to Entry Bypass', Lgcl), - create_door(player, 'GT Double Switch Left to Exit Bypass', Lgcl), + create_door(player, 'GT Double Switch Left to Exit Bypass', Lgcl).no_exit(), # considered out of logic create_door(player, 'GT Double Switch Pot Corners to Entry Barrier - Orange', Lgcl), create_door(player, 'GT Double Switch Pot Corners to Exit Barrier - Blue', Lgcl), - create_door(player, 'GT Double Switch Pot Corners to Ranged Switches', Lgcl), - create_door(player, 'GT Double Switch Pot Corners Ranged Switches Exit', Lgcl), + create_door(player, 'GT Double Switch Pot Corners to Ranged Switches', Lgcl), # considered out of logic + create_door(player, 'GT Double Switch Pot Corners Ranged Switches Exit', Lgcl).no_exit(), # blocked here create_door(player, 'GT Double Switch Exit to Blue Barrier', Lgcl), create_door(player, 'GT Double Switch EN', Intr).dir(Ea, 0x9b, Top, High).small_key().pos(0), create_door(player, 'GT Spike Crystals WN', Intr).dir(We, 0x9b, Top, High).small_key().pos(0), @@ -1438,6 +1440,7 @@ def create_doors(world, player): world.get_door('GT Double Switch Entry to Pot Corners Barrier - Orange', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Entry to Left Barrier - Orange', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Entry Ranged Switches Exit', player).c_switch() + world.get_door('GT Double Switch Left Crystal Exit', player).c_switch() world.get_door('GT Double Switch Left to Entry Barrier - Orange', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Left to Entry Bypass', player).barrier(CrystalBarrier.Blue) world.get_door('GT Double Switch Left to Pot Corners Bypass', player).barrier(CrystalBarrier.Blue) diff --git a/Dungeons.py b/Dungeons.py index 0acf7852..d5297400 100644 --- a/Dungeons.py +++ b/Dungeons.py @@ -304,7 +304,8 @@ gt_regions = [ 'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', 'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room', - 'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', 'GT Double Switch Left', + 'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners', + 'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches', 'GT Double Switch Exit', 'GT Spike Crystal Left', 'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section', 'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot', diff --git a/Main.py b/Main.py index f7dee19f..7faef477 100644 --- a/Main.py +++ b/Main.py @@ -290,7 +290,9 @@ def main(args, seed=None, fish=None): rom_names.append((player, team, list(rom.name))) world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash) - 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]) + 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]) if args.jsonout: jsonout[f'patch_t{team}_p{player}'] = rom.patches diff --git a/Regions.py b/Regions.py index bbec1ca0..6fd64c33 100644 --- a/Regions.py +++ b/Regions.py @@ -876,7 +876,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'GT Double Switch Entry', 'Ganon\'s Tower', None, ['GT Double Switch NW', 'GT Double Switch Entry to Left Barrier - Orange', 'GT Double Switch Entry to Pot Corners Barrier - Orange', 'GT Double Switch Entry to Ranged Switches']), create_dungeon_region(player, 'GT Double Switch Entry - Ranged Switches', 'Ganon\'s Tower', None, ['GT Double Switch Entry Ranged Switches Exit']), - create_dungeon_region(player, 'GT Double Switch Left', 'Ganon\'s Tower', None, ['GT Double Switch Left to Entry Barrier - Orange', 'GT Double Switch Left to Entry Bypass', 'GT Double Switch Left to Pot Corners Bypass', 'GT Double Switch Left to Exit Bypass']), + create_dungeon_region(player, 'GT Double Switch Left', 'Ganon\'s Tower', None, ['GT Double Switch Left to Crystal', 'GT Double Switch Left to Entry Barrier - Orange', 'GT Double Switch Left to Entry Bypass', 'GT Double Switch Left to Pot Corners Bypass', 'GT Double Switch Left to Exit Bypass']), + create_dungeon_region(player, 'GT Double Switch Left - Crystal', 'Ganon\'s Tower', None, ['GT Double Switch Left Crystal Exit']), create_dungeon_region(player, 'GT Double Switch Pot Corners', 'Ganon\'s Tower', ['Ganons Tower - Double Switch Pot Key'], ['GT Double Switch Pot Corners to Entry Barrier - Orange', 'GT Double Switch Pot Corners to Exit Barrier - Blue', 'GT Double Switch Pot Corners to Ranged Switches']), create_dungeon_region(player, 'GT Double Switch Pot Corners - Ranged Switches', 'Ganon\'s Tower', None, ['GT Double Switch Pot Corners Ranged Switches Exit']), create_dungeon_region(player, 'GT Double Switch Exit', 'Ganon\'s Tower', None, ['GT Double Switch EN', 'GT Double Switch Exit to Blue Barrier']), @@ -997,7 +998,7 @@ def create_dungeon_regions(world, player): world.get_region('GT Crystal Conveyor Corner - Ranged Crystal', player).crystal_switch = True world.get_region('GT Hookshot South Platform', player).crystal_switch = True world.get_region('GT Hookshot South Entry - Ranged Crystal', player).crystal_switch = True - world.get_region('GT Double Switch Left', player).crystal_switch = True + world.get_region('GT Double Switch Left - Crystal', player).crystal_switch = True world.get_region('GT Double Switch Entry - Ranged Switches', player).crystal_switch = True world.get_region('GT Double Switch Pot Corners - Ranged Switches', player).crystal_switch = True world.get_region('GT Spike Crystal Left', player).crystal_switch = True diff --git a/Rom.py b/Rom.py index 01f930b3..c3ff380e 100644 --- a/Rom.py +++ b/Rom.py @@ -496,8 +496,6 @@ class Sprite(object): sprite_name = read_utf16le(stream) author_name = read_utf16le(stream) - # Ignoring the Author Rom name for the time being. - real_csum = sum(filedata) % 0x10000 if real_csum != csum or real_csum ^ 0xFFFF != icsum: logger.warning('ZSPR file has incorrect checksum. It may be corrupted.') @@ -1723,7 +1721,15 @@ def hud_format_text(text): return output[:32] -def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, ow_palettes, uw_palettes): +def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, + ow_palettes, uw_palettes, reduce_flashing): + if not os.path.exists("data/sprites/official/001.link.1.zspr"): + dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380], + rom.orig_buffer[0xdedf5:0xdedf9], "data/sprites/official/001.link.1.zspr", "Nintendo", "Link") + + # todo: implement a flag for msu resume delay + rom.write_bytes(0x18021D, [0, 0]) # default to off for now + if sprite and not isinstance(sprite, Sprite): sprite = Sprite(sprite) if os.path.isfile(sprite) else get_sprite_from_name(sprite) @@ -1780,6 +1786,32 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr if sprite is not None: write_sprite(rom, sprite) + # sprite author credits + padded_author = sprite.author_name if sprite is not None else "Nintendo" + padded_author = padded_author[:28] if len(padded_author) > 28 else padded_author + padded_author = padded_author.center(28).upper() + + def convert_char_to_credits(char): + char_map = { + " ": (0x9F, 0x9F), "0": (0x53, 0x79), "1": (0x54, 0x7A), "2": (0x55, 0x7B), "3": (0x56, 0x7C), + "4": (0x57, 0x7D), "5": (0x58, 0x7E), "6": (0x59, 0x7F), "7": (0x5A, 0x80), "8": (0x5B, 0x81), + "9": (0x5C, 0x82), "A": (0x5D, 0x83), "B": (0x5E, 0x84), "C": (0x5F, 0x85), "D": (0x60, 0x86), + "E": (0x61, 0x87), "F": (0x62, 0x88), "G": (0x63, 0x89), "H": (0x64, 0x8A), "I": (0x65, 0x8B), + "J": (0x66, 0x8C), "K": (0x67, 0x8D), "L": (0x68, 0x8E), "M": (0x69, 0x8F), "N": (0x6A, 0x90), + "O": (0x6B, 0x91), "P": (0x6C, 0x92), "Q": (0x6D, 0x93), "R": (0x6E, 0x94), "S": (0x6F, 0x95), + "T": (0x70, 0x96), "U": (0x71, 0x97), "V": (0x72, 0x98), "W": (0x73, 0x99), "X": (0x74, 0x9A), + "Y": (0x75, 0x9B), "Z": (0x76, 0x9C), "'": (0x77, 0x9d), ".": (0xA0, 0xC0), "/": (0xA2, 0xC2), + ":": (0xA3, 0xC3), "_": (0xA6, 0xC6)} + return char_map[char] if char in char_map else (0x9F, 0x9F) + + character_bytes = map(convert_char_to_credits, padded_author) + for i, pair in enumerate(character_bytes): + rom.write_byte(0x118002 + i, pair[0]) + rom.write_byte(0x118020 + i, pair[1]) + + if reduce_flashing: + rom.write_byte(0x18017f, 1) + default_ow_palettes(rom) if ow_palettes == 'random': randomize_ow_palettes(rom) @@ -1795,6 +1827,60 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr if isinstance(rom, LocalRom): rom.write_crc() +# .zspr file dumping logic copied with permission from SpriteSomething: +# https://github.com/Artheau/SpriteSomething/blob/master/source/meta/classes/spritelib.py#L443 (thanks miketrethewey!) +def dump_zspr(basesprite, basepalette, baseglove, outfilename, author_name, sprite_name): + palettes = basepalette + # Add glove data + palettes.extend(baseglove) + HEADER_STRING = b"ZSPR" + VERSION = 0x01 + SPRITE_TYPE = 0x01 # this format has "1" for the player sprite + RESERVED_BYTES = b'\x00\x00\x00\x00\x00\x00' + QUAD_BYTE_NULL_CHAR = b'\x00\x00\x00\x00' + DOUBLE_BYTE_NULL_CHAR = b'\x00\x00' + SINGLE_BYTE_NULL_CHAR = b'\x00' + + write_buffer = bytearray() + + write_buffer.extend(HEADER_STRING) + write_buffer.extend(struct.pack('B', VERSION)) # as_u8 + checksum_start = len(write_buffer) + write_buffer.extend(QUAD_BYTE_NULL_CHAR) # checksum + sprite_sheet_pointer = len(write_buffer) + write_buffer.extend(QUAD_BYTE_NULL_CHAR) + write_buffer.extend(struct.pack('