From eb7ca4fdf975e95e9c23788c916735edd18abf5f Mon Sep 17 00:00:00 2001 From: Bonta-kun <40473493+Bonta0@users.noreply.github.com> Date: Mon, 6 Jan 2020 19:13:42 +0100 Subject: [PATCH] Implement --startinventory --- EntranceRandomizer.py | 3 +- ItemList.py | 58 ++++++--------- Items.py | 2 +- Main.py | 6 ++ Rom.py | 164 ++++++++++++++++++++++++++++++++++++++---- 5 files changed, 180 insertions(+), 53 deletions(-) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 204fb40b..c6f03721 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -216,6 +216,7 @@ def parse_arguments(argv, no_defaults=False): Keys are universal, shooting arrows costs rupees, and a few other little things make this more like Zelda-1. ''', action='store_true') + parser.add_argument('--startinventory', default=defval(''), help='Specifies a list of items that will be in your starting inventory (separated by commas)') parser.add_argument('--custom', default=defval(False), help='Not supported.') parser.add_argument('--customitemarray', default=defval(False), help='Not supported.') parser.add_argument('--accessibility', default=defval('items'), const='items', nargs='?', choices=['items', 'locations', 'none'], help='''\ @@ -284,7 +285,7 @@ def parse_arguments(argv, no_defaults=False): for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', - 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', + 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'retro', 'accessibility', 'hints', 'shufflepalette', 'shufflepots', 'beemizer', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage']: value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) diff --git a/ItemList.py b/ItemList.py index 19eb83e3..73e40043 100644 --- a/ItemList.py +++ b/ItemList.py @@ -51,8 +51,8 @@ difficulties = { progressivearmor = ['Progressive Armor'] * 2, basicarmor = ['Blue Mail', 'Red Mail'], swordless = ['Rupees (20)'] * 4, - progressivesword = ['Progressive Sword'] * 3, - basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'], + progressivesword = ['Progressive Sword'] * 4, + basicsword = ['Fighter Sword', 'Master Sword', 'Tempered Sword', 'Golden Sword'], basicbow = ['Bow', 'Silver Arrows'], timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, @@ -78,8 +78,8 @@ difficulties = { progressivearmor = ['Progressive Armor'] * 2, basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, - progressivesword = ['Progressive Sword'] * 3, - basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'], + progressivesword = ['Progressive Sword'] * 4, + basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword', 'Tempered Sword'], basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 25, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, @@ -105,8 +105,8 @@ difficulties = { progressivearmor = ['Progressive Armor'] * 2, # neither will count basicarmor = ['Progressive Armor'] * 2, # neither will count swordless = ['Rupees (20)'] * 4, - progressivesword = ['Progressive Sword'] * 3, - basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'], + progressivesword = ['Progressive Sword'] * 4, + basicsword = ['Fighter Sword', 'Fighter Sword', 'Master Sword', 'Master Sword'], basicbow = ['Bow'] * 2, timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, @@ -444,34 +444,17 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r else: pool.extend(diff.basicarmor) - if swords != 'swordless': - if want_progressives(): - pool.extend(['Progressive Bow'] * 2) - else: - pool.extend(diff.basicbow) + if want_progressives(): + pool.extend(['Progressive Bow'] * 2) + elif swords != 'swordless': + pool.extend(diff.basicbow) + else: + pool.extend(['Bow', 'Silver Arrows']) if swords == 'swordless': pool.extend(diff.swordless) - if want_progressives(): - pool.extend(['Progressive Bow'] * 2) - else: - pool.extend(['Bow', 'Silver Arrows']) - elif swords == 'assured': - precollected_items.append('Fighter Sword') - if want_progressives(): - pool.extend(diff.progressivesword) - pool.extend(['Rupees (100)']) - else: - pool.extend(diff.basicsword) - pool.extend(['Rupees (100)']) elif swords == 'vanilla': - swords_to_use = [] - if want_progressives(): - swords_to_use.extend(diff.progressivesword) - swords_to_use.extend(['Progressive Sword']) - else: - swords_to_use.extend(diff.basicsword) - swords_to_use.extend(['Fighter Sword']) + swords_to_use = diff.progressivesword.copy() if want_progressives() else diff.basicsword.copy() random.shuffle(swords_to_use) place_item('Link\'s Uncle', swords_to_use.pop()) @@ -482,12 +465,15 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r else: place_item('Master Sword Pedestal', 'Triforce') else: - if want_progressives(): - pool.extend(diff.progressivesword) - pool.extend(['Progressive Sword']) - else: - pool.extend(diff.basicsword) - pool.extend(['Fighter Sword']) + pool.extend(diff.progressivesword if want_progressives() else diff.basicsword) + if swords == 'assured': + if want_progressives(): + precollected_items.append('Progressive Sword') + pool.remove('Progressive Sword') + else: + precollected_items.append('Fighter Sword') + pool.remove('Fighter Sword') + pool.extend(['Rupees (50)']) extraitems = total_items_to_place - len(pool) - len(placed_items) diff --git a/Items.py b/Items.py index d134fdd4..f2d4e910 100644 --- a/Items.py +++ b/Items.py @@ -44,8 +44,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla 'Flippers': (True, False, None, 0x1E, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the flippers'), 'Ice Rod': (True, False, None, 0x08, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the ice rod'), 'Titans Mitts': (True, False, None, 0x1C, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the mitts'), - 'Ether': (True, False, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'), 'Bombos': (True, False, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'), + 'Ether': (True, False, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'), 'Quake': (True, False, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'), 'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a Bottle'), 'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'), diff --git a/Main.py b/Main.py index 31b28244..8e7ccfb4 100644 --- a/Main.py +++ b/Main.py @@ -9,6 +9,7 @@ import time import zlib from BaseClasses import World, CollectionState, Item, Region, Location, Shop +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 @@ -64,6 +65,11 @@ def main(args, seed=None): if world.mode[player] == 'standard' and world.enemy_shuffle[player] != 'none': world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it + for tok in filter(None, args.startinventory[player].split(',')): + item = ItemFactory(tok.strip(), player) + if item: + world.push_precollected(item) + if world.mode[player] != 'inverted': create_regions(world, player) else: diff --git a/Rom.py b/Rom.py index aa4c150a..adb462b8 100644 --- a/Rom.py +++ b/Rom.py @@ -9,13 +9,13 @@ import struct import sys import subprocess -from BaseClasses import ShopType, Region, Location, Item +from BaseClasses import CollectionState, ShopType, Region, Location from Dungeons import dungeon_music_addresses from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc -from Items import ItemFactory, item_table +from Items import ItemFactory from EntranceShuffle import door_addresses @@ -895,24 +895,158 @@ def patch_rom(world, player, rom, enemized): rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00) rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles - rom.write_byte(0x180034, 0x0A) # starting max bombs - rom.write_byte(0x180035, 30) # starting max arrows - for x in range(0x183000, 0x18304F): - rom.write_byte(x, 0) # Zero the initial equipment array - rom.write_byte(0x18302C, 0x18) # starting max health - rom.write_byte(0x18302D, 0x18) # starting current health - rom.write_byte(0x183039, 0x68) # starting abilities, bit array - + + # Starting equipment + equip = [0] * (0x340 + 0x4F) + equip[0x36C] = 0x18 + equip[0x36D] = 0x18 + equip[0x379] = 0x68 + starting_max_bombs = 10 + starting_max_arrows = 30 + + startingstate = CollectionState(world) + + if startingstate.has('Bow', player): + equip[0x340] = 1 + equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases + if not world.retro[player]: + equip[0x38E] |= 0x80 + if startingstate.has('Silver Arrows', player): + equip[0x38E] |= 0x40 + + if startingstate.has('Titans Mitts', player): + equip[0x354] = 2 + elif startingstate.has('Power Glove', player): + equip[0x354] = 1 + + if startingstate.has('Golden Sword', player): + equip[0x359] = 4 + elif startingstate.has('Tempered Sword', player): + equip[0x359] = 3 + elif startingstate.has('Master Sword', player): + equip[0x359] = 2 + elif startingstate.has('Fighter Sword', player): + equip[0x359] = 1 + + if startingstate.has('Mirror Shield', player): + equip[0x35A] = 3 + elif startingstate.has('Red Shield', player): + equip[0x35A] = 2 + elif startingstate.has('Blue Shield', player): + equip[0x35A] = 1 + + if startingstate.has('Red Mail', player): + equip[0x35B] = 2 + elif startingstate.has('Blue Mail', player): + equip[0x35B] = 1 + + if startingstate.has('Magic Upgrade (1/4)', player): + equip[0x37B] = 2 + equip[0x36E] = 0x80 + elif startingstate.has('Magic Upgrade (1/2)', player): + equip[0x37B] = 1 + equip[0x36E] = 0x80 + for item in world.precollected_items: if item.player != player: continue - if item.name == 'Fighter Sword': - rom.write_byte(0x183000+0x19, 0x01) - rom.write_byte(0x0271A6+0x19, 0x01) - rom.write_byte(0x180043, 0x01) # special starting sword byte + if item.name in ['Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)', + 'Titans Mitts', 'Power Glove', 'Progressive Glove', + 'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword', + 'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield', + 'Red Mail', 'Blue Mail', 'Progressive Armor', + 'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']: + continue + + set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2), + 'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1), + 'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)} + or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02), + 'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20), + 'Crystal 5': (0x37A, 0x04), 'Crystal 6': (0x37A, 0x01), 'Crystal 7': (0x37A, 0x08), + 'Big Key (Eastern Palace)': (0x367, 0x20), 'Compass (Eastern Palace)': (0x365, 0x20), 'Map (Eastern Palace)': (0x369, 0x20), + 'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10), + 'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20), + 'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0), + 'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02), + 'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10), + 'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80), + 'Big Key (Swamp Palace)': (0x367, 0x04), 'Compass (Swamp Palace)': (0x365, 0x04), 'Map (Swamp Palace)': (0x369, 0x04), + 'Big Key (Ice Palace)': (0x366, 0x40), 'Compass (Ice Palace)': (0x364, 0x40), 'Map (Ice Palace)': (0x368, 0x40), + 'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01), + 'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08), + 'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)} + set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04), + 'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01), + 'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10), + 'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)} + keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F], + 'Small Key (Tower of Hera)': [0x386], + 'Small Key (Agahnims Tower)': [0x380], 'Small Key (Palace of Darkness)': [0x382], + 'Small Key (Thieves Town)': [0x387], + 'Small Key (Skull Woods)': [0x384], 'Small Key (Swamp Palace)': [0x381], + 'Small Key (Ice Palace)': [0x385], + 'Small Key (Misery Mire)': [0x383], 'Small Key (Turtle Rock)': [0x388], + 'Small Key (Ganons Tower)': [0x389], + 'Small Key (Universal)': [0x38B], 'Small Key (Escape)': [0x37C, 0x37D]} + bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5, + 'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8} + rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300} + bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10} + arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10} + bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10} + arrows = {'Single Arrow': 1, 'Arrows (10)': 10} + + if item.name in set_table: + equip[set_table[item.name][0]] = set_table[item.name][1] + elif item.name in or_table: + equip[or_table[item.name][0]] |= or_table[item.name][1] + elif item.name in set_or_table: + equip[set_or_table[item.name][0]] = set_or_table[item.name][1] + equip[set_or_table[item.name][2]] |= set_or_table[item.name][3] + elif item.name in keys: + for address in keys[item.name]: + equip[address] = min(equip[address] + 1, 99) + elif item.name in bottles: + if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit: + equip[0x35C + equip[0x34F]] = bottles[item.name] + equip[0x34F] += 1 + elif item.name in rupees: + equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False)) + equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False)) + elif item.name in bomb_caps: + starting_max_bombs = min(starting_max_bombs + bomb_caps[item.name], 50) + elif item.name in arrow_caps: + starting_max_arrows = min(starting_max_arrows + arrow_caps[item.name], 70) + elif item.name in bombs: + equip[0x343] += bombs[item.name] + elif item.name in arrows: + if world.retro[player]: + equip[0x38E] |= 0x80 + equip[0x377] = 1 + else: + equip[0x377] += arrows[item.name] + elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']: + if item.name == 'Piece of Heart': + equip[0x36B] = (equip[0x36B] + 1) % 4 + if item.name != 'Piece of Heart' or equip[0x36B] == 0: + equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0) + equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0) else: - raise RuntimeError("Unsupported pre-collected item: {}".format(item)) + raise RuntimeError(f'Unsupported item in starting equipment: {item.name}') + + equip[0x343] = min(equip[0x343], starting_max_bombs) + rom.write_byte(0x180034, starting_max_bombs) + equip[0x377] = min(equip[0x377], starting_max_arrows) + rom.write_byte(0x180035, starting_max_arrows) + rom.write_bytes(0x180046, equip[0x360:0x362]) + if equip[0x359]: + rom.write_byte(0x180043, equip[0x359]) + + assert equip[:0x340] == [0] * 0x340 + rom.write_bytes(0x183000, equip[0x340:]) + rom.write_bytes(0x271A6, equip[0x340:0x340+60]) rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier