FactorioClient: implement JSONPrint in the client
This commit is contained in:
parent
e098b3c504
commit
096e682b18
|
@ -32,102 +32,6 @@ else:
|
||||||
|
|
||||||
gui_enabled = Utils.is_frozen() or "--nogui" not in sys.argv
|
gui_enabled = Utils.is_frozen() or "--nogui" not in sys.argv
|
||||||
|
|
||||||
def get_kivy_app():
|
|
||||||
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
|
||||||
os.environ["KIVY_NO_FILELOG"] = "1"
|
|
||||||
os.environ["KIVY_NO_ARGS"] = "1"
|
|
||||||
from kivy.app import App
|
|
||||||
from kivy.base import ExceptionHandler, ExceptionManager, Config
|
|
||||||
from kivy.uix.gridlayout import GridLayout
|
|
||||||
from kivy.uix.textinput import TextInput
|
|
||||||
from kivy.uix.recycleview import RecycleView
|
|
||||||
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
|
|
||||||
from kivy.lang import Builder
|
|
||||||
|
|
||||||
class FactorioManager(App):
|
|
||||||
def __init__(self, ctx):
|
|
||||||
super(FactorioManager, self).__init__()
|
|
||||||
self.ctx = ctx
|
|
||||||
self.commandprocessor = ctx.command_processor(ctx)
|
|
||||||
self.icon = r"data/icon.png"
|
|
||||||
|
|
||||||
def build(self):
|
|
||||||
self.grid = GridLayout()
|
|
||||||
self.grid.cols = 1
|
|
||||||
|
|
||||||
self.tabs = TabbedPanel()
|
|
||||||
self.tabs.default_tab_text = "All"
|
|
||||||
self.title = "Archipelago Factorio Client"
|
|
||||||
pairs = [
|
|
||||||
("Client", "Archipelago"),
|
|
||||||
("FactorioServer", "Factorio Server Log"),
|
|
||||||
("FactorioWatcher", "Bridge Data Log"),
|
|
||||||
]
|
|
||||||
self.tabs.default_tab_content = UILog(*(logging.getLogger(logger_name) for logger_name, name in pairs))
|
|
||||||
for logger_name, display_name in pairs:
|
|
||||||
bridge_logger = logging.getLogger(logger_name)
|
|
||||||
panel = TabbedPanelItem(text=display_name)
|
|
||||||
panel.content = UILog(bridge_logger)
|
|
||||||
self.tabs.add_widget(panel)
|
|
||||||
|
|
||||||
self.grid.add_widget(self.tabs)
|
|
||||||
textinput = TextInput(size_hint_y=None, height=30, multiline=False)
|
|
||||||
textinput.bind(on_text_validate=self.on_message)
|
|
||||||
self.grid.add_widget(textinput)
|
|
||||||
self.commandprocessor("/help")
|
|
||||||
return self.grid
|
|
||||||
|
|
||||||
def on_stop(self):
|
|
||||||
self.ctx.exit_event.set()
|
|
||||||
|
|
||||||
def on_message(self, textinput: TextInput):
|
|
||||||
try:
|
|
||||||
input_text = textinput.text.strip()
|
|
||||||
textinput.text = ""
|
|
||||||
|
|
||||||
if self.ctx.input_requests > 0:
|
|
||||||
self.ctx.input_requests -= 1
|
|
||||||
self.ctx.input_queue.put_nowait(input_text)
|
|
||||||
elif input_text:
|
|
||||||
self.commandprocessor(input_text)
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
|
|
||||||
|
|
||||||
class LogtoUI(logging.Handler):
|
|
||||||
def __init__(self, on_log):
|
|
||||||
super(LogtoUI, self).__init__(logging.DEBUG)
|
|
||||||
self.on_log = on_log
|
|
||||||
|
|
||||||
def handle(self, record: logging.LogRecord) -> None:
|
|
||||||
self.on_log(record)
|
|
||||||
|
|
||||||
|
|
||||||
class UILog(RecycleView):
|
|
||||||
cols = 1
|
|
||||||
|
|
||||||
def __init__(self, *loggers_to_handle, **kwargs):
|
|
||||||
super(UILog, self).__init__(**kwargs)
|
|
||||||
self.data = []
|
|
||||||
for logger in loggers_to_handle:
|
|
||||||
logger.addHandler(LogtoUI(self.on_log))
|
|
||||||
|
|
||||||
def on_log(self, record: logging.LogRecord) -> None:
|
|
||||||
self.data.append({"text": record.getMessage()})
|
|
||||||
|
|
||||||
|
|
||||||
class E(ExceptionHandler):
|
|
||||||
def handle_exception(self, inst):
|
|
||||||
logger.exception(inst)
|
|
||||||
return ExceptionManager.RAISE
|
|
||||||
|
|
||||||
|
|
||||||
ExceptionManager.add_handler(E())
|
|
||||||
|
|
||||||
Config.set("input", "mouse", "mouse,disable_multitouch")
|
|
||||||
Builder.load_file(Utils.local_path("data", "client.kv"))
|
|
||||||
return FactorioManager
|
|
||||||
|
|
||||||
|
|
||||||
class FactorioCommandProcessor(ClientCommandProcessor):
|
class FactorioCommandProcessor(ClientCommandProcessor):
|
||||||
ctx: FactorioContext
|
ctx: FactorioContext
|
||||||
|
@ -150,6 +54,7 @@ class FactorioCommandProcessor(ClientCommandProcessor):
|
||||||
class FactorioContext(CommonContext):
|
class FactorioContext(CommonContext):
|
||||||
command_processor = FactorioCommandProcessor
|
command_processor = FactorioCommandProcessor
|
||||||
game = "Factorio"
|
game = "Factorio"
|
||||||
|
ui = None
|
||||||
|
|
||||||
# updated by spinup server
|
# updated by spinup server
|
||||||
mod_version: Utils.Version = Utils.Version(0, 0, 0)
|
mod_version: Utils.Version = Utils.Version(0, 0, 0)
|
||||||
|
@ -185,8 +90,11 @@ class FactorioContext(CommonContext):
|
||||||
self.print_to_game(args['text'])
|
self.print_to_game(args['text'])
|
||||||
|
|
||||||
def on_print_json(self, args: dict):
|
def on_print_json(self, args: dict):
|
||||||
text = self.raw_json_text_parser(copy.deepcopy(args["data"]))
|
if self.ui:
|
||||||
logger.info(text)
|
self.ui.print_json(copy.deepcopy(args["data"]))
|
||||||
|
else:
|
||||||
|
text = self.raw_json_text_parser(copy.deepcopy(args["data"]))
|
||||||
|
logger.info(text)
|
||||||
if self.rcon_client:
|
if self.rcon_client:
|
||||||
text = self.factorio_json_text_parser(args["data"])
|
text = self.factorio_json_text_parser(args["data"])
|
||||||
self.print_to_game(text)
|
self.print_to_game(text)
|
||||||
|
@ -215,9 +123,9 @@ async def game_watcher(ctx: FactorioContext):
|
||||||
ctx.awaiting_bridge = False
|
ctx.awaiting_bridge = False
|
||||||
data = json.loads(ctx.rcon_client.send_command("/ap-sync"))
|
data = json.loads(ctx.rcon_client.send_command("/ap-sync"))
|
||||||
if data["slot_name"] != ctx.auth:
|
if data["slot_name"] != ctx.auth:
|
||||||
logger.warning(f"Connected World is not the expected one {data['slot_name']} != {ctx.auth}")
|
bridge_logger.warning(f"Connected World is not the expected one {data['slot_name']} != {ctx.auth}")
|
||||||
elif data["seed_name"] != ctx.seed_name:
|
elif data["seed_name"] != ctx.seed_name:
|
||||||
logger.warning(
|
bridge_logger.warning(
|
||||||
f"Connected Multiworld is not the expected one {data['seed_name']} != {ctx.seed_name}")
|
f"Connected Multiworld is not the expected one {data['seed_name']} != {ctx.seed_name}")
|
||||||
else:
|
else:
|
||||||
data = data["info"]
|
data = data["info"]
|
||||||
|
@ -370,7 +278,9 @@ async def main(args):
|
||||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
||||||
if gui_enabled:
|
if gui_enabled:
|
||||||
input_task = None
|
input_task = None
|
||||||
ui_app = get_kivy_app()(ctx)
|
from kvui import FactorioManager
|
||||||
|
ui_app = FactorioManager(ctx)
|
||||||
|
ctx.ui = ui_app
|
||||||
ui_task = asyncio.create_task(ui_app.async_run(), name="UI")
|
ui_task = asyncio.create_task(ui_app.async_run(), name="UI")
|
||||||
else:
|
else:
|
||||||
input_task = asyncio.create_task(console_loop(ctx), name="Input")
|
input_task = asyncio.create_task(console_loop(ctx), name="Input")
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
size_hint_y: None
|
size_hint_y: None
|
||||||
height: self.texture_size[1]
|
height: self.texture_size[1]
|
||||||
font_size: dp(20)
|
font_size: dp(20)
|
||||||
|
markup: True
|
||||||
<UILog>:
|
<UILog>:
|
||||||
viewclass: 'Row'
|
viewclass: 'Row'
|
||||||
scroll_y: 0
|
scroll_y: 0
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
||||||
|
os.environ["KIVY_NO_FILELOG"] = "1"
|
||||||
|
os.environ["KIVY_NO_ARGS"] = "1"
|
||||||
|
from kivy.app import App
|
||||||
|
from kivy.base import ExceptionHandler, ExceptionManager, Config
|
||||||
|
from kivy.uix.gridlayout import GridLayout
|
||||||
|
from kivy.uix.textinput import TextInput
|
||||||
|
from kivy.uix.recycleview import RecycleView
|
||||||
|
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
|
||||||
|
from kivy.utils import escape_markup
|
||||||
|
from kivy.lang import Builder
|
||||||
|
|
||||||
|
import Utils
|
||||||
|
from NetUtils import JSONtoTextParser, JSONMessagePart
|
||||||
|
|
||||||
|
class GameManager(App):
|
||||||
|
logging_pairs = [
|
||||||
|
("Client", "Archipelago"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, ctx):
|
||||||
|
self.ctx = ctx
|
||||||
|
self.commandprocessor = ctx.command_processor(ctx)
|
||||||
|
self.icon = r"data/icon.png"
|
||||||
|
self.json_to_kivy_parser = KivyJSONtoTextParser(ctx)
|
||||||
|
self.log_panels = {}
|
||||||
|
super(GameManager, self).__init__()
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
self.grid = GridLayout()
|
||||||
|
self.grid.cols = 1
|
||||||
|
|
||||||
|
self.tabs = TabbedPanel()
|
||||||
|
self.tabs.default_tab_text = "All"
|
||||||
|
|
||||||
|
self.log_panels["All"] = self.tabs.default_tab_content = UILog(*(logging.getLogger(logger_name)
|
||||||
|
for logger_name, name in
|
||||||
|
self.logging_pairs))
|
||||||
|
|
||||||
|
for logger_name, display_name in self.logging_pairs:
|
||||||
|
bridge_logger = logging.getLogger(logger_name)
|
||||||
|
panel = TabbedPanelItem(text=display_name)
|
||||||
|
self.log_panels[display_name] = panel.content = UILog(bridge_logger)
|
||||||
|
self.tabs.add_widget(panel)
|
||||||
|
|
||||||
|
self.grid.add_widget(self.tabs)
|
||||||
|
textinput = TextInput(size_hint_y=None, height=30, multiline=False)
|
||||||
|
textinput.bind(on_text_validate=self.on_message)
|
||||||
|
self.grid.add_widget(textinput)
|
||||||
|
self.commandprocessor("/help")
|
||||||
|
return self.grid
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
self.ctx.exit_event.set()
|
||||||
|
|
||||||
|
def on_message(self, textinput: TextInput):
|
||||||
|
try:
|
||||||
|
input_text = textinput.text.strip()
|
||||||
|
textinput.text = ""
|
||||||
|
|
||||||
|
if self.ctx.input_requests > 0:
|
||||||
|
self.ctx.input_requests -= 1
|
||||||
|
self.ctx.input_queue.put_nowait(input_text)
|
||||||
|
elif input_text:
|
||||||
|
self.commandprocessor(input_text)
|
||||||
|
except Exception as e:
|
||||||
|
logging.getLogger("Client").exception(e)
|
||||||
|
|
||||||
|
def print_json(self, data):
|
||||||
|
text = self.json_to_kivy_parser(data)
|
||||||
|
self.log_panels["Archipelago"].on_message_markup(text)
|
||||||
|
self.log_panels["All"].on_message_markup(text)
|
||||||
|
|
||||||
|
|
||||||
|
class FactorioManager(GameManager):
|
||||||
|
logging_pairs = [
|
||||||
|
("Client", "Archipelago"),
|
||||||
|
("FactorioServer", "Factorio Server Log"),
|
||||||
|
("FactorioWatcher", "Bridge Data Log"),
|
||||||
|
]
|
||||||
|
title = "Archipelago Factorio Client"
|
||||||
|
|
||||||
|
def __init__(self, ctx):
|
||||||
|
super(FactorioManager, self).__init__(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class LogtoUI(logging.Handler):
|
||||||
|
def __init__(self, on_log):
|
||||||
|
super(LogtoUI, self).__init__(logging.DEBUG)
|
||||||
|
self.on_log = on_log
|
||||||
|
|
||||||
|
def handle(self, record: logging.LogRecord) -> None:
|
||||||
|
self.on_log(record)
|
||||||
|
|
||||||
|
|
||||||
|
class UILog(RecycleView):
|
||||||
|
cols = 1
|
||||||
|
|
||||||
|
def __init__(self, *loggers_to_handle, **kwargs):
|
||||||
|
super(UILog, self).__init__(**kwargs)
|
||||||
|
self.data = []
|
||||||
|
for logger in loggers_to_handle:
|
||||||
|
logger.addHandler(LogtoUI(self.on_log))
|
||||||
|
|
||||||
|
def on_log(self, record: logging.LogRecord) -> None:
|
||||||
|
self.data.append({"text": escape_markup(record.getMessage())})
|
||||||
|
|
||||||
|
def on_message_markup(self, text):
|
||||||
|
self.data.append({"text": text})
|
||||||
|
|
||||||
|
|
||||||
|
class E(ExceptionHandler):
|
||||||
|
logger = logging.getLogger("Client")
|
||||||
|
|
||||||
|
def handle_exception(self, inst):
|
||||||
|
self.logger.exception(inst)
|
||||||
|
return ExceptionManager.RAISE
|
||||||
|
|
||||||
|
|
||||||
|
class KivyJSONtoTextParser(JSONtoTextParser):
|
||||||
|
color_codes = {
|
||||||
|
# not exact color names, close enough but decent looking
|
||||||
|
"black": "000000",
|
||||||
|
"red": "EE0000",
|
||||||
|
"green": "00FF7F",
|
||||||
|
"yellow": "FAFAD2",
|
||||||
|
"blue": "6495ED",
|
||||||
|
"magenta": "EE00EE",
|
||||||
|
"cyan": "00EEEE",
|
||||||
|
"white": "FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _handle_color(self, node: JSONMessagePart):
|
||||||
|
colors = node["color"].split(";")
|
||||||
|
node["text"] = escape_markup(node["text"])
|
||||||
|
for color in colors:
|
||||||
|
color_code = self.color_codes.get(color, None)
|
||||||
|
if color_code:
|
||||||
|
node["text"] = f"[color={color_code}]{node['text']}[/color]"
|
||||||
|
return self._handle_text(node)
|
||||||
|
return self._handle_text(node)
|
||||||
|
|
||||||
|
|
||||||
|
ExceptionManager.add_handler(E())
|
||||||
|
|
||||||
|
Config.set("input", "mouse", "mouse,disable_multitouch")
|
||||||
|
Builder.load_file(Utils.local_path("data", "client.kv"))
|
Loading…
Reference in New Issue