diff --git a/MultiClient.py b/MultiClient.py index 00c7e3b5..e4bf3a03 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -5,8 +5,6 @@ import logging import typing import urllib.parse import atexit -import sys -import os exit_func = atexit.register(input, "Press enter to close.") @@ -31,7 +29,7 @@ class ReceivedItem(typing.NamedTuple): player: int class Context: - def __init__(self, snes_address, server_address, password): + def __init__(self, snes_address, server_address, password, found_items): self.snes_address = snes_address self.server_address = server_address @@ -64,6 +62,7 @@ class Context: self.awaiting_rom = False self.rom = None self.auth = None + self.found_items = found_items color_codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, @@ -719,6 +718,12 @@ async def process_server_cmd(ctx : Context, cmd, args): logging.info( '%s sent %s to %s (%s)' % (player_sent, item, player_recvd, get_location_name_from_address(location))) + elif cmd == 'ItemFound': + found = ReceivedItem(*args) + item = color(get_item_name_from_id(found.item), 'cyan' if found.player != ctx.slot else 'green') + player_sent = color(ctx.player_names[found.player], 'yellow' if found.player != ctx.slot else 'magenta') + logging.info('%s found %s (%s)' % (player_sent, item, get_location_name_from_address(found.location))) + elif cmd == 'Hint': hints = [Utils.Hint(*hint) for hint in args] for hint in hints: @@ -733,6 +738,11 @@ async def process_server_cmd(ctx : Context, cmd, args): elif cmd == 'Print': logging.info(args) +def get_tags(ctx: Context): + tags = ['Berserker'] + if ctx.found_items: + tags.append('FoundItems') + return tags async def server_auth(ctx: Context, password_requested): if password_requested and not ctx.password: @@ -745,7 +755,7 @@ async def server_auth(ctx: Context, password_requested): ctx.awaiting_rom = False ctx.auth = ctx.rom.copy() await send_msgs(ctx.socket, [['Connect', { - 'password': ctx.password, 'rom': ctx.auth, 'version': [1, 2, 0], 'tags': ['Berserker'] + 'password': ctx.password, 'rom': ctx.auth, 'version': [1, 2, 0], 'tags': get_tags(ctx) }]]) async def console_input(ctx : Context): @@ -819,6 +829,14 @@ async def console_loop(ctx : Context): if location not in ctx.locations_checked: logging.info('Missing: ' + location) + elif precommand == "show_items": + if len(command) > 1: + ctx.found_items = command[1].lower() in {"1", "true", "on"} + else: + ctx.found_items = not ctx.found_items + logging.info(f"Set showing team items to {ctx.found_items}") + asyncio.create_task(send_msgs(ctx.socket, [['UpdateTags', get_tags(ctx)]])) + elif precommand == "license": with open("LICENSE") as f: logging.info(f.read()) @@ -970,6 +988,7 @@ async def main(): parser.add_argument('--connect', default=None, help='Address of the multiworld host.') parser.add_argument('--password', default=None, help='Password of the multiworld host.') parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical']) + parser.add_argument('--founditems', default=False, action='store_true', help='Show items found by other players for themselves.') args = parser.parse_args() logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO)) @@ -981,7 +1000,7 @@ async def main(): logging.info(f"Wrote rom file to {romfile}") asyncio.create_task(run_game(romfile)) - ctx = Context(args.snes, args.connect, args.password) + ctx = Context(args.snes, args.connect, args.password, args.founditems) input_task = asyncio.create_task(console_loop(ctx)) diff --git a/MultiServer.py b/MultiServer.py index 0c47449c..5e8670b5 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -6,7 +6,6 @@ import logging import zlib import collections import typing -import os import ModuleUpdate @@ -39,6 +38,10 @@ class Client: self.tags = [] self.version = [0, 0, 0] + @property + def wants_item_notification(self): + return self.auth and "FoundItems" in self.tags + class Context: def __init__(self, host: str, port: int, password: str, location_check_points: int, hint_cost: int, @@ -135,7 +138,6 @@ def notify_hints(ctx: Context, team: int, hints: typing.List[Utils.Hint]): payload = texts asyncio.create_task(send_msgs(client.socket, payload)) - async def server(websocket, path, ctx: Context): client = Client(websocket) ctx.clients.append(client) @@ -210,7 +212,7 @@ def get_connected_players_string(ctx: Context): return f'{len(auth_clients)} players of {len(ctx.player_names)} connected ' + text[:-1] -def get_received_items(ctx: Context, team: int, player: int): +def get_received_items(ctx: Context, team: int, player: int) -> typing.List[ReceivedItem]: return ctx.received_items.setdefault((team, player), []) @@ -235,7 +237,7 @@ def forfeit_player(ctx: Context, team: int, slot: int): def register_location_checks(ctx: Context, team: int, slot: int, locations): - ctx.location_checks[team, slot] |= set(locations) + found_items = False for location in locations: @@ -254,8 +256,17 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations): recvd_items.append(new_item) if slot != target_player: broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]]) - logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], get_location_name_from_address(location))) + logging.info('(Team #%d) %s sent %s to %s (%s)' % ( + team + 1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), + ctx.player_names[(team, target_player)], get_location_name_from_address(location))) found_items = True + elif target_player == slot: # local pickup, notify clients of the pickup + if location not in ctx.location_checks[team, slot]: + for client in ctx.clients: + if client.team == team and client.wants_item_notification: + asyncio.create_task( + send_msgs(client.socket, [['ItemFound', (target_item, location, slot)]])) + ctx.location_checks[team, slot] |= set(locations) send_new_items(ctx) if found_items: @@ -398,6 +409,12 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args): logging.info(f"{client.name} in team {client.team+1} scouted {', '.join([l[0] for l in locs])}") await send_msgs(client.socket, [['LocationInfo', [l[1:] for l in locs]]]) + if cmd == 'UpdateTags': + if not args or type(args) is not list: + await send_msgs(client.socket, [['InvalidArguments', 'UpdateTags']]) + return + client.tags = args + if cmd == 'Say': if type(args) is not str or not args.isprintable(): await send_msgs(client.socket, [['InvalidArguments', 'Say']])