From cfff12d8d7a7ae36cef355d0f93449dd2d8b1027 Mon Sep 17 00:00:00 2001 From: recklesscoder <57289227+recklesscoder@users.noreply.github.com> Date: Fri, 28 Oct 2022 00:45:26 +0200 Subject: [PATCH] Factorio: Added ability to chat from within the game. (#1068) * Factorio: Added ability to chat from within the game. This also allows using commands such as !hint from within the game. * Factorio: Only prepend player names to chat in multiplayer. * Factorio: Mirror chat sent from the FactorioClient UI to the Factorio server. * Factorio: Remove local coordinates from outgoing chat. * Factorio: Added setting to disable bridging chat out. Added client command to toggle this setting at run-time. * Factorio: Added in-game command to toggle chat bridging setting at run-time. * . * Factorio: Document toggle for chat bridging feature. * (Removed superfluous type annotations.) * (Removed hard to read regex.) * Docs/Factorio: Fix display of multiline code snippets. --- FactorioClient.py | 53 ++++++++++++++++++- Utils.py | 1 + host.yaml | 2 + worlds/factorio/data/mod_template/control.lua | 6 +++ worlds/factorio/docs/setup_en.md | 27 ++++++---- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/FactorioClient.py b/FactorioClient.py index 8ab9799b7c66..12ec22916b60 100644 --- a/FactorioClient.py +++ b/FactorioClient.py @@ -8,6 +8,7 @@ import subprocess import time import random +import typing import ModuleUpdate ModuleUpdate.update() @@ -51,6 +52,9 @@ def _cmd_toggle_send_filter(self): """Toggle filtering of item sends that get displayed in-game to only those that involve you.""" self.ctx.toggle_filter_item_sends() + def _cmd_toggle_chat(self): + """Toggle sending of chat messages from players on the Factorio server to Archipelago.""" + self.ctx.toggle_bridge_chat_out() class FactorioContext(CommonContext): command_processor = FactorioCommandProcessor @@ -71,6 +75,8 @@ def __init__(self, server_address, password): self.energy_link_increment = 0 self.last_deplete = 0 self.filter_item_sends: bool = False + self.multiplayer: bool = False # whether multiple different players have connected + self.bridge_chat_out: bool = True async def server_auth(self, password_requested: bool = False): if password_requested and not self.password: @@ -87,13 +93,15 @@ async def server_auth(self, password_requested: bool = False): def on_print(self, args: dict): super(FactorioContext, self).on_print(args) if self.rcon_client: - self.print_to_game(args['text']) + if not args['text'].startswith(self.player_names[self.slot] + ":"): + self.print_to_game(args['text']) def on_print_json(self, args: dict): if self.rcon_client: if not self.filter_item_sends or not self.is_uninteresting_item_send(args): text = self.factorio_json_text_parser(copy.deepcopy(args["data"])) - self.print_to_game(text) + if not text.startswith(self.player_names[self.slot] + ":"): + self.print_to_game(text) super(FactorioContext, self).on_print_json(args) @property @@ -130,6 +138,27 @@ def on_package(self, cmd: str, args: dict): f"{Utils.format_SI_prefix(args['value'])}J remaining.") self.rcon_client.send_command(f"/ap-energylink {gained}") + def on_user_say(self, text: str) -> typing.Optional[str]: + # Mirror chat sent from the UI to the Factorio server. + self.print_to_game(f"{self.player_names[self.slot]}: {text}") + return text + + async def chat_from_factorio(self, user: str, message: str) -> None: + if not self.bridge_chat_out: + return + + # Pass through commands + if message.startswith("!"): + await self.send_msgs([{"cmd": "Say", "text": message}]) + return + + # Omit messages that contain local coordinates + if "[gps=" in message: + return + + prefix = f"({user}) " if self.multiplayer else "" + await self.send_msgs([{"cmd": "Say", "text": f"{prefix}{message}"}]) + def toggle_filter_item_sends(self) -> None: self.filter_item_sends = not self.filter_item_sends if self.filter_item_sends: @@ -139,6 +168,15 @@ def toggle_filter_item_sends(self) -> None: logger.info(announcement) self.print_to_game(announcement) + def toggle_bridge_chat_out(self) -> None: + self.bridge_chat_out = not self.bridge_chat_out + if self.bridge_chat_out: + announcement = "Chat is now bridged to Archipelago." + else: + announcement = "Chat is no longer bridged to Archipelago." + logger.info(announcement) + self.print_to_game(announcement) + def run_gui(self): from kvui import GameManager @@ -178,6 +216,7 @@ async def game_watcher(ctx: FactorioContext): research_data = {int(tech_name.split("-")[1]) for tech_name in research_data} victory = data["victory"] await ctx.update_death_link(data["death_link"]) + ctx.multiplayer = data.get("multiplayer", False) if not ctx.finished_game and victory: await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) @@ -281,8 +320,14 @@ async def factorio_server_watcher(ctx: FactorioContext): elif re.match(r"^[0-9.]+ Script @[^ ]+\.lua:\d+: Player command toggle-ap-send-filter", msg): factorio_server_logger.debug(msg) ctx.toggle_filter_item_sends() + elif re.match(r"^[0-9.]+ Script @[^ ]+\.lua:\d+: Player command toggle-ap-chat$", msg): + factorio_server_logger.debug(msg) + ctx.toggle_bridge_chat_out() else: factorio_server_logger.info(msg) + match = re.match(r"^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d \[CHAT\] ([^:]+): (.*)$", msg) + if match: + await ctx.chat_from_factorio(match.group(1), match.group(2)) if ctx.rcon_client: commands = {} while ctx.send_index < len(ctx.items_received): @@ -383,6 +428,7 @@ async def factorio_spinup_server(ctx: FactorioContext) -> bool: async def main(args): ctx = FactorioContext(args.connect, args.password) ctx.filter_item_sends = initial_filter_item_sends + ctx.bridge_chat_out = initial_bridge_chat_out ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop") if gui_enabled: @@ -438,6 +484,9 @@ def _handle_color(self, node: JSONMessagePart): if not isinstance(options["factorio_options"]["filter_item_sends"], bool): logging.warning(f"Warning: Option filter_item_sends should be a bool.") initial_filter_item_sends = bool(options["factorio_options"]["filter_item_sends"]) + if not isinstance(options["factorio_options"]["bridge_chat_out"], bool): + logging.warning(f"Warning: Option bridge_chat_out should be a bool.") + initial_bridge_chat_out = bool(options["factorio_options"]["bridge_chat_out"]) if not os.path.exists(os.path.dirname(executable)): raise FileNotFoundError(f"Path {os.path.dirname(executable)} does not exist or could not be accessed.") diff --git a/Utils.py b/Utils.py index b2e98358bce6..e0c86ddb393e 100644 --- a/Utils.py +++ b/Utils.py @@ -232,6 +232,7 @@ def get_default_options() -> OptionsType: "factorio_options": { "executable": os.path.join("factorio", "bin", "x64", "factorio"), "filter_item_sends": False, + "bridge_chat_out": True, }, "sni_options": { "sni": "SNI", diff --git a/host.yaml b/host.yaml index 0bdd95356e77..4e94a9a30c8f 100644 --- a/host.yaml +++ b/host.yaml @@ -101,6 +101,8 @@ factorio_options: # server_settings: "factorio\\data\\server-settings.json" # Whether to filter item send messages displayed in-game to only those that involve you. filter_item_sends: false + # Whether to send chat messages from players on the Factorio server to Archipelago. + bridge_chat_out: true minecraft_options: forge_directory: "Minecraft Forge server" max_heap_size: "2G" diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua index 86e83b9f4dff..98c9ca621ad2 100644 --- a/worlds/factorio/data/mod_template/control.lua +++ b/worlds/factorio/data/mod_template/control.lua @@ -157,6 +157,7 @@ function on_player_created(event) {%- if silo == 2 %} check_spawn_silo(game.players[event.player_index].force) {%- endif %} + dumpInfo(player.force) end script.on_event(defines.events.on_player_created, on_player_created) @@ -491,6 +492,7 @@ commands.add_command("ap-sync", "Used by the Archipelago client to get progress ["death_link"] = DEATH_LINK, ["energy"] = chain_lookup(forcedata, "energy"), ["energy_bridges"] = chain_lookup(forcedata, "energy_bridges"), + ["multiplayer"] = #game.players > 1, } for tech_name, tech in pairs(force.technologies) do @@ -600,5 +602,9 @@ commands.add_command("toggle-ap-send-filter", "Toggle filtering of item sends th log("Player command toggle-ap-send-filter") -- notifies client end) +commands.add_command("toggle-ap-chat", "Toggle sending of chat messages from players on the Factorio server to Archipelago.", function(call) + log("Player command toggle-ap-chat") -- notifies client +end) + -- data progressive_technologies = {{ dict_to_lua(progressive_technology_table) }} diff --git a/worlds/factorio/docs/setup_en.md b/worlds/factorio/docs/setup_en.md index 8b24da13d57a..73ff5c8c4893 100644 --- a/worlds/factorio/docs/setup_en.md +++ b/worlds/factorio/docs/setup_en.md @@ -132,6 +132,8 @@ This allows you to host your own Factorio game. For additional client features, issue the `/help` command in the Archipelago Client. Once connected to the AP server, you can also issue the `!help` command to learn about additional commands like `!hint`. +For more information about the commands you can use, see the [Commands Guide](/tutorial/Archipelago/commands/en) and +[Other Settings](#other-settings). ## Allowing Other People to Join Your Game @@ -148,10 +150,20 @@ you can also issue the `!help` command to learn about additional commands like ` - Type `/toggle-ap-send-filter` in-game - Type `/toggle_send_filter` in the Archipelago Client - In your `host.yaml` set - ``` - factorio_options: - filter_item_sends: true - ``` +``` +factorio_options: + filter_item_sends: true +``` +- By default, in-game chat is bridged to Archipelago. If you prefer to be able to speak privately, you can disable this + feature by doing one of the following: + - Type `/toggle-ap-chat` in-game + - Type `/toggle_chat` in the Archipelago Client + - In your `host.yaml` set +``` +factorio_options: + bridge_chat_out: false +``` + Note that this will also disable `!` commands from within the game, and that it will not affect incoming chat. ## Troubleshooting @@ -159,13 +171,6 @@ In case any problems should occur, the Archipelago Client will create a file `Fa contents of this file may help you troubleshoot an issue on your own and is vital for requesting help from other people in Archipelago. -## Commands in game - -Once you have connected to the server successfully using the Archipelago Factorio Client you should see a message -stating you can get help using Archipelago commands by typing `!help`. Commands cannot currently be sent from within -the Factorio session, but you can send them from the Archipelago Factorio Client. For more information about the commands -you can use see the [commands guide](/tutorial/Archipelago/commands/en). - ## Additional Resources - Alternate Tutorial by