diff --git a/MultiClient.py b/MultiClient.py index 0aa574a2..2170d217 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -13,6 +13,7 @@ import sys import typing import os import subprocess +import re import shutil from random import randrange @@ -95,6 +96,7 @@ class Context(): self.locations_scouted = set() self.items_received = [] self.items_missing = [] + self.items_checked = None self.locations_info = {} self.awaiting_rom = False self.rom = None @@ -802,6 +804,18 @@ async def server_autoreconnect(ctx: Context): ctx.server_task = asyncio.create_task(server_loop(ctx)) +missing_unknown = re.compile("Unknown Location ID: (?P\d+)") +def convert_unknown_missing(missing_items: list) -> list: + missing = [] + for location in missing_items: + match = missing_unknown.match(location) + if match: + missing.append(Regions.lookup_id_to_name.get(int(match['ID']), location)) + else: + missing.append(location) + return missing + + async def process_server_cmd(ctx: Context, cmd, args): if cmd == 'RoomInfo': logger.info('--------------------------------') @@ -879,11 +893,12 @@ async def process_server_cmd(ctx: Context, cmd, args): await ctx.send_msgs(msgs) if ctx.finished_game: await send_finished_game(ctx) - ctx.items_missing = args[2] if len(args) >= 3 else [] # Get the server side view of missing as of time of connecting. + ctx.items_missing = convert_unknown_missing(args[2] if len(args) >= 3 else []) # Get the server side view of missing as of time of connecting. + ctx.items_checked = convert_unknown_missing(args[3] if len(args) >= 4 else None) # This list is used to only send to the server what is reported as ACTUALLY Missing. # This also serves to allow an easy visual of what locations were already checked previously # when /missing is used for the client side view of what is missing. - if not ctx.items_missing: + if not ctx.items_missing and not ctx.items_checked: asyncio.create_task(ctx.send_msgs([['Say', '!missing']])) elif cmd == 'ReceivedItems': @@ -933,12 +948,16 @@ async def process_server_cmd(ctx: Context, cmd, args): elif cmd == 'Missing': if 'locations' in args: - locations = json.loads(args['locations']) + locations = convert_unknown_missing(json.loads(args['locations'])) if ctx.items_missing: for location in locations: logger.info(f'Missing: {location}') + ctx.items_missing = locations + if 'locations_checked' in args: + ctx.items_checked = convert_unknown_missing(json.loads(args['locations_checked'])) + logger.info(f'Missing {len(locations)}/{len(locations)+len(ctx.items_checked)} location checks') + else: logger.info(f'Found {len(locations)} missing location checks') - ctx.items_missing = [location for location in locations] elif cmd == 'Hint': hints = [Utils.Hint(*hint) for hint in args] @@ -1068,30 +1087,15 @@ class ClientCommandProcessor(CommandProcessor): """List all missing location checks, from your local game state""" count = 0 checked_count = 0 - for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]: + for location in Regions.lookup_name_to_id.keys(): if location not in self.ctx.locations_checked: - if location not in self.ctx.items_missing: - self.output('Checked: ' + location) - checked_count += 1 - else: + if location in self.ctx.items_missing: self.output('Missing: ' + location) - count += 1 - - key_drop_count = 0 - for location in [k for k, v in Regions.key_drop_data.items()]: - if location not in self.ctx.items_missing: - key_drop_count += 1 - - # No point on reporting on missing key drop locations if the server doesn't declare ANY of them missing. - if key_drop_count != len(Regions.key_drop_data.items()): - for location in [k for k, v in Regions.key_drop_data.items()]: - if location not in self.ctx.locations_checked: - if location not in self.ctx.items_missing: - self.output('Checked: ' + location) - key_drop_count += 1 - else: - self.output('Missing: ' + location) count += 1 + elif self.ctx.items_checked is None or location in self.ctx.items_checked: + self.output('Checked: ' + location) + count += 1 + checked_count += 1 if count: self.output(f"Found {count} missing location checks{f'. {checked_count} locations checks previously visited.' if checked_count else ''}") @@ -1163,7 +1167,17 @@ async def track_locations(ctx : Context, roomid, roomdata): def new_check(location): ctx.unsafe_locations_checked.add(location) - logging.info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked))) + + check = None + if ctx.items_checked is None: + check = f'New Check: {location} ({len(ctx.unsafe_locations_checked)}/{len(Regions.lookup_name_to_id)})' + else: + items_total = len(ctx.items_missing) + len(ctx.items_checked) + if location in ctx.items_missing or location in ctx.items_checked: + check = f'New Check: {location} ({len(ctx.unsafe_locations_checked)}/{items_total})' + + if check: + logger.info(check) ctx.ui_node.send_location_check(ctx, location) for location, (loc_roomid, loc_mask) in location_table_uw.items(): diff --git a/MultiServer.py b/MultiServer.py index 011fca9a..0f1c53bd 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -429,9 +429,10 @@ async def countdown(ctx: Context, timer): ctx.notify_all(f'[Server]: GO') ctx.countdown_timer = 0 -async def missing(ctx: Context, client: Client, locations: list): +async def missing(ctx: Context, client: Client, locations: list, checked_locations: list): await ctx.send_msgs(client, [['Missing', { - 'locations': json.dumps(locations) + 'locations': json.dumps(locations), + 'checked_locations': json.dumps(checked_locations) }]]) @@ -836,6 +837,7 @@ class ClientMessageProcessor(CommonCommandProcessor): """List all missing location checks from the server's perspective""" locations = get_missing_checks(self.ctx, self.client) + checked_locations = get_checked_checks(self.ctx, self.client) if len(locations) > 0: if self.client.version < [2, 3, 0]: @@ -844,7 +846,7 @@ class ClientMessageProcessor(CommonCommandProcessor): buffer += f'Missing: {location}\n' self.output(buffer + f"Found {len(locations)} missing location checks") else: - asyncio.create_task(missing(self.ctx, self.client, locations)) + asyncio.create_task(missing(self.ctx, self.client, locations, checked_locations)) else: self.output("No missing location checks found.") return True @@ -965,6 +967,13 @@ class ClientMessageProcessor(CommonCommandProcessor): return False +def get_checked_checks(ctx: Context, client: Client) -> list: + return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for + location_id, slot in ctx.locations if + slot == client.slot and + location_id in ctx.location_checks[client.team, client.slot]] + + def get_missing_checks(ctx: Context, client: Client) -> list: return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for location_id, slot in ctx.locations if @@ -1032,7 +1041,7 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args): client.tags = args.get('tags', Client.tags) reply = [['Connected', [(client.team, client.slot), [(p, ctx.get_aliased_name(t, p)) for (t, p), n in ctx.player_names.items() if - t == client.team], get_missing_checks(ctx, client)]]] + t == client.team], get_missing_checks(ctx, client), get_checked_checks(ctx, client)]]] items = get_received_items(ctx, client.team, client.slot) if items: reply.append(['ReceivedItems', (0, tuplize_received_items(items))])