From ffe5b4b1a1d806c09a1be91a283dd3a6cdbcaafa Mon Sep 17 00:00:00 2001 From: pepperpow Date: Tue, 15 Dec 2020 02:34:22 -0600 Subject: [PATCH] Fixes to clients/servers for shop checks --- BaseClasses.py | 21 --------------------- ItemPool.py | 2 +- Main.py | 1 + MultiClient.py | 25 ++++++++++++++++++++++++- MultiServer.py | 2 +- Regions.py | 16 +++++++++++++++- Rom.py | 22 ++++++++++++++++++++++ host.yaml | 8 ++++---- 8 files changed, 68 insertions(+), 29 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 5617fd85..8c4ca144 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -341,27 +341,6 @@ class World(object): if collect: self.state.collect(item, location.event, location) - # TODO: Prevents fast_filling certain items. Move this to a proper filter. - if location.parent_region.shop is not None and location.name != 'Potion Shop': # includes potion shop slots but not potion shop powder - slot_num = int(location.name[-1]) - 1 - my_item = location.parent_region.shop.inventory[slot_num] - if (my_item is not None and my_item['item'] == item.name) or 'Rupee' in item.name or ('Bee' in item.name and 'Trap' not in item.name): - # this will filter items that match the item in the shop or Rupees, or single bees - # really not a way for the player to know a renewable item from a player pool item - # bombs can be sitting on top of arrows or a potion refill, but dunno if that's a big deal - logging.debug('skipping item shop {}'.format(item.name)) - else: - if my_item is None: - location.parent_region.shop.add_inventory(slot_num, 'None', 0) - my_item = location.parent_region.shop.inventory[slot_num] - else: - my_item['replacement'] = my_item['item'] - my_item['replacement_price'] = my_item['price'] - my_item['item'] = item.name - my_item['price'] = self.random.randrange(1, 61) * 5 # can probably replace this with a price chart - my_item['max'] = 1 - my_item['player'] = item.player if item.player != location.player else 0 - logging.debug('Placed %s at %s', item, location) else: raise RuntimeError('Cannot assign item %s to location %s.' % (item, location)) diff --git a/ItemPool.py b/ItemPool.py index 132785a1..a1506e0c 100644 --- a/ItemPool.py +++ b/ItemPool.py @@ -464,7 +464,7 @@ 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) + loc = Location(player, "{} Shop Slot {}".format(shop.region.name, i+1), parent=shop.region) shop.region.locations.append(loc) world.dynamic_locations.append(loc) diff --git a/Main.py b/Main.py index e66e2181..3300635d 100644 --- a/Main.py +++ b/Main.py @@ -305,6 +305,7 @@ def main(args, seed=None): main_entrance = get_entrance_to_region(region) for location in region.locations: if type(location.address) == int: # skips events and crystals + if location.address >= 0x400000: continue if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name: er_hint_data[region.player][location.address] = main_entrance.name diff --git a/MultiClient.py b/MultiClient.py index dfd82629..88dbb352 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -157,6 +157,11 @@ SCOUT_LOCATION_ADDR = SAVEDATA_START + 0x4D7 # 1 byte SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte +SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes + + +location_shop_order = [ name for name, info in Regions.shop_table.items() ] # probably don't leave this here. This relies on python 3.6+ dictionary keys having defined order +location_shop_ids = set([info[0] for name, info in Regions.shop_table.items()]) location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10), "Blind's Hideout - Left": (0x11d, 0x20), @@ -1116,6 +1121,18 @@ async def track_locations(ctx : Context, roomid, roomdata): ctx.unsafe_locations_checked.add(location) ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked))) ctx.ui_node.send_location_check(ctx, location) + + try: + if roomid in location_shop_ids: + misc_data = await snes_read(ctx, SHOP_ADDR, len(location_shop_order)*3) + for cnt, b in enumerate(misc_data): + my_check = Regions.shop_table_by_location_id[0x400000 + cnt] + if int(b) > 0 and my_check not in ctx.unsafe_locations_checked: + new_check(my_check) + except Exception as e: + print(e) + ctx.ui_node.log_info(f"Exception: {e}") + for location, (loc_roomid, loc_mask) in location_table_uw.items(): try: @@ -1175,7 +1192,13 @@ async def track_locations(ctx : Context, roomid, roomdata): for location in ctx.unsafe_locations_checked: if (location in ctx.items_missing and location not in ctx.locations_checked) or ctx.send_unsafe: ctx.locations_checked.add(location) - new_locations.append(Regions.lookup_name_to_id[location]) + try: + my_id = Regions.lookup_name_to_id.get(location, Regions.shop_table_by_location.get(location, -1)) + new_locations.append(my_id) + except Exception as e: + print(e) + ctx.ui_node.log_info(f"Exception: {e}") + await ctx.send_msgs([['LocationChecks', new_locations]]) diff --git a/MultiServer.py b/MultiServer.py index a077c515..ac092167 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -950,7 +950,7 @@ def get_missing_checks(ctx: Context, client: Client) -> list: #for location_id in [k[0] for k, v in ctx.locations if k[1] == client.slot]: # if location_id not in ctx.location_checks[client.team, client.slot]: # locations.append(Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}')) - for location_id, location_name in Regions.lookup_id_to_name.items(): # cheat console is -1, keep in mind + for location_id, location_name in {**Regions.lookup_id_to_name, **Regions.shop_table_by_location_id}.items(): # cheat console is -1, keep in mind if location_id != -1 and location_id not in ctx.location_checks[client.team, client.slot] and (location_id, client.slot) in ctx.locations: locations.append(location_name) return locations diff --git a/Regions.py b/Regions.py index 11968766..d0ae80a7 100644 --- a/Regions.py +++ b/Regions.py @@ -396,7 +396,8 @@ def create_shops(world, player: int): if my_shop_slots.pop(): additional_item = world.random.choice(['Rupees (20)', 'Rupees (50)', 'Rupees (100)']) world.itempool.append(ItemFactory(additional_item, player)) - loc = Location(player, "{} Slot Item {}".format(shop.region.name, index+1), parent=shop.region) + slot_name = "{} Shop Slot {}".format(shop.region.name, index+1) + loc = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region) shop.region.locations.append(loc) world.dynamic_locations.append(loc) @@ -420,6 +421,17 @@ shop_table = { 'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)]) } +shop_table_by_location_id = {0x400000 + cnt: s for cnt, s in enumerate( [item for sublist in [ ["{} Shop Slot {}".format(name, num + 1) for num in range(3)] for name in shop_table ] for item in sublist])} +shop_table_by_location = {y:x for x,y in shop_table_by_location_id.items()} + +shop_generation_types = { + 'default': _basic_shop_defaults + [('Bombs (3)', 20), ('Green Potion', 90), ('Blue Potion', 190), ('Bee', 10), ('Single Arrow', 5)] + [('Red Shield', 500), ('Blue Shield', 50)], + 'potion': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)], + 'discount_potion': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], + 'bottle': [('Bee', 10)], + 'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)], +} + old_location_address_to_new_location_address = { 0x2eb18: 0x18001b, # Bottle Merchant 0x33d68: 0x18001a, # Purple Chest @@ -717,8 +729,10 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'), lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int} lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"} +lookup_id_to_name.update(shop_table_by_location_id) lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int} lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1} +lookup_name_to_id.update(shop_table_by_location) lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks', 1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks', diff --git a/Rom.py b/Rom.py index 6eaf6854..751c06b4 100644 --- a/Rom.py +++ b/Rom.py @@ -638,6 +638,28 @@ def patch_rom(world, rom, player, team, enemized): if location.address is None: continue + if 'Shop Slot' in location.name and location.parent_region.shop is not None: + slot_num = int(location.name[-1]) - 1 + my_item = location.parent_region.shop.inventory[slot_num] + item = location.item + if (my_item is not None and my_item['item'] == item.name) or 'Rupee' in item.name or (item.name in ['Bee']): + # this will filter items that match the item in the shop or Rupees, or single bees + # really not a way for the player to know a renewable item from a player pool item + # bombs can be sitting on top of arrows or a potion refill, but dunno if that's a big deal + logging.debug('skipping item shop {}'.format(item.name)) + else: + if my_item is None: + location.parent_region.shop.add_inventory(slot_num, 'None', 0) + my_item = location.parent_region.shop.inventory[slot_num] + else: + my_item['replacement'] = my_item['item'] + my_item['replacement_price'] = my_item['price'] + my_item['item'] = item.name + my_item['price'] = world.random.randrange(1, 61) * 5 # can probably replace this with a price chart + my_item['max'] = 1 + my_item['player'] = item.player if item.player != location.player else 0 + continue + if not location.crystal: if location.item is not None: # Keys in their native dungeon should use the orignal item code for keys diff --git a/host.yaml b/host.yaml index 33e749d0..cabacd4f 100644 --- a/host.yaml +++ b/host.yaml @@ -13,13 +13,13 @@ general_options: # Null means nothing, for the server this means to default the value # These overwrite command line arguments! server_options: - host: null + host: 0.0.0.0 port: 38281 password: null multidata: null savefile: null disable_save: false - loglevel: "info" + loglevel: "debug" # Allows for clients to log on and manage the server. If this is null, no remote administration is possible. server_password: null # Automatically forward the port that is used, then close that port after 24 hours @@ -28,9 +28,9 @@ server_options: disable_item_cheat: false # Client hint system # Points given to a player for each acquired item in their world - location_check_points: 1 + location_check_points: 50 # Point cost to receive a hint via !hint for players - hint_cost: 1000 # Set to 0 if you want free hints + hint_cost: 0 # Set to 0 if you want free hints # Forfeit modes # "disabled" -> clients can't forfeit, # "enabled" -> clients can always forfeit