From 4520828f9ec0731589cd203b13445e12a923ec23 Mon Sep 17 00:00:00 2001
From: Owen <48495634+Owen3H@users.noreply.github.com>
Date: Sun, 11 Feb 2024 23:27:43 +0000
Subject: [PATCH] Add CSync page (#95)
---
docs/.vitepress/config.mts | 10 +-
docs/dev/apis/csync.md | 33 +++++
docs/dev/apis/csync/troubleshooting.md | 47 ++++++++
docs/dev/apis/csync/usage-guide.md | 160 +++++++++++++++++++++++++
docs/dev/apis/overview.md | 4 +-
5 files changed, 252 insertions(+), 2 deletions(-)
create mode 100644 docs/dev/apis/csync.md
create mode 100644 docs/dev/apis/csync/troubleshooting.md
create mode 100644 docs/dev/apis/csync/usage-guide.md
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 820c987888c..438cbd5d84a 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -133,7 +133,15 @@ export default defineConfig({
{ text: 'Coding Our AI', link: '/dev/apis/lethallib/custom-enemies/coding-ai'},
]
},
-
+ {
+ text: 'CSync',
+ link: '/dev/apis/csync',
+ collapsed: true,
+ items: [
+ { text: 'Usage Guide', link: '/dev/apis/csync/usage-guide' },
+ { text: 'Troubleshooting', link: '/dev/apis/csync/troubleshooting' }
+ ]
+ },
]
},
{
diff --git a/docs/dev/apis/csync.md b/docs/dev/apis/csync.md
new file mode 100644
index 00000000000..b74d0a82d4b
--- /dev/null
+++ b/docs/dev/apis/csync.md
@@ -0,0 +1,33 @@
+---
+prev: false
+next: false
+description: A guide to synchronizing a BepInEx config file between host and clients using the CSync library.
+---
+
+# CSync (Config Syncing Library)
+
+## Setup
+There are two ways to depend upon **CSync**, but I recommend using the [NuGet package](https://www.nuget.org/packages/Owen3H.BepInEx.CSync).
+This will automatically include both an assembly reference and documentation as opposed to Thunderstore.
+
+### NuGet (Recommended)
+**1**. Open the terminal in Visual Studio.
+**2**. Run the following command.
+```console
+dotnet add package Owen3H.BepInEx.CSync
+```
+
+Alternatively, you can install it visually via the **NuGet** package manager.
+
+**1**. Head to `Project > Manage NuGet Packages`.
+**2**. Search and select the `Owen3H.BepInEx.CSync` package.
+**3**. Choose the latest version and hit Install.
+
+### Thunderstore (Manual)
+**1**. Download the latest version of the library on the [Thunderstore page](https://thunderstore.io/c/lethal-company/p/Owen3H/CSync/).
+**2**. Extract the zip into your game directory root.
+**3**. In your mod's project, add an **Assembly Reference** to `../BepInEx/plugins/CSync.dll`.
+
+## Overview
+- [Usage Guide](/dev/apis/csync/usage-guide) - The guide/tutorial to using this library.
+- [Troubleshooting](/dev/apis/csync/troubleshooting) - Common issues and their solutions.
\ No newline at end of file
diff --git a/docs/dev/apis/csync/troubleshooting.md b/docs/dev/apis/csync/troubleshooting.md
new file mode 100644
index 00000000000..6de67a2ec31
--- /dev/null
+++ b/docs/dev/apis/csync/troubleshooting.md
@@ -0,0 +1,47 @@
+---
+prev: true
+next: false
+description: Troubleshooting section for problems using CSync.
+---
+
+# Troubleshooting
+This page is intended to answer common questions and address frequently encountered issues.
+If you encounter an issue that isn't here, please report it within the [CSync forum](https://discord.com/channels/1168655651455639582/1199756974368227439) in the modding discord.
+
+## Syncing doesn't work when I patch manually.
+
+If you're using `PatchAll()` with type parameters, make sure to patch the `Config` class like other files.
+Example:
+```cs
+harmony.PatchAll(typeof(StartMatchLeverPatch));
+harmony.PatchAll(typeof(GameNetworkManagerPatch));
+harmony.PatchAll(typeof(Config)); // [!code ++]
+```
+
+## I am not seeing any logs from the request/receiver methods?
+
+Harmony may refuse to patch the `InitializeLocalPlayer` method inside `Config.cs` if you have already have a dedicated patch file for `PlayerControllerB`. You can try placing the method there instead.
+```cs
+[HarmonyPatch(typeof(PlayerControllerB))]
+internal class PlayerControllerBPatch {
+ [HarmonyPostfix]
+ [HarmonyPatch("ConnectClientToPlayerObject")]
+ public static void InitializeLocalPlayer() {
+ if (Config.IsHost) {
+ try {
+ Config.MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnRequestConfigSync", Config.OnRequestSync);
+ Config.Synced = true;
+ }
+ catch (Exception e) {
+ Plugin.Logger.LogError(e);
+ }
+
+ return;
+ }
+
+ Config.Synced = false;
+ Config.MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnReceiveConfigSync", Config.OnReceiveSync);
+ Config.RequestSync();
+ }
+}
+```
\ No newline at end of file
diff --git a/docs/dev/apis/csync/usage-guide.md b/docs/dev/apis/csync/usage-guide.md
new file mode 100644
index 00000000000..b054c9242bb
--- /dev/null
+++ b/docs/dev/apis/csync/usage-guide.md
@@ -0,0 +1,160 @@
+---
+prev: false
+next: true
+description: The main guide to using CSync.
+---
+
+# Guide to using CSync
+
+## 1. Creating a serializable config class
+To begin, we will create a new class that will inherit from `SyncedInstance`.
+We must then add the `[DataContract]` attribute for this to be synced with clients.
+
+```cs
+[DataContract]
+public class Config : SyncedInstance
+```
+
+Within this class, we can now begin writing out our config entries that we want to sync.
+We must also mark them with the `[DataMember]` attribute for the serializer to recognize them.
+
+```cs
+[DataContract]
+public class Config : SyncedInstance {
+ public ConfigEntry DISPLAY_DEBUG_INFO { get; private set; }
+
+ [DataMember] public SyncedEntry MOVEMENT_SPEED { get; private set; }
+ [DataMember] public SyncedEntry CLIMB_SPEED { get; private set; }
+}
+```
+
+::: warning
+When using client side and synced entries in the same class, any instance of `ConfigEntry` should **NOT** be marked with `[DataMember]` to avoid BepInEx runtime errors.
+:::
+
+## 2. Binding config entries
+Before binding, we will add the following line at the top of the constructor.
+```cs
+InitInstance(this);
+```
+
+We can now bind our entries to the **BepInEx** config file like usual, however we will use the dedicated `BindSyncedEntry` extension method provided by **CSync**.
+
+```cs
+public Config(ConfigFile cfg) {
+ InitInstance(this);
+
+ MOVEMENT_SPEED = cfg.BindSyncedEntry("Movement", "fMovementSpeed", 4.1f,
+ "The base speed at which the player moves. This is NOT a multiplier."
+ );
+
+ CLIMB_SPEED = cfg.BindSyncedEntry("Movement", "fClimbSpeed", 3.9f,
+ "The base speed at which the player climbs. This is NOT a multiplier."
+ );
+}
+```
+
+## 3. Adding synchronization methods
+We will now place the following methods within the class, making sure to replace `PluginInfo.PLUGIN_GUID` if it is defined elsewhere.
+
+::: warning
+If you specify a GUID that is not unique, your mod will conflict with other mods that use **CSync**.
+:::
+
+```cs
+internal static void RequestSync() {
+ if (!IsClient) return;
+
+ using FastBufferWriter stream = new(IntSize, Allocator.Temp);
+
+ // Method `OnRequestSync` will then get called on host.
+ stream.SendMessage($"{PluginInfo.PLUGIN_GUID}_OnRequestConfigSync");
+}
+
+internal static void OnRequestSync(ulong clientId, FastBufferReader _) {
+ if (!IsHost) return;
+
+ byte[] array = SerializeToBytes(Instance);
+ int value = array.Length;
+
+ using FastBufferWriter stream = new(value + IntSize, Allocator.Temp);
+
+ try {
+ stream.WriteValueSafe(in value, default);
+ stream.WriteBytesSafe(array);
+
+ stream.SendMessage($"{PluginInfo.PLUGIN_GUID}_OnReceiveConfigSync", clientId);
+ } catch(Exception e) {
+ Plugin.Logger.LogError($"Error occurred syncing config with client: {clientId}\n{e}");
+ }
+}
+
+internal static void OnReceiveSync(ulong _, FastBufferReader reader) {
+ if (!reader.TryBeginRead(IntSize)) {
+ Plugin.Logger.LogError("Config sync error: Could not begin reading buffer.");
+ return;
+ }
+
+ reader.ReadValueSafe(out int val, default);
+ if (!reader.TryBeginRead(val)) {
+ Plugin.Logger.LogError("Config sync error: Host could not sync.");
+ return;
+ }
+
+ byte[] data = new byte[val];
+ reader.ReadBytesSafe(ref data, val);
+
+ try {
+ SyncInstance(data);
+ } catch(Exception e) {
+ Plugin.Logger.LogError($"Error syncing config instance!\n{e}");
+ }
+}
+```
+
+## 4. Apply Harmony patches
+Add in the following method and make sure the GUID is defined just like the previous step.
+
+```cs
+[HarmonyPostfix]
+[HarmonyPatch(typeof(PlayerControllerB), "ConnectClientToPlayerObject")]
+public static void InitializeLocalPlayer() {
+ if (IsHost) {
+ MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnRequestConfigSync", OnRequestSync);
+ Synced = true;
+
+ return;
+ }
+
+ Synced = false;
+ MessageManager.RegisterNamedMessageHandler($"{PluginInfo.PLUGIN_GUID}_OnReceiveConfigSync", OnReceiveSync);
+ RequestSync();
+}
+```
+
+Finally, we need to make sure the client reverts back to their own config upon leaving.
+
+```cs
+[HarmonyPostfix]
+[HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")]
+public static void PlayerLeave() {
+ Config.RevertSync();
+}
+```
+
+# Finalizing and Publishing
+It is recommended you inform **BepInEx** that you depend upon **CSync**.
+You can do this by adding a `BepInDependency` attribute and specifying the GUID of this library.
+
+```cs
+[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
+[BepInDependency("io.github.CSync")] // [!code ++]
+public class MyPlugin : BaseUnityPlugin
+```
+
+If you plan to upload your mod to **Thunderstore**, make sure you also specify the dependency within your `manifest.json` file by adding its **Thunderstore** ID to the array.
+```json
+"dependencies": ["BepInEx-BepInExPack-5.4.2100", "Owen3H-CSync-1.0.7"]
+```
+
+**NOTE**: Please ensure your manifest contains the latest version, the one seen above may be outdated!
\ No newline at end of file
diff --git a/docs/dev/apis/overview.md b/docs/dev/apis/overview.md
index 2286fe2ae3e..b99da3c0273 100644
--- a/docs/dev/apis/overview.md
+++ b/docs/dev/apis/overview.md
@@ -36,7 +36,9 @@ APIs marked with a `Gold Star ⭐` have a tutorial on this wiki.
## Programming APIs
### Configuration
-- [ConfigurableCompany by Ansuz/Amrv](https://thunderstore.io/c/lethal-company/p/AMRV/ConfigurableCompany/) implements an in-game menu that allows developers to create file-dependant configurations in a simple way that will automatically synchronize when needed. [Learn how to develop with mod](/dev/apis/configurable-company.md).
+- [ConfigurableCompany by Ansuz/Amrv](https://thunderstore.io/c/lethal-company/p/AMRV/ConfigurableCompany/) implements an in-game menu that allows developers to create file-dependant configurations in a simple way that will automatically synchronize when needed. [Learn how to develop with mod](/dev/apis/configurable-company.md).
+
+- ⭐ [CSync by Owen3H](https://thunderstore.io/c/lethal-company/p/Owen3H/CSync/) is a compact library/API enabling mod authors to synchronize the host's configuration file with all other clients. [The wiki page on CSync](/dev/apis/csync) will guide you through the process of implementing this into your own mod.
### Dungeons
- [LethalLib by Evaisa](https://thunderstore.io/c/lethal-company/p/Evaisa/LethalLib/) allows you to add new dungeons.