CommonClient: allow running it as text client

CommonClient: move logging init to library
Setup: add TextClient
This commit is contained in:
Fabian Dill 2021-09-30 09:09:21 +02:00
parent bde02f696b
commit 8b2433584d
6 changed files with 110 additions and 33 deletions

View File

@ -4,6 +4,7 @@ import typing
import asyncio import asyncio
import urllib.parse import urllib.parse
import sys import sys
import os
import websockets import websockets
@ -17,6 +18,9 @@ logger = logging.getLogger("Client")
gui_enabled = Utils.is_frozen() or "--nogui" not in sys.argv 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): class ClientCommandProcessor(CommandProcessor):
def __init__(self, ctx: CommonContext): def __init__(self, ctx: CommonContext):
@ -198,7 +202,7 @@ class CommonContext():
def event_invalid_game(self): def event_invalid_game(self):
raise Exception('Invalid Game; please verify that you connected with the right game to the correct world.') 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: if password_requested and not self.password:
logger.info('Enter the password required to join this game:') logger.info('Enter the password required to join this game:')
self.password = await self.console_input() 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"])) logger.info("Server protocol tags: " + ", ".join(args["tags"]))
if args['password']: if args['password']:
logger.info('Password required') logger.info('Password required')
for permission_name, permission_flag in args.get("permissions", {}).items(): for permission_name, permission_flag in args.get("permissions", {}).items():
flag = Permission(permission_flag) flag = Permission(permission_flag)
logger.info(f"{permission_name.capitalize()} permission: {flag.name}") 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.") f" for each location checked. Use !hint for more information.")
ctx.hint_cost = int(args['hint_cost']) ctx.hint_cost = int(args['hint_cost'])
ctx.check_points = int(args['location_check_points']) 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: if len(args['players']) < 1:
logger.info('No player connected') logger.info('No player connected')
else: else:
@ -453,3 +457,78 @@ async def console_loop(ctx: CommonContext):
commandprocessor(input_text) commandprocessor(input_text)
except Exception as e: except Exception as e:
logger.exception(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()

View File

@ -10,7 +10,8 @@ import factorio_rcon
import colorama import colorama
import asyncio import asyncio
from queue import Queue 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 from MultiServer import mark_raw
import Utils import Utils
@ -19,17 +20,7 @@ from NetUtils import NetworkItem, ClientStatus, JSONtoTextParser, JSONMessagePar
from worlds.factorio import Factorio from worlds.factorio import Factorio
log_folder = Utils.local_path("logs") init_logging("FactorioClient")
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"))
class FactorioCommandProcessor(ClientCommandProcessor): class FactorioCommandProcessor(ClientCommandProcessor):
@ -66,7 +57,7 @@ class FactorioContext(CommonContext):
self.awaiting_bridge = False self.awaiting_bridge = False
self.factorio_json_text_parser = FactorioJSONtoTextParser(self) 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: if password_requested and not self.password:
await super(FactorioContext, self).server_auth(password_requested) await super(FactorioContext, self).server_auth(password_requested)
@ -268,13 +259,15 @@ async def factorio_spinup_server(ctx: FactorioContext) -> bool:
ctx.exit_event.set() ctx.exit_event.set()
else: 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 return True
finally: finally:
factorio_process.terminate() factorio_process.terminate()
factorio_process.wait(5) factorio_process.wait(5)
return False return False
async def main(args): async def main(args):
ctx = FactorioContext(args.connect, args.password) ctx = FactorioContext(args.connect, args.password)
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
@ -345,7 +338,8 @@ if __name__ == '__main__':
args, rest = parser.parse_known_args() args, rest = parser.parse_known_args()
colorama.init() colorama.init()
rcon_port = args.rcon_port 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") factorio_server_logger = logging.getLogger("FactorioServer")
options = Utils.get_options() options = Utils.get_options()

View File

@ -26,24 +26,14 @@ from NetUtils import *
from worlds.alttp import Regions, Shops from worlds.alttp import Regions, Shops
from worlds.alttp import Items from worlds.alttp import Items
import Utils 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") snes_logger = logging.getLogger("SNES")
from MultiServer import mark_raw 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): class LttPCommandProcessor(ClientCommandProcessor):
def _cmd_slow_mode(self, toggle: str = ""): def _cmd_slow_mode(self, toggle: str = ""):
"""Toggle slow mode, which limits how fast you send / receive items.""" """Toggle slow mode, which limits how fast you send / receive items."""

View File

@ -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: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting
Name: "server"; Description: "Server"; Types: full hosting Name: "server"; Description: "Server"; Types: full hosting
Name: "client"; Description: "Clients"; Types: full playing 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/factorio"; Description: "Factorio"; Types: full playing
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
[Dirs] [Dirs]
NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; 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}\ArchipelagoGenerate.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: generator
Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server Source: "{#sourcepath}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: server
Source: "{#sourcepath}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio 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}\ArchipelagoLttPClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/lttp
Source: "{#sourcepath}\ArchipelagoLttPAdjuster.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 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] [Icons]
Name: "{group}\{#MyAppName} Folder"; Filename: "{app}"; Name: "{group}\{#MyAppName} Folder"; Filename: "{app}";
Name: "{group}\{#MyAppName} Server"; Filename: "{app}\{#MyAppExeName}"; Components: server 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} LttP Client"; Filename: "{app}\ArchipelagoLttPClient.exe"; Components: client/lttp
Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio
Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft

10
kvui.py
View File

@ -16,6 +16,7 @@ from kivy.lang import Builder
import Utils import Utils
from NetUtils import JSONtoTextParser, JSONMessagePart from NetUtils import JSONtoTextParser, JSONMessagePart
class GameManager(App): class GameManager(App):
logging_pairs = [ logging_pairs = [
("Client", "Archipelago"), ("Client", "Archipelago"),
@ -83,6 +84,7 @@ class FactorioManager(GameManager):
] ]
title = "Archipelago Factorio Client" title = "Archipelago Factorio Client"
class LttPManager(GameManager): class LttPManager(GameManager):
logging_pairs = [ logging_pairs = [
("Client", "Archipelago"), ("Client", "Archipelago"),
@ -90,6 +92,14 @@ class LttPManager(GameManager):
] ]
title = "Archipelago LttP Client" title = "Archipelago LttP Client"
class TextManager(GameManager):
logging_pairs = [
("Client", "Archipelago")
]
title = "Archipelago Text Client"
class LogtoUI(logging.Handler): class LogtoUI(logging.Handler):
def __init__(self, on_log): def __init__(self, on_log):
super(LogtoUI, self).__init__(logging.DEBUG) super(LogtoUI, self).__init__(logging.DEBUG)

View File

@ -74,6 +74,7 @@ scripts = {
# Core # Core
"MultiServer.py": ("ArchipelagoServer", False, icon), "MultiServer.py": ("ArchipelagoServer", False, icon),
"Generate.py": ("ArchipelagoGenerate", False, icon), "Generate.py": ("ArchipelagoGenerate", False, icon),
"CommonClient.py": ("ArchipelagoTextClient", True, icon),
# LttP # LttP
"LttPClient.py": ("ArchipelagoLttPClient", True, icon), "LttPClient.py": ("ArchipelagoLttPClient", True, icon),
"LttPAdjuster.py": ("ArchipelagoLttPAdjuster", True, icon), "LttPAdjuster.py": ("ArchipelagoLttPAdjuster", True, icon),