Skip to content

Commit

Permalink
Merge branch 'factorio_gui_client' into Archipelago_Main
Browse files Browse the repository at this point in the history
  • Loading branch information
Berserker66 committed Jun 2, 2021
2 parents 034f338 + 1d84346 commit a3d2df7
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 55 deletions.
61 changes: 37 additions & 24 deletions FactorioClient.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def __init__(self, *args, **kwargs):
super(FactorioContext, self).__init__(*args, **kwargs)
self.send_index = 0
self.rcon_client = None
self.awaiting_bridge = False
self.raw_json_text_parser = RawJSONtoTextParser(self)

async def server_auth(self, password_requested):
Expand All @@ -86,38 +87,40 @@ def on_print(self, args: dict):
def on_print_json(self, args: dict):
if not self.found_items and args.get("type", None) == "ItemSend" and args["receiving"] == args["sending"]:
pass # don't want info on other player's local pickups.
copy_data = copy.deepcopy(args["data"]) # jsontotextparser is destructive currently
logger.info(self.jsontotextparser(args["data"]))
text = self.raw_json_text_parser(args["data"])
logger.info(text)
if self.rcon_client:
cleaned_text = self.raw_json_text_parser(copy_data).replace('"', '')
cleaned_text = text.replace('"', '')
self.rcon_client.send_command(f"/sc game.print(\"Archipelago: {cleaned_text}\")")

async def game_watcher(ctx: FactorioContext, bridge_file: str):
bridge_logger = logging.getLogger("FactorioWatcher")
from worlds.factorio.Technologies import lookup_id_to_name
bridge_counter = 0
try:
while 1:
while not ctx.exit_event.is_set():
if os.path.exists(bridge_file):
bridge_logger.info("Found Factorio Bridge file.")
while 1:
with open(bridge_file) as f:
data = json.load(f)
research_data = data["research_done"]
research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
victory = data["victory"]
ctx.auth = data["slot_name"]
ctx.seed_name = data["seed_name"]

if not ctx.finished_game and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True

if ctx.locations_checked != research_data:
bridge_logger.info(f"New researches done: "
f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}")
ctx.locations_checked = research_data
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
while not ctx.exit_event.is_set():
if ctx.awaiting_bridge:
ctx.awaiting_bridge = False
with open(bridge_file) as f:
data = json.load(f)
research_data = data["research_done"]
research_data = {int(tech_name.split("-")[1]) for tech_name in research_data}
victory = data["victory"]
ctx.auth = data["slot_name"]
ctx.seed_name = data["seed_name"]

if not ctx.finished_game and victory:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True

if ctx.locations_checked != research_data:
bridge_logger.info(f"New researches done: "
f"{[lookup_id_to_name[rid] for rid in research_data - ctx.locations_checked]}")
ctx.locations_checked = research_data
await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": tuple(research_data)}])
await asyncio.sleep(1)
else:
bridge_counter += 1
Expand Down Expand Up @@ -160,8 +163,9 @@ async def factorio_server_watcher(ctx: FactorioContext):
stream_factorio_output(factorio_process.stdout, factorio_queue)
stream_factorio_output(factorio_process.stderr, factorio_queue)
script_folder = None
progression_watcher = None
try:
while 1:
while not ctx.exit_event.is_set():
while not factorio_queue.empty():
msg = factorio_queue.get()
factorio_server_logger.info(msg)
Expand All @@ -177,7 +181,10 @@ async def factorio_server_watcher(ctx: FactorioContext):
if os.path.exists(bridge_file):
os.remove(bridge_file)
logging.info(f"Bridge File Path: {bridge_file}")
asyncio.create_task(game_watcher(ctx, bridge_file), name="FactorioProgressionWatcher")
progression_watcher= asyncio.create_task(
game_watcher(ctx, bridge_file), name="FactorioProgressionWatcher")
if not ctx.awaiting_bridge and "Archipelago Bridge File written for game tick " in msg:
ctx.awaiting_bridge = True
if ctx.rcon_client:
while ctx.send_index < len(ctx.items_received):
transfer_item: NetworkItem = ctx.items_received[ctx.send_index]
Expand All @@ -192,10 +199,16 @@ async def factorio_server_watcher(ctx: FactorioContext):
ctx.send_index += 1
await asyncio.sleep(1)


except Exception as e:
logging.exception(e)
logging.error("Aborted Factorio Server Bridge")

finally:
factorio_process.terminate()
if progression_watcher:
await progression_watcher


async def main():
ctx = FactorioContext(None, None, True)
Expand Down
155 changes: 155 additions & 0 deletions FactorioClientGUI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import os
import logging

os.makedirs("logs", exist_ok=True)
logging.basicConfig(format='[%(name)s]: %(message)s', level=logging.INFO)
logging.getLogger().addHandler(logging.FileHandler(os.path.join("logs", "FactorioClient.txt"), "w"))
os.environ["KIVY_NO_CONSOLELOG"] = "1"
os.environ["KIVY_NO_FILELOG"] = "1"
os.environ["KIVY_NO_ARGS"] = "1"

import asyncio
from CommonClient import server_loop, logger
from FactorioClient import FactorioContext, factorio_server_watcher


async def main():
ctx = FactorioContext(None, None, True)

ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
factorio_server_task = asyncio.create_task(factorio_server_watcher(ctx), name="FactorioServer")
ui_app = FactorioManager(ctx)
ui_task = asyncio.create_task(ui_app.async_run(), name="UI")

await ctx.exit_event.wait() # wait for signal to exit application
ui_app.stop()
ctx.server_address = None
ctx.snes_reconnect_address = None
# allow tasks to quit
await ui_task
await factorio_server_task
await ctx.server_task

if ctx.server is not None and not ctx.server.socket.closed:
await ctx.server.socket.close()
if ctx.server_task is not None:
await ctx.server_task

while ctx.input_requests > 0: # clear queue for shutdown
ctx.input_queue.put_nowait(None)
ctx.input_requests -= 1


from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.recycleview import RecycleView
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
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 = "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 File 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 = TabbedPanelHeader(text=display_name)
self.tabs.add_widget(panel)
panel.content = UILog(bridge_logger)
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()})

def update_text_width(self, *_):
self.message.text_size = (self.message.width * 0.9, None)


Builder.load_string('''
<TabbedPanel>
tab_width: 200
<Row@Label>:
canvas.before:
Color:
rgba: 0.2, 0.2, 0.2, 1
Rectangle:
size: self.size
pos: self.pos
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
font_size: dp(20)
<UILog>:
viewclass: 'Row'
scroll_y: 0
effect_cls: "ScrollEffect"
RecycleBoxLayout:
default_size: None, dp(20)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(3)
''')

if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
5 changes: 4 additions & 1 deletion Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,10 @@ def write_multidata(roms, mods):
minimum_versions = {"server": (0, 1, 1), "clients": client_versions}
games = {}
for slot in world.player_ids:
client_versions[slot] = (0, 0, 3)
if world.game[slot] == "Factorio":
client_versions[slot] = (0, 1, 2)
else:
client_versions[slot] = (0, 0, 3)
games[slot] = world.game[slot]
connect_names = {base64.b64encode(rom_name).decode(): (team, slot) for
slot, team, rom_name in rom_names}
Expand Down
2 changes: 1 addition & 1 deletion Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Version(typing.NamedTuple):
minor: int
build: int

__version__ = "0.1.1"
__version__ = "0.1.2"
_version_tuple = tuplize_version(__version__)

import builtins
Expand Down
15 changes: 9 additions & 6 deletions data/factorio/mod_template/control.lua
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,18 @@ script.on_init(function()
end)

-- for testing
script.on_event(defines.events.on_tick, function(event)
if event.tick%600 == 300 then
dumpInfo(game.forces["player"])
end
end)
-- script.on_event(defines.events.on_tick, function(event)
-- if event.tick%3600 == 300 then
-- dumpInfo(game.forces["player"])
-- end
-- end)

-- hook into researches done
script.on_event(defines.events.on_research_finished, function(event)
local technology = event.research
dumpInfo(technology.force)
if technology.researched and string.find(technology.name, "ap%-") == 1 then
dumpInfo(technology.force) --is sendable
end
if FREE_SAMPLES == 0 then
return -- Nothing else to do
end
Expand Down Expand Up @@ -186,6 +188,7 @@ function dumpInfo(force)
end
end
game.write_file("ap_bridge.json", game.table_to_json(data_collection), false, 0)
log("Archipelago Bridge File written for game tick ".. game.tick .. ".")
-- game.write_file("research_done.json", game.table_to_json(data_collection), false, 0)
-- game.print("Sent progress to Archipelago.")
end
Expand Down
Loading

0 comments on commit a3d2df7

Please sign in to comment.