From 8b2433584defc57694aae0a15157db675b7860d3 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 30 Sep 2021 09:09:21 +0200 Subject: [PATCH] CommonClient: allow running it as text client CommonClient: move logging init to library Setup: add TextClient --- CommonClient.py | 85 +++++++++++++++++++++++++++++++++++++++++++++-- FactorioClient.py | 26 ++++++--------- LttPClient.py | 16 ++------- inno_setup_38.iss | 5 ++- kvui.py | 10 ++++++ setup.py | 1 + 6 files changed, 110 insertions(+), 33 deletions(-) diff --git a/CommonClient.py b/CommonClient.py index 79d73a7e..5c52fe8f 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -4,6 +4,7 @@ import typing import asyncio import urllib.parse import sys +import os import websockets @@ -17,6 +18,9 @@ logger = logging.getLogger("Client") gui_enabled = Utils.is_frozen() or "--nogui" not in sys.argv +log_folder = Utils.local_path("logs") +os.makedirs(log_folder, exist_ok=True) + class ClientCommandProcessor(CommandProcessor): def __init__(self, ctx: CommonContext): @@ -198,7 +202,7 @@ class CommonContext(): def event_invalid_game(self): raise Exception('Invalid Game; please verify that you connected with the right game to the correct world.') - async def server_auth(self, password_requested): + async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: logger.info('Enter the password required to join this game:') self.password = await self.console_input() @@ -315,6 +319,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict): logger.info("Server protocol tags: " + ", ".join(args["tags"])) if args['password']: logger.info('Password required') + for permission_name, permission_flag in args.get("permissions", {}).items(): flag = Permission(permission_flag) logger.info(f"{permission_name.capitalize()} permission: {flag.name}") @@ -324,8 +329,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict): f" for each location checked. Use !hint for more information.") ctx.hint_cost = int(args['hint_cost']) ctx.check_points = int(args['location_check_points']) - ctx.forfeit_mode = args['forfeit_mode'] - ctx.remaining_mode = args['remaining_mode'] + if len(args['players']) < 1: logger.info('No player connected') else: @@ -453,3 +457,78 @@ async def console_loop(ctx: CommonContext): commandprocessor(input_text) except Exception as e: logger.exception(e) + + +def init_logging(name: str): + if gui_enabled: + logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, + filename=os.path.join(log_folder, f"{name}.txt"), filemode="w", force=True) + else: + logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, force=True) + logging.getLogger().addHandler(logging.FileHandler(os.path.join(log_folder, f"{name}.txt"), "w")) + + +if __name__ == '__main__': + # Text Mode to use !hint and such with games that have no text entry + init_logging("TextClient") + + class TextContext(CommonContext): + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super(TextContext, self).server_auth(password_requested) + if not self.auth: + logger.info('Enter slot name:') + self.auth = await self.console_input() + + await self.send_msgs([{"cmd": 'Connect', + 'password': self.password, 'name': self.auth, 'version': Utils.version_tuple, + 'tags': ['AP', 'IgnoreGame'], + 'uuid': Utils.get_unique_identifier(), 'game': self.game + }]) + + async def main(args): + ctx = TextContext(args.connect, args.password) + ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") + if gui_enabled: + input_task = None + from kvui import TextManager + ctx.ui = TextManager(ctx) + ui_task = asyncio.create_task(ctx.ui.async_run(), name="UI") + else: + input_task = asyncio.create_task(console_loop(ctx), name="Input") + ui_task = None + await ctx.exit_event.wait() + + ctx.server_address = None + if ctx.server and not ctx.server.socket.closed: + await ctx.server.socket.close() + if ctx.server_task: + await ctx.server_task + + while ctx.input_requests > 0: + ctx.input_queue.put_nowait(None) + ctx.input_requests -= 1 + + if ui_task: + await ui_task + + if input_task: + input_task.cancel() + + + import argparse + import colorama + + parser = argparse.ArgumentParser(description="Gameless Archipelago Client, for text interfaction.") + parser.add_argument('--connect', default=None, help='Address of the multiworld host.') + parser.add_argument('--password', default=None, help='Password of the multiworld host.') + if not Utils.is_frozen(): # Frozen state has no cmd window in the first place + parser.add_argument('--nogui', default=False, action='store_true', help="Turns off Client GUI.") + + args, rest = parser.parse_known_args() + colorama.init() + + loop = asyncio.get_event_loop() + loop.run_until_complete(main(args)) + loop.close() + colorama.deinit() diff --git a/FactorioClient.py b/FactorioClient.py index f64d6cdb..d9a4d856 100644 --- a/FactorioClient.py +++ b/FactorioClient.py @@ -10,7 +10,8 @@ import factorio_rcon import colorama import asyncio from queue import Queue -from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, logger, gui_enabled +from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, logger, gui_enabled, \ + init_logging from MultiServer import mark_raw import Utils @@ -19,17 +20,7 @@ from NetUtils import NetworkItem, ClientStatus, JSONtoTextParser, JSONMessagePar from worlds.factorio import Factorio -log_folder = Utils.local_path("logs") - -os.makedirs(log_folder, exist_ok=True) - - -if gui_enabled: - logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, - filename=os.path.join(log_folder, "FactorioClient.txt"), filemode="w", force=True) -else: - logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, force=True) - logging.getLogger().addHandler(logging.FileHandler(os.path.join(log_folder, "FactorioClient.txt"), "w")) +init_logging("FactorioClient") class FactorioCommandProcessor(ClientCommandProcessor): @@ -66,7 +57,7 @@ class FactorioContext(CommonContext): self.awaiting_bridge = False self.factorio_json_text_parser = FactorioJSONtoTextParser(self) - async def server_auth(self, password_requested): + async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: await super(FactorioContext, self).server_auth(password_requested) @@ -100,7 +91,7 @@ class FactorioContext(CommonContext): def print_to_game(self, text): self.rcon_client.send_command(f"/ap-print [font=default-large-bold]Archipelago:[/font] " - f"{text}") + f"{text}") def on_package(self, cmd: str, args: dict): if cmd == "Connected": @@ -268,13 +259,15 @@ async def factorio_spinup_server(ctx: FactorioContext) -> bool: ctx.exit_event.set() else: - logger.info(f"Got World Information from AP Mod {tuple(ctx.mod_version)} for seed {ctx.seed_name} in slot {ctx.auth}") + logger.info( + f"Got World Information from AP Mod {tuple(ctx.mod_version)} for seed {ctx.seed_name} in slot {ctx.auth}") return True finally: factorio_process.terminate() factorio_process.wait(5) return False + async def main(args): ctx = FactorioContext(args.connect, args.password) ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") @@ -345,7 +338,8 @@ if __name__ == '__main__': args, rest = parser.parse_known_args() colorama.init() rcon_port = args.rcon_port - rcon_password = args.rcon_password if args.rcon_password else ''.join(random.choice(string.ascii_letters) for x in range(32)) + rcon_password = args.rcon_password if args.rcon_password else ''.join( + random.choice(string.ascii_letters) for x in range(32)) factorio_server_logger = logging.getLogger("FactorioServer") options = Utils.get_options() diff --git a/LttPClient.py b/LttPClient.py index 4d499ad2..a9c1d1c2 100644 --- a/LttPClient.py +++ b/LttPClient.py @@ -26,24 +26,14 @@ from NetUtils import * from worlds.alttp import Regions, Shops from worlds.alttp import Items import Utils -from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, gui_enabled +from CommonClient import CommonContext, server_loop, console_loop, ClientCommandProcessor, gui_enabled, init_logging + +init_logging("LttPClient") snes_logger = logging.getLogger("SNES") from MultiServer import mark_raw -log_folder = Utils.local_path("logs") -os.makedirs(log_folder, exist_ok=True) - -# Log to file in gui case -if gui_enabled: - logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, - filename=os.path.join(log_folder, "LttPClient.txt"), filemode="w", force=True) -else: - logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO, force=True) - logging.getLogger().addHandler(logging.FileHandler(os.path.join(log_folder, "LttPClient.txt"), "w")) - - class LttPCommandProcessor(ClientCommandProcessor): def _cmd_slow_mode(self, toggle: str = ""): """Toggle slow mode, which limits how fast you send / receive items.""" diff --git a/inno_setup_38.iss b/inno_setup_38.iss index 7662c59e..15fa6c61 100644 --- a/inno_setup_38.iss +++ b/inno_setup_38.iss @@ -54,9 +54,10 @@ Name: "generator/lttp"; Description: "A Link to the Past ROM Setup"; Types: full Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting Name: "server"; Description: "Server"; Types: full hosting Name: "client"; Description: "Clients"; Types: full playing -Name: "client/lttp"; Description: "A Link to the Past"; Types: full playing hosting +Name: "client/lttp"; Description: "A Link to the Past"; Types: full playing Name: "client/factorio"; Description: "Factorio"; Types: full playing Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 +Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing [Dirs] NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; @@ -71,6 +72,7 @@ Source: "{#sourcepath}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app} Source: "{#sourcepath}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server Source: "{#sourcepath}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio +Source: "{#sourcepath}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/text Source: "{#sourcepath}\ArchipelagoLttPClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp Source: "{#sourcepath}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp Source: "{#sourcepath}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft @@ -82,6 +84,7 @@ Source: "{tmp}\forge-installer.jar"; DestDir: "{app}"; Flags: skipifsourcedoesnt [Icons] Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server +Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/lttp Name: "{group}\{#MyAppName} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/lttp Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft diff --git a/kvui.py b/kvui.py index 8dd296e6..75560007 100644 --- a/kvui.py +++ b/kvui.py @@ -16,6 +16,7 @@ from kivy.lang import Builder import Utils from NetUtils import JSONtoTextParser, JSONMessagePart + class GameManager(App): logging_pairs = [ ("Client", "Archipelago"), @@ -83,6 +84,7 @@ class FactorioManager(GameManager): ] title = "Archipelago Factorio Client" + class LttPManager(GameManager): logging_pairs = [ ("Client", "Archipelago"), @@ -90,6 +92,14 @@ class LttPManager(GameManager): ] title = "Archipelago LttP Client" + +class TextManager(GameManager): + logging_pairs = [ + ("Client", "Archipelago") + ] + title = "Archipelago Text Client" + + class LogtoUI(logging.Handler): def __init__(self, on_log): super(LogtoUI, self).__init__(logging.DEBUG) diff --git a/setup.py b/setup.py index 99c1b4c7..374a8638 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ scripts = { # Core "MultiServer.py": ("ArchipelagoServer", False, icon), "Generate.py": ("ArchipelagoGenerate", False, icon), + "CommonClient.py": ("ArchipelagoTextClient", True, icon), # LttP "LttPClient.py": ("ArchipelagoLttPClient", True, icon), "LttPAdjuster.py": ("ArchipelagoLttPAdjuster", True, icon),