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/CHANGELOG.md b/CHANGELOG.md index c0017790..dc84da1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### 0.1.6.3 +- Fixed borked credits (and missing Sprite Author) when collection rate isn't 216 or if GTBK count isn't /22 +- Added OW Rando in credits +- Actually merged in DR v0.4.0.7 (with no thanks to GitHub) + ### 0.1.6.2 - Added Balanced option for Flute Shuffle - Fixed issue with Flute Spot to Mountain Entry softlocking @@ -28,7 +33,7 @@ ### 0.1.5.0 - Added OW Tile Swap setting - Fixed horizontal VRAM visual loading glitch on megatiles -- Merged DR v0.4.0.7 - Fast Credits / Reduced Flashing / Sprite Author in Credits +- ~~Merged DR v0.4.0.7 - Fast Credits / Reduced Flashing / Sprite Author in Credits~~ ### 0.1.4.3 - Merged DR v0.4.0.6 - TT Maiden Attic Hint / DR Entrance Floor Mat Mods / Hard/Expert Item Pool Fix diff --git a/CLI.py b/CLI.py index 164c1c64..ac0e4d22 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}) @@ -193,6 +194,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 0acd618b..207b3b37 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 30ec3955..596da920 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 4eacbcb3..f708d69b 100644 --- a/Main.py +++ b/Main.py @@ -315,7 +315,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/OverworldShuffle.py b/OverworldShuffle.py index 28861552..3360772f 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -4,7 +4,7 @@ from sortedcontainers import SortedList from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot from OWEdges import OWTileGroups, OWEdgeGroups, OpenStd, parallel_links, IsParallel -__version__ = '0.1.6.2-u' +__version__ = '0.1.6.3-u' def link_overworld(world, player): # setup mandatory connections diff --git a/README.md b/README.md index c8cf70b7..be7c291c 100644 --- a/README.md +++ b/README.md @@ -25,21 +25,24 @@ This is a very new mode of LTTPR so the tools and info is very limited. - There - In Mixed OW Tile Swap, Smith and Stumpy have issues when their tiles are swapped. Progression cannot be found on them when these tiles are swapped - Screens that loop on itself and also have free-standing items, the sprites are duplicated and can cause item duplication - When OWG are performed to enter mega-tile screens (large OW screens), there is a small chance that an incorrect VRAM reference value causes the map graphics to offset in increments of 16 pixels -- There may be an issue with progression being front-loaded in the seed in some scenarios, due to an unsophisticated shuffle algorithm that could make varying-sized parts of each world unreachable # Feedback and Bug Reports All feedback and dev conversation happens in the #ow-rando channel on the [ALTTP Randomizer discord](https://discordapp.com/invite/alttprandomizer). -# Installation from source +# Installation from Source -See these instructions. +Download the source code from the repository directly and put it in a folder of your choosing. -https://github.com/codemann8/ALttPDoorRandomizer/blob/OverworldShuffle/docs/BUILDING.md +You must have Python installed (version 3.6 - 3.9 supported) -When installing platform specific dependencies, don't forget to run the appropriate command from the bottom of the page! Those will install missing pip dependencies. +This program requires all python dependencies that are necessary to run Aerinon's Door Randomizer plus an additional 'sortedcontainers' package. Try running ```pip install sortedcontainers``` on the command line to install the dependency. -Running the MultiServer and MultiClient for multiworld should run resources/ci/common/local_install.py for those dependencies as well. +Alternatively, run ```resources/ci/common/local_install.py``` to install all the missing dependencies as well. + +See the following link if you have additional trouble: https://github.com/codemann8/ALttPDoorRandomizer/blob/OverworldShuffle/docs/BUILDING.md + +# Running the Program To use the CLI, run ```DungeonRandomizer.py```. 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 a266dc1b..2737eb3d 100644 --- a/Rom.py +++ b/Rom.py @@ -28,7 +28,7 @@ from OverworldShuffle import default_flute_connections, flute_data JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '35f262588baff34e2e2bf523a45a2703' +RANDOMIZERBASEHASH = '5cf6e7c96f0b7775e2936ef1b7b3be66' class JsonRom(object): @@ -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.') @@ -892,7 +890,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): write_int16(rom, 0x187010, credits_total) # dynamic credits if credits_total != 216: # collection rate address: - cr_address = 0x2391BE + cr_address = 0x239214 cr_pc = cr_address - 0x120000 # convert to pc mid_top, mid_bot = credits_digit((credits_total // 10) % 10) last_top, last_bot = credits_digit(credits_total % 10) @@ -911,7 +909,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): total += count_locations_exclude_logic(region.locations, gt_logic) # rom.write_byte(0x187012, total) # dynamic credits # gt big key address: - gtbk_address = 0x2390E0 + gtbk_address = 0x23911C gtbk_pc = gtbk_address - 0x120000 # convert to pc mid_top, mid_bot = credits_digit(total // 10) last_top, last_bot = credits_digit(total % 10) @@ -1769,7 +1767,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") and rom.orig_buffer: + 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) @@ -1826,6 +1832,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) @@ -1841,6 +1873,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('