Fixes to clients/servers for shop checks

This commit is contained in:
pepperpow 2020-12-15 02:34:22 -06:00
parent 360fcbea9e
commit ffe5b4b1a1
8 changed files with 68 additions and 29 deletions

View File

@ -341,27 +341,6 @@ class World(object):
if collect: if collect:
self.state.collect(item, location.event, location) 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) logging.debug('Placed %s at %s', item, location)
else: else:
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location)) raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))

View File

@ -464,7 +464,7 @@ def create_dynamic_shop_locations(world, player):
if item is None: if item is None:
continue continue
if item['create_location']: 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) shop.region.locations.append(loc)
world.dynamic_locations.append(loc) world.dynamic_locations.append(loc)

View File

@ -305,6 +305,7 @@ def main(args, seed=None):
main_entrance = get_entrance_to_region(region) main_entrance = get_entrance_to_region(region)
for location in region.locations: for location in region.locations:
if type(location.address) == int: # skips events and crystals 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: if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
er_hint_data[region.player][location.address] = main_entrance.name er_hint_data[region.player][location.address] = main_entrance.name

View File

@ -157,6 +157,11 @@ SCOUT_LOCATION_ADDR = SAVEDATA_START + 0x4D7 # 1 byte
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 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), location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
"Blind's Hideout - Left": (0x11d, 0x20), "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.unsafe_locations_checked.add(location)
ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked))) ctx.ui_node.log_info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked)))
ctx.ui_node.send_location_check(ctx, location) 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(): for location, (loc_roomid, loc_mask) in location_table_uw.items():
try: try:
@ -1175,7 +1192,13 @@ async def track_locations(ctx : Context, roomid, roomdata):
for location in ctx.unsafe_locations_checked: for location in ctx.unsafe_locations_checked:
if (location in ctx.items_missing and location not in ctx.locations_checked) or ctx.send_unsafe: if (location in ctx.items_missing and location not in ctx.locations_checked) or ctx.send_unsafe:
ctx.locations_checked.add(location) 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]]) await ctx.send_msgs([['LocationChecks', new_locations]])

View File

@ -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]: #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]: # 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}')) # 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: 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) locations.append(location_name)
return locations return locations

View File

@ -396,7 +396,8 @@ def create_shops(world, player: int):
if my_shop_slots.pop(): if my_shop_slots.pop():
additional_item = world.random.choice(['Rupees (20)', 'Rupees (50)', 'Rupees (100)']) additional_item = world.random.choice(['Rupees (20)', 'Rupees (50)', 'Rupees (100)'])
world.itempool.append(ItemFactory(additional_item, player)) 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) shop.region.locations.append(loc)
world.dynamic_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)]) '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 = { old_location_address_to_new_location_address = {
0x2eb18: 0x18001b, # Bottle Merchant 0x2eb18: 0x18001b, # Bottle Merchant
0x33d68: 0x18001a, # Purple Chest 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 = {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 = {**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 = {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 = {**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', 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', 1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',

22
Rom.py
View File

@ -638,6 +638,28 @@ def patch_rom(world, rom, player, team, enemized):
if location.address is None: if location.address is None:
continue 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 not location.crystal:
if location.item is not None: if location.item is not None:
# Keys in their native dungeon should use the orignal item code for keys # Keys in their native dungeon should use the orignal item code for keys

View File

@ -13,13 +13,13 @@ general_options:
# Null means nothing, for the server this means to default the value # Null means nothing, for the server this means to default the value
# These overwrite command line arguments! # These overwrite command line arguments!
server_options: server_options:
host: null host: 0.0.0.0
port: 38281 port: 38281
password: null password: null
multidata: null multidata: null
savefile: null savefile: null
disable_save: false 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. # Allows for clients to log on and manage the server. If this is null, no remote administration is possible.
server_password: null server_password: null
# Automatically forward the port that is used, then close that port after 24 hours # Automatically forward the port that is used, then close that port after 24 hours
@ -28,9 +28,9 @@ server_options:
disable_item_cheat: false disable_item_cheat: false
# Client hint system # Client hint system
# Points given to a player for each acquired item in their world # 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 # 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 # Forfeit modes
# "disabled" -> clients can't forfeit, # "disabled" -> clients can't forfeit,
# "enabled" -> clients can always forfeit # "enabled" -> clients can always forfeit