From 1630215a243f8ddc3e03d093a7c3965ef1522b51 Mon Sep 17 00:00:00 2001 From: LLCoolDave Date: Thu, 25 May 2017 12:09:50 +0200 Subject: [PATCH] Add Prize Pack shuffling and standard/open mode rom switches. Can now work off one base rom. --- Main.py | 151 ++--------------------------------- Rom.py | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Rules.py | 2 +- 3 files changed, 243 insertions(+), 144 deletions(-) create mode 100644 Rom.py diff --git a/Main.py b/Main.py index 1912c7bf..86994893 100644 --- a/Main.py +++ b/Main.py @@ -1,7 +1,7 @@ from BaseClasses import World, CollectionState -from Regions import create_regions, location_addresses, crystal_locations, dungeon_music_addresses -from EntranceShuffle import link_entrances, door_addresses, single_doors -from Text import string_to_alttp_text, text_addresses, altar_text +from Regions import create_regions +from EntranceShuffle import link_entrances +from Rom import patch_rom from Rules import set_rules from Dungeons import fill_dungeons from Items import * @@ -382,128 +382,6 @@ def print_location_spoiler(world): return 'Locations:\n\n' + '\n'.join(['%s: %s' % (location, location.item if location.item is not None else 'Nothing') for location in world.get_locations()]) + '\n\n' -def patch_rom(world, rom): - # patch items - for location in world.get_locations(): - if location.name == 'Ganon': - # cannot shuffle this yet - continue - - itemid = location.item.code if location.item is not None else 0x5A - - try: - # regular items - locationaddress = location_addresses[location.name] - write_byte(rom, locationaddress, itemid) - except KeyError: - # crystals - locationaddress = crystal_locations[location.name] - for address, value in zip(locationaddress, itemid): - write_byte(rom, address, value) - - # patch music - music_addresses = dungeon_music_addresses[location.name] - music = 0x11 if 'Pendant' in location.item.name else 0x16 - for music_address in music_addresses: - write_byte(rom, music_address, music) - - # patch entrances - for region in world.regions: - for exit in region.exits: - if exit.target is not None: - try: - # ugly fix for agahnim fix in simple dungeon shuffle mode - if world.agahnim_fix_required and exit.name == 'Dark Death Mountain Ledge (East)': - write_byte(rom, door_addresses[exit.name][0], exit.target) - continue - - # toDo consider aga tower fix - addresses = door_addresses[exit.name] - write_byte(rom, addresses[0], exit.target[0]) - write_byte(rom, addresses[1], exit.target[1]) - except KeyError: - # probably cave - - # ugly fix for agahnim fix in simple dungeon shuffle mode - if world.agahnim_fix_required and exit.name == 'Mimic Cave Mirror Spot': - write_byte(rom, single_doors[exit.name], exit.target[0]) - write_byte(rom, door_addresses['Dark Death Mountain Ledge (East)'][1], exit.target[1]) - continue - - addresses = single_doors[exit.name] - if not isinstance(addresses, tuple): - addresses = (addresses,) - for address in addresses: - write_byte(rom, address, exit.target) - - # patch medallion requirements - if world.required_medallions[0] == 'Bombos': - write_byte(rom, 0x180022, 0x00) # requirement - write_byte(rom, 0x4FF2, 0x31) # sprite - write_byte(rom, 0x50D1, 0x80) - write_byte(rom, 0x51B0, 0x00) - elif world.required_medallions[0] == 'Quake': - write_byte(rom, 0x180022, 0x02) # requirement - write_byte(rom, 0x4FF2, 0x31) # sprite - write_byte(rom, 0x50D1, 0x88) - write_byte(rom, 0x51B0, 0x00) - if world.required_medallions[1] == 'Bombos': - write_byte(rom, 0x180023, 0x00) # requirement - write_byte(rom, 0x5020, 0x31) # sprite - write_byte(rom, 0x50FF, 0x90) - write_byte(rom, 0x51DE, 0x00) - elif world.required_medallions[1] == 'Ether': - write_byte(rom, 0x180023, 0x01) # requirement - write_byte(rom, 0x5020, 0x31) # sprite - write_byte(rom, 0x50FF, 0x98) - write_byte(rom, 0x51DE, 0x00) - - if world.swamp_patch_required: - # patch swamp: Need to enable permanent drain of water as dam or swamp were moved - rom = rom.replace(bytearray([0xAF, 0xBB, 0xF2, 0x7E, 0x29, 0xDF, 0x8F, 0xBB, 0xF2, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) - rom = rom.replace(bytearray([0xAF, 0xFB, 0xF2, 0x7E, 0x29, 0xDF, 0x8F, 0xFB, 0xF2, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) - rom = rom.replace(bytearray([0xAF, 0x16, 0xF2, 0x7E, 0x29, 0x7F, 0x8F, 0x16, 0xF2, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) - rom = rom.replace(bytearray([0xAF, 0x51, 0xF0, 0x7E, 0x29, 0xFE, 0x8F, 0x51, 0xF0, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) - - # set correct flag for hera basement item - if world.get_location('[dungeon-L3-1F] Tower of Hera - Freestanding Key').item is not None and world.get_location('[dungeon-L3-1F] Tower of Hera - Freestanding Key').item.name == 'Small Key (Tower of Hera)': - write_byte(rom, 0x4E3BB, 0xE4) - else: - write_byte(rom, 0x4E3BB, 0xEB) - - # write strings - write_string_to_rom(rom, 'Ganon2', 'Did you find the silver arrows in Hyrule?') - write_string_to_rom(rom, 'Uncle', 'Good Luck!\nYou will need it.') - write_string_to_rom(rom, 'Triforce', 'Product has Hole in center. Bad seller, 0 out of 5.') - write_string_to_rom(rom, 'BombShop1', 'Big Bomb?\nI Uh … Never heard of that. Move along.') - write_string_to_rom(rom, 'BombShop2', 'Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!') - write_string_to_rom(rom, 'PyramidFairy', 'May I talk to you about our lord and savior, Ganon?') - write_string_to_rom(rom, 'Sahasrahla1', 'How Did you Find me?') - write_string_to_rom(rom, 'Sahasrahla2', 'You already got my item, idiot.') - write_string_to_rom(rom, 'Blind', 'I bet you expected a vision related pun?\n\nNot Today.\n Didn\'t see that coming, did you?') - write_string_to_rom(rom, 'Ganon1', '\n\n\n\n\n\n\n\n\nWhy are you reading an empty textbox?') - write_string_to_rom(rom, 'TavernMan', 'Did you know that talking to random NPCs wastes time in a race? I hope this information may be of use to you in the future.') - - # disable open door sprites when exiting caves - for i in range(0x85): - write_byte(rom, 0x15274 + i, 0x00) - - altaritem = world.get_location('Altar').item.name if world.get_location('Altar').item is not None else 'Nothing' - write_string_to_rom(rom, 'Altar', altar_text.get(altaritem, 'Unknown Item.')) - - return rom - - -def write_byte(rom, address, value): - rom[address] = value - - -def write_string_to_rom(rom, target, string): - address, maxbytes = text_addresses[target] - for i, byte in enumerate(string_to_alttp_text(string, maxbytes)): - write_byte(rom, address + i, byte) - - if __name__ == '__main__': parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') @@ -524,27 +402,14 @@ if __name__ == '__main__': 'Madness decouples entrances and exits from each other and shuffles them freely, only ensuring that no fake Light/Dark World happens and all locations are reachable.\n' 'Insanity is Madness without the world restrictions. Mirror and Pearl are provided early to ensure Filling algorithm works properly. Deal with Fake LW/DW at your discretion. Experimental.\n' 'The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla.') - parser.add_argument('--openrom', default='Open_Base_Rom.sfc', help='Path to a VT21 open normal difficulty rom to use as a base.') - parser.add_argument('--standardrom', default='Standard_Base_Rom.sfc', help='Path to a VT21 standard normal difficulty rom to use as a base.') + parser.add_argument('--rom', default='Base_Rom.sfc', help='Path to a VT21 standard normal difficulty rom to use as a base.') parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') parser.add_argument('--seed', help='Define seed number to generate.', type=int) parser.add_argument('--count', help='Use to batch generate multiple seeds with same settings. If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each time).', type=int) args = parser.parse_args() - # check if rom for patching is available - rom_to_use = None - expected_name = '' - if args.mode == 'open': - if os.path.isfile(args.openrom): - rom_to_use = args.openrom # ToDo check checksum or some such in future when common base rom is in use - expected_name = args.openrom - elif args.mode == 'standard': - if os.path.isfile(args.standardrom): - rom_to_use = args.standardrom # ToDo check checksum or some such in future when common base rom is in use - expected_name = args.standardrom - - if rom_to_use is None: - 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.' % expected_name) + if 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) exit(1) # set up logger @@ -554,7 +419,7 @@ if __name__ == '__main__': if args.count is not None: seed = args.seed for i in range(args.count): - main(seed=seed, logic=args.logic, mode=args.mode, goal=args.goal, difficulty=args.difficulty, algo=args.algorithm, shuffle=args.shuffle, base_rom=rom_to_use, spoiler=args.create_spoiler) + main(seed=seed, logic=args.logic, mode=args.mode, goal=args.goal, difficulty=args.difficulty, algo=args.algorithm, shuffle=args.shuffle, base_rom=args.rom, spoiler=args.create_spoiler) seed = random.randint(0, 999999999) else: - main(seed=args.seed, logic=args.logic, mode=args.mode, goal=args.goal, difficulty=args.difficulty, algo=args.algorithm, shuffle=args.shuffle, base_rom=rom_to_use, spoiler=args.create_spoiler) + main(seed=args.seed, logic=args.logic, mode=args.mode, goal=args.goal, difficulty=args.difficulty, algo=args.algorithm, shuffle=args.shuffle, base_rom=args.rom, spoiler=args.create_spoiler) diff --git a/Rom.py b/Rom.py new file mode 100644 index 00000000..eeff2030 --- /dev/null +++ b/Rom.py @@ -0,0 +1,234 @@ +from Regions import location_addresses, crystal_locations, dungeon_music_addresses +from EntranceShuffle import door_addresses, single_doors +from Text import string_to_alttp_text, text_addresses, altar_text +import random + + +def patch_rom(world, rom): + # patch items + for location in world.get_locations(): + if location.name == 'Ganon': + # cannot shuffle this yet + continue + + itemid = location.item.code if location.item is not None else 0x5A + + try: + # regular items + locationaddress = location_addresses[location.name] + write_byte(rom, locationaddress, itemid) + except KeyError: + # crystals + locationaddress = crystal_locations[location.name] + for address, value in zip(locationaddress, itemid): + write_byte(rom, address, value) + + # patch music + music_addresses = dungeon_music_addresses[location.name] + music = 0x11 if 'Pendant' in location.item.name else 0x16 + for music_address in music_addresses: + write_byte(rom, music_address, music) + + # patch entrances + for region in world.regions: + for exit in region.exits: + if exit.target is not None: + try: + # ugly fix for agahnim fix in simple dungeon shuffle mode + if world.agahnim_fix_required and exit.name == 'Dark Death Mountain Ledge (East)': + write_byte(rom, door_addresses[exit.name][0], exit.target) + continue + + addresses = door_addresses[exit.name] + write_byte(rom, addresses[0], exit.target[0]) + write_byte(rom, addresses[1], exit.target[1]) + except KeyError: + # probably cave + + # ugly fix for agahnim fix in simple dungeon shuffle mode + if world.agahnim_fix_required and exit.name == 'Mimic Cave Mirror Spot': + write_byte(rom, single_doors[exit.name], exit.target[0]) + write_byte(rom, door_addresses['Dark Death Mountain Ledge (East)'][1], exit.target[1]) + continue + + addresses = single_doors[exit.name] + if not isinstance(addresses, tuple): + addresses = (addresses,) + for address in addresses: + write_byte(rom, address, exit.target) + + # patch medallion requirements + if world.required_medallions[0] == 'Bombos': + write_byte(rom, 0x180022, 0x00) # requirement + write_byte(rom, 0x4FF2, 0x31) # sprite + write_byte(rom, 0x50D1, 0x80) + write_byte(rom, 0x51B0, 0x00) + elif world.required_medallions[0] == 'Quake': + write_byte(rom, 0x180022, 0x02) # requirement + write_byte(rom, 0x4FF2, 0x31) # sprite + write_byte(rom, 0x50D1, 0x88) + write_byte(rom, 0x51B0, 0x00) + if world.required_medallions[1] == 'Bombos': + write_byte(rom, 0x180023, 0x00) # requirement + write_byte(rom, 0x5020, 0x31) # sprite + write_byte(rom, 0x50FF, 0x90) + write_byte(rom, 0x51DE, 0x00) + elif world.required_medallions[1] == 'Ether': + write_byte(rom, 0x180023, 0x01) # requirement + write_byte(rom, 0x5020, 0x31) # sprite + write_byte(rom, 0x50FF, 0x98) + write_byte(rom, 0x51DE, 0x00) + + # set open mode: + if world.mode == 'open': + write_byte(rom, 0x180032, 0x01) # open mode + write_byte(rom, 0x180038, 0x00) # sewers light cone disable + write_byte(rom, 0x180039, 0x00) # light world light cone disable + write_byte(rom, 0x18003A, 0x00) # dark world light cone disable + + # disable sword sprite from uncle + write_bytes(rom, 0x6D263, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) + write_bytes(rom, 0x6D26B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) + write_bytes(rom, 0x6D293, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) + write_bytes(rom, 0x6D29B, [0x00, 0x00, 0xf7, 0xff, 0x00, 0x0E]) + write_bytes(rom, 0x6D2B3, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E]) + write_bytes(rom, 0x6D2BB, [0x00, 0x00, 0xf6, 0xff, 0x02, 0x0E]) + write_bytes(rom, 0x6D2E3, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E]) + write_bytes(rom, 0x6D2EB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E]) + write_bytes(rom, 0x6D31B, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) + write_bytes(rom, 0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) + else: + write_byte(rom, 0x180032, 0x00) # standard mode + write_byte(rom, 0x180038, 0x01) # sewers light cone enabled + write_byte(rom, 0x180039, 0x01) # light world light cone enabled + write_byte(rom, 0x18003A, 0x00) # dark world light cone disable + + # disable light world cane in minor glitches + if world.logic == 'minorglitches': + write_byte(rom, 0x180039, 0x00) # light world light cone disable + write_byte(rom, 0x18003A, 0x00) # dark world light cone disable + + # handle difficulty + if world.difficulty == 'normal': + # Spike Cave Damage + write_byte(rom, 0x180168, 0x08) + # Powdered Fairies Prize + write_byte(rom, 0x36DD0, 0xE3) # fairy + # potion heal amount + write_byte(rom, 0x180084, 0xA0) # full + # potion magic restore amount + write_byte(rom, 0x180085, 0x80) # full + elif world.difficulty == 'hard': + # Spike Cave Damage + write_byte(rom, 0x180168, 0x02) + # Powdered Fairies Prize + write_byte(rom, 0x36DD0, 0x79) # Bee + # potion heal amount + write_byte(rom, 0x180084, 0x08) # One Heart + # potion magic restore amount + write_byte(rom, 0x180085, 0x20) # Quarter Magic + + # set up game internal RNG seed + for i in range(1024): + write_byte(rom, 0x178000 + i, random.randint(0, 255)) + + # shuffle prize packs + prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF, + 0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8, + 0xD8, 0xE3, 0xE0, 0xDB, 0xDE, 0xD8, 0xDB, 0xE2, 0xD9, 0xDA, 0xDB, 0xD9, 0xDB, 0xD9, 0xDB] + random.shuffle(prizes) + + # write tree pull prizes + write_byte(rom, 0xEFBD4, prizes.pop()) + write_byte(rom, 0xEFBD5, prizes.pop()) + write_byte(rom, 0xEFBD6, prizes.pop()) + # in open mode with shuffled caves, cannot guarantee access to rupees or a shop. Make 4 kill tree pull single bombs always to give guaranteed access + if world.shuffle not in ['default', 'dungeonsfull', 'dungeonssimple']: + write_byte(rom, 0xEFBD6, 0xDC) + + # rupee crab prizes + write_byte(rom, 0x329C8, prizes.pop()) # first prize + write_byte(rom, 0x329C4, prizes.pop()) # final prize + + # stunned enemy prize + write_byte(rom, 0x37993, prizes.pop()) + + # saved fish prize + write_byte(rom, 0xE82CC, prizes.pop()) + + # fill enemy prize packs + write_bytes(rom, 0x37A78, prizes) + + # prize pack drop chances + if world.difficulty == 'normal': + droprates = [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01] # 50% + else: + droprates = [0x01, 0x02, 0x03, 0x03, 0x03, 0x04, 0x04] # 50%, 25%, 3* 12.5%, 2* 6.25% + random.shuffle(droprates) + write_bytes(rom, 0x37A62, droprates) + + # deal with sprize prize packs (ToDo: figure out what this ACTUALLY does Probably assigns sprites to drop classes + for i in range(243): + if rom[0x6B632 + i] & 0x0F != 0x00: + rom[0x6B632 + i] = (rom[0x6B632 + i] & 0xF0) | random.randint(1, 7) + + # set bonk prizes + bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, 0xE3, 0xE3, + 0xDA, 0x79, 0xAC, 0xAC, 0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xE3, 0x79, 0xDE, 0xE3, 0xAC, 0xDB, 0x79, 0xE3, 0xD8, 0xAC, 0x79, 0xE3, 0xDB, 0xDB, 0xE3, 0xE3, 0x79, 0xD8, 0xDD] + bonk_addresses = [0x4CF6C, 0x4CFBA, 0x4CFE0, 0x4CFFB, 0x4D018, 0x4D01B, 0x4D028, 0x4D03C, 0x4D059, 0x4D07A, 0x4D09E, 0x4D0A8, 0x4D0AB, 0x4D0AE, 0x4D0BE, 0x4D0DD, + 0x4D16A, 0x4D1E5, 0x4D1EE, 0x4D20B, 0x4CBBF, 0x4CBBF, 0x4CC17, 0x4CC1A, 0x4CC4A, 0x4CC4D, 0x4CC53, 0x4CC69, 0x4CC6F, 0x4CC7C, 0x4CCEF, 0x4CD51, + 0x4CDC0, 0x4CDC3, 0x4CDC6, 0x4CE37, 0x4D2DE, 0x4D32F, 0x4D355, 0x4D367, 0x4D384, 0x4D387, 0x4D397, 0x4D39E, 0x4D3AB, 0x4D3AE, 0x4D3D1, 0x4D3D7, + 0x4D3F8, 0x4D416, 0x4D420, 0x4D423, 0x4D42D, 0x4D449, 0x4D48C, 0x4D4D9, 0x4D4DC, 0x4D4E3, 0x4D504, 0x4D507, 0x4D55E, 0x4D56A] + random.shuffle(bonk_prizes) + for prize, address in zip(bonk_prizes, bonk_addresses): + write_byte(rom, address, prize) + + if world.swamp_patch_required: + # patch swamp: Need to enable permanent drain of water as dam or swamp were moved + rom = rom.replace(bytearray([0xAF, 0xBB, 0xF2, 0x7E, 0x29, 0xDF, 0x8F, 0xBB, 0xF2, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) + rom = rom.replace(bytearray([0xAF, 0xFB, 0xF2, 0x7E, 0x29, 0xDF, 0x8F, 0xFB, 0xF2, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) + rom = rom.replace(bytearray([0xAF, 0x16, 0xF2, 0x7E, 0x29, 0x7F, 0x8F, 0x16, 0xF2, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) + rom = rom.replace(bytearray([0xAF, 0x51, 0xF0, 0x7E, 0x29, 0xFE, 0x8F, 0x51, 0xF0, 0x7E]), bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) + + # set correct flag for hera basement item + if world.get_location('[dungeon-L3-1F] Tower of Hera - Freestanding Key').item is not None and world.get_location('[dungeon-L3-1F] Tower of Hera - Freestanding Key').item.name == 'Small Key (Tower of Hera)': + write_byte(rom, 0x4E3BB, 0xE4) + else: + write_byte(rom, 0x4E3BB, 0xEB) + + # write strings + write_string_to_rom(rom, 'Ganon2', 'Did you find the silver arrows in Hyrule?') + write_string_to_rom(rom, 'Uncle', 'Good Luck!\nYou will need it.') + write_string_to_rom(rom, 'Triforce', 'Product has Hole in center. Bad seller, 0 out of 5.') + write_string_to_rom(rom, 'BombShop1', 'Big Bomb?\nI Uh … Never heard of that. Move along.') + write_string_to_rom(rom, 'BombShop2', 'Bombs!\nBombs!\nBiggest!\nBestest!\nGreatest!\nBoomest!') + write_string_to_rom(rom, 'PyramidFairy', 'May I talk to you about our lord and savior, Ganon?') + write_string_to_rom(rom, 'Sahasrahla1', 'How Did you Find me?') + write_string_to_rom(rom, 'Sahasrahla2', 'You already got my item, idiot.') + write_string_to_rom(rom, 'Blind', 'I bet you expected a vision related pun?\n\nNot Today.\n Didn\'t see that coming, did you?') + write_string_to_rom(rom, 'Ganon1', '\n\n\n\n\n\n\n\n\nWhy are you reading an empty textbox?') + write_string_to_rom(rom, 'TavernMan', 'Did you know that talking to random NPCs wastes time in a race? I hope this information may be of use to you in the future.') + + # disable open door sprites when exiting caves + if world.shuffle not in ['default', 'dungeonssimple', 'dungeonsfull']: + for i in range(0x85): + write_byte(rom, 0x15274 + i, 0x00) + + altaritem = world.get_location('Altar').item.name if world.get_location('Altar').item is not None else 'Nothing' + write_string_to_rom(rom, 'Altar', altar_text.get(altaritem, 'Unknown Item.')) + + return rom + + +def write_byte(rom, address, value): + rom[address] = value + + +def write_bytes(rom, startaddress, values): + for i, value in enumerate(values): + write_byte(rom, startaddress + i, value) + + +def write_string_to_rom(rom, target, string): + address, maxbytes = text_addresses[target] + write_bytes(rom, address, string_to_alttp_text(string, maxbytes)) \ No newline at end of file diff --git a/Rules.py b/Rules.py index 4a90a47f..d86d2bf6 100644 --- a/Rules.py +++ b/Rules.py @@ -228,7 +228,7 @@ def global_rules(world): for location in ['[dungeon-D3-B1] Skull Woods - Big Chest']: forbid_item(world.get_location(location), 'Big Key (Skull Woods)') - set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) + set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword())) set_rule(world.get_location('[dungeon-D5-B5] Ice Palace - Big Chest'), lambda state: state.can_collect('Big Key (Ice Palace)')) set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.can_collect('Big Key (Ice Palace)') and state.can_collect('Small Key (Ice Palace)', 2)) set_rule(world.get_entrance('Ice Palace (East)'), lambda state: state.has('Hookshot') or (state.can_collect('Small Key (Ice Palace)', 1) and ((state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item is not None and state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item.name in ['Big Key (Ice Palace)']) or