From 09cae6e640e2cd0509c9ddb77ece4406a02b9dff Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 3 Feb 2021 10:49:29 -0700 Subject: [PATCH] Shopsanity multiworld and rupee progression balancing --- Fill.py | 156 +++++++++++++++++++++++++++++++++++++++++- ItemList.py | 17 +++-- Items.py | 30 ++++---- Main.py | 3 +- Regions.py | 7 ++ Rom.py | 4 +- data/base2current.bps | Bin 131582 -> 132284 bytes 7 files changed, 190 insertions(+), 27 deletions(-) diff --git a/Fill.py b/Fill.py index d7bbcd75..38a9d8f5 100644 --- a/Fill.py +++ b/Fill.py @@ -2,7 +2,8 @@ import random import logging from BaseClasses import CollectionState -from Regions import shop_to_location_table, retro_shops +from Items import ItemFactory +from Regions import shop_to_location_table class FillError(RuntimeError): @@ -382,8 +383,6 @@ def sell_potions(world, player): # potions are excluded from the cap fairy due to visual problem if shop.region.name in shop_to_location_table and shop.region.name != 'Capacity Upgrade': loc_choices += [world.get_location(loc, player) for loc in shop_to_location_table[shop.region.name]] - if world.retro[player] and shop.region.name in retro_shops: - loc_choices += [world.get_location(loc, player) for loc in retro_shops[shop.region.name]] locations = [loc for loc in loc_choices if not loc.item] for potion in ['Green Potion', 'Blue Potion', 'Red Potion']: location = random.choice(locations) @@ -502,3 +501,154 @@ def balance_multiworld_progression(world): break elif not sphere_locations: raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') + + +def balance_money_progression(world): + logger = logging.getLogger('') + state = CollectionState(world) + unchecked_locations = world.get_locations().copy() + wallet = {player: 0 for player in range(1, world.players+1)} + kiki_check = {player: False for player in range(1, world.players+1)} + kiki_paid = {player: False for player in range(1, world.players+1)} + rooms_visited = {player: set() for player in range(1, world.players+1)} + balance_locations = {player: set() for player in range(1, world.players+1)} + + pay_for_locations = {'Bottle Merchant': 100, 'Chest Game': 30, 'Digging Game': 80, + 'King Zora': 500, 'Blacksmith': 10} + rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, + 'Rupees (100)': 100, 'Rupees (300)': 300} + rupee_rooms = {'Eastern Rupees': 90, 'Mire Key Rupees': 45, 'Mire Shooter Rupees': 90, + 'TR Rupees': 270, 'PoD Dark Basement': 270} + acceptable_balancers = ['Bombs (3)', 'Arrows (10)', 'Bombs (10)'] + + def get_sphere_locations(sphere_state, locations): + sphere_state.sweep_for_events(key_only=True, locations=locations) + return [loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)] + + def interesting_item(location, item, world, player): + if item.advancement: + return True + if item.type is not None or item.name.startswith('Rupee'): + return True + if item.name in ['Progressive Armor', 'Blue Mail', 'Red Mail']: + return True + if world.retro[player] and (item.name in ['Single Arrow', 'Small Key (Universal)']): + return True + if location.name in pay_for_locations: + return True + return False + + def kiki_required(state, location): + path = state.path[location.parent_region] + if path: + while path[1]: + if path[0] == 'Palace of Darkness': + return True + path = path[1] + return False + + done = False + while not done: + sphere_costs = {player: 0 for player in range(1, world.players+1)} + locked_by_money = {player: set() for player in range(1, world.players+1)} + sphere_locations = get_sphere_locations(state, unchecked_locations) + checked_locations = [] + for player in range(1, world.players+1): + if not kiki_check[player]: + kiki_payable = state.prog_items[('Moon Pearl', player)] > 0 or world.mode[player] == 'inverted' + if kiki_payable and world.get_region('East Dark World', player) in state.reachable_regions[player]: + if not kiki_paid[player]: + kiki_check[player] = True + sphere_costs[player] += 110 + locked_by_money[player].add('Kiki') + for location in sphere_locations: + location_free, loc_player = True, location.player + if location.parent_region.name in shop_to_location_table and location.name != 'Potion Shop': + slot = shop_to_location_table[location.parent_region.name].index(location.name) + shop = location.parent_region.shop + shop_item = shop.inventory[slot] + sphere_costs[loc_player] += shop_item['price'] + location_free = False + locked_by_money[loc_player].add(location) + elif location.name in pay_for_locations: + sphere_costs[loc_player] += pay_for_locations[location.name] + location_free = False + locked_by_money[loc_player].add(location) + if kiki_check[loc_player] and not kiki_paid[loc_player] and kiki_required(state, location): + locked_by_money[loc_player].add(location) + location_free = False + if location_free: + state.collect(location.item, True, location) + unchecked_locations.remove(location) + if location.item.name.startswith('Rupee'): + wallet[location.item.player] += rupee_chart[location.item.name] + if location.item.name != 'Rupees (300)': + balance_locations[location.item.player].add(location) + if interesting_item(location, location.item, world, location.item.player): + checked_locations.append(location) + elif location.item.name in acceptable_balancers: + balance_locations[location.item.player].add(location) + for room, income in rupee_rooms.items(): + for player in range(1, world.players+1): + if room not in rooms_visited[player] and world.get_region(room, player) in state.reachable_regions[player]: + wallet[player] += income + rooms_visited[player].add(room) + if checked_locations: + if world.has_beaten_game(state): + done = True + continue + # else go to next sphere + else: + # check for solvent players + solvent = set() + insolvent = set() + for player in range(1, world.players+1): + if wallet[player] >= sphere_costs[player] > 0: + solvent.add(player) + if sphere_costs[player] > 0 and sphere_costs[player] > wallet[player]: + insolvent.add(player) + if len(solvent) == 0: + target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p]) + difference = sphere_costs[target_player]-wallet[target_player] + logger.debug(f'Money balancing needed: Player {target_player} short {difference}') + while difference > 0: + swap_targets = [x for x in unchecked_locations if x not in sphere_locations and x.item.name.startswith('Rupees') and x.item.player == target_player] + if len(swap_targets) == 0: + best_swap, best_value = None, 300 + else: + best_swap = max(swap_targets, key=lambda t: rupee_chart[t.item.name]) + best_value = rupee_chart[best_swap.item.name] + increase_targets = [x for x in balance_locations[target_player] if x.item.name in rupee_chart and rupee_chart[x.item.name] < best_value] + if len(increase_targets) == 0: + increase_targets = [x for x in balance_locations[target_player] if (rupee_chart[x.item.name] if x.item.name in rupee_chart else 0) < best_value] + if len(increase_targets) == 0: + raise Exception('No early sphere swaps for rupees - money grind would be required - bailing for now') + best_target = min(increase_targets, key=lambda t: rupee_chart[t.item.name] if t.item.name in rupee_chart else 0) + old_value = rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0 + if best_swap is None: + logger.debug(f'Upgrading {best_target.item.name} @ {best_target.name} for 300 Rupees') + best_target.item = ItemFactory('Rupees (300)', best_target.item.player) + best_target.item.location = best_target + else: + old_item = best_target.item + logger.debug(f'Swapping {best_target.item.name} @ {best_target.name} for {best_swap.item.name} @ {best_swap.name}') + best_target.item = best_swap.item + best_target.item.location = best_target + best_swap.item = old_item + best_swap.item.location = best_swap + increase = best_value - old_value + difference -= increase + wallet[target_player] += increase + solvent.add(player) + # apply solvency + for player in solvent: + wallet[player] -= sphere_costs[player] + sphere_costs[player] = 0 + for location in locked_by_money[player]: + if location == 'Kiki': + kiki_paid[player] = True + else: + state.collect(location.item, True, location) + unchecked_locations.remove(location) + if location.item.name.startswith('Rupee'): + wallet[location.item.player] += rupee_chart[location.item.name] diff --git a/ItemList.py b/ItemList.py index 1b062152..38fb83d8 100644 --- a/ItemList.py +++ b/ItemList.py @@ -7,7 +7,7 @@ from BaseClasses import Region, RegionType, Shop, ShopType, Location from Bosses import place_bosses from Dungeons import get_dungeon_item_pool from EntranceShuffle import connect_entrance -from Regions import shop_to_location_table, retro_shops +from Regions import shop_to_location_table, retro_shops, shop_table_by_location from Fill import FillError, fill_restrictive from Items import ItemFactory @@ -437,7 +437,10 @@ def create_dynamic_shop_locations(world, player): if item is None: continue if item['create_location']: - loc = Location(player, "{} Item {}".format(shop.region.name, i+1), parent=shop.region) + slot_name = "{} Item {}".format(shop.region.name, i+1) + address = shop_table_by_location[slot_name] if world.shopsanity[player] else None + loc = Location(player, slot_name, address=address, + parent=shop.region, hint_text='in an old-fashioned cave') shop.region.locations.append(loc) world.dynamic_locations.append(loc) @@ -483,16 +486,18 @@ def set_up_shops(world, player): removals = [next(item for item in world.itempool if item.name == 'Arrows (10)' and item.player == player)] red_pots = [item for item in world.itempool if item.name == 'Red Potion' and item.player == player][:5] shields_n_hearts = [item for item in world.itempool if item.name in ['Blue Shield', 'Small Heart'] and item.player == player] + removals.extend([item for item in world.itempool if item.name == 'Arrow Upgrade (+5)' and item.player == player]) removals.extend(red_pots) removals.extend(random.sample(shields_n_hearts, 5)) for remove in removals: world.itempool.remove(remove) - for i in range(6): + for i in range(6): # replace the Arrows (10) and randomly selected hearts/blue shield arrow_item = ItemFactory('Single Arrow', player) arrow_item.advancement = True world.itempool.append(arrow_item) - for i in range(5): + for i in range(5): # replace the red potions world.itempool.append(ItemFactory('Small Key (Universal)', player)) + world.itempool.append(ItemFactory('Rupees (50)', player)) # replaces the arrow upgrade # TODO: move hard+ mode changes for shields here, utilizing the new shops else: rss = world.get_region('Red Shield Shop', player).shop @@ -509,7 +514,7 @@ def set_up_shops(world, player): def customize_shops(world, player): - found_bomb_upgrade, found_arrow_upgrade = False, False + found_bomb_upgrade, found_arrow_upgrade = False, world.retro[player] possible_replacements = [] shops_to_customize = shop_to_location_table.copy() if world.retro[player]: @@ -584,7 +589,7 @@ def randomize_price(price): if price <= 10: return price else: - half_price = int(math.ceil(half_price / 10.0)) * 10 + half_price = int(math.ceil(half_price / 5.0)) * 5 max_price = price - half_price max_price //= 5 return random.randint(0, max_price) * 5 + half_price diff --git a/Items.py b/Items.py index 1ed849e1..d280d6e5 100644 --- a/Items.py +++ b/Items.py @@ -24,25 +24,25 @@ def ItemFactory(items, player): # Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text) item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'), - 'Progressive Bow': (True, False, None, 0x64, 100, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), - 'Progressive Bow (Alt)': (True, False, None, 0x65, 100, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), - 'Book of Mudora': (True, False, None, 0x1D, 100, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'), - 'Hammer': (True, False, None, 0x09, 200, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the hammer'), - 'Hookshot': (True, False, None, 0x0A, 200, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'), - 'Magic Mirror': (True, False, None, 0x1A, 200, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'), - 'Ocarina': (True, False, None, 0x14, 200, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'), - 'Pegasus Boots': (True, False, None, 0x4B, 200, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'), + 'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), + 'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'), + 'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'), + 'Hammer': (True, False, None, 0x09, 250, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the hammer'), + 'Hookshot': (True, False, None, 0x0A, 250, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'), + 'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'), + 'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'), + 'Pegasus Boots': (True, False, None, 0x4B, 250, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'), 'Power Glove': (True, False, None, 0x1B, 100, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the glove'), 'Cape': (True, False, None, 0x19, 50, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the cape'), 'Mushroom': (True, False, None, 0x29, 50, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the mushroom'), 'Shovel': (True, False, None, 0x13, 50, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the shovel'), - 'Lamp': (True, False, None, 0x12, 100, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the lamp'), + 'Lamp': (True, False, None, 0x12, 150, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the lamp'), 'Magic Powder': (True, False, None, 0x0D, 50, 'you can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the powder'), 'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the moon pearl'), - 'Cane of Somaria': (True, False, None, 0x15, 200, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the red cane'), - 'Fire Rod': (True, False, None, 0x07, 200, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the fire rod'), - 'Flippers': (True, False, None, 0x1E, 200, '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, 200, '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'), + 'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the red cane'), + 'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the fire rod'), + 'Flippers': (True, False, None, 0x1E, 250, '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, 250, '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, 200, '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'), 'Bombos': (True, False, None, 0x0F, 100, '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, 100, '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'), @@ -58,8 +58,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche 'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'), 'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the small sword'), 'Golden Sword': (True, False, 'Sword', 0x03, 200, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'), - 'Progressive Sword': (True, False, 'Sword', 0x5E, 100, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'), - 'Progressive Glove': (True, False, None, 0x61, 100, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'), + 'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a sword'), + 'Progressive Glove': (True, False, None, 0x61, 150, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'), 'Silver Arrows': (True, False, None, 0x58, 100, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the silver arrows'), 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], 999, None, None, None, None, None, None, None), 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], 999, None, None, None, None, None, None, None), diff --git a/Main.py b/Main.py index 3b780520..8c079b6d 100644 --- a/Main.py +++ b/Main.py @@ -22,7 +22,7 @@ from RoomData import create_rooms from Rules import set_rules from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items -from Fill import sell_potions, sell_keys, balance_multiworld_progression +from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names @@ -216,6 +216,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players+1): if world.shopsanity[player]: customize_shops(world, player) + balance_money_progression(world) outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed) diff --git a/Regions.py b/Regions.py index 4da654aa..6794eee7 100644 --- a/Regions.py +++ b/Regions.py @@ -902,6 +902,13 @@ def adjust_locations(world, player): dungeon.small_keys.append(key_item) elif key_item.bigkey: dungeon.big_key = key_item + if world.shopsanity[player]: + index = 0 + for shop, location_list in shop_to_location_table.items(): + for location in location_list: + world.get_location(location, player).address = 0x400000 + index + # player address? it is in the shop table + index += 1 # (type, room_id, shopkeeper, custom, locked, [items]) diff --git a/Rom.py b/Rom.py index 43ad26ff..62eded0b 100644 --- a/Rom.py +++ b/Rom.py @@ -27,7 +27,7 @@ from EntranceShuffle import door_addresses, exit_ids JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'bffd4e834049ca5f5295601436fc6009' +RANDOMIZERBASEHASH = '05128f2ed347479abb5f3149463bb06d' class JsonRom(object): @@ -535,7 +535,7 @@ def patch_rom(world, rom, player, team, enemized): itemid = location.item.code if location.item is not None else 0x5A - if location.address is None: + if location.address is None or (type(location.address) is int and location.address >= 0x400000): continue if not location.crystal: diff --git a/data/base2current.bps b/data/base2current.bps index a78021b41948b971adfb2f134e573dfd72d09b2f..81effac2f4f23bb5a75cbff86b2cefa2887cabce 100644 GIT binary patch delta 11922 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 a{|BES7VIE63Kwj4Jtn*KV*R7}&Ho4UnbpYv delta 11386 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