Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
Owen3H committed Feb 27, 2024
2 parents 381d968 + edd5774 commit 05d7349
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 27 deletions.
12 changes: 10 additions & 2 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export default defineConfig({
{ text: 'Coding Our AI', link: '/dev/apis/lethallib/custom-enemies/coding-ai'},
]
},

]
},
{
Expand All @@ -158,7 +157,16 @@ export default defineConfig({
link: '/dev/apis/configurable-company/event-listening'
}
]
}
},
{
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' }
]
},
]
}
]
Expand Down
15 changes: 8 additions & 7 deletions docs/dev/advanced/networking.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ Hooking to a ClientRpc can cause errors with code running multiple times on the

Now that we've finished the networking code, all that's left is to patch the compiled mod assembly with Unity Netcode Patcher. Before we can do so, we need to prepare the mod for patching.

1. Make sure that there is a .pdb file when you build your plugin. If it is not there, make sure that Debug Symbols is set to `Portable` and not embedded.
1. Make sure that `DebugSymbols` are set to `portable` (or `pdbonly`) or `embedded`. `Full` debug symbols **will not work** and NetcodePatcher will produce an uninterpretable error message.

2. Add the following code to your main Plugin.cs file, and make sure the method only runs **once**:

Expand Down Expand Up @@ -460,16 +460,17 @@ static void Awake()
}
```

Before you can run Unity Netcode Patcher, make sure it's set up - specifically, copy the contents of `Lethal Company/Lethal Company_Data/Managed` into `NetcodePatcher/deps`.
Before you can run Unity Netcode Patcher, make sure it's set up - specifically, run `dotnet tool install -g Evaisa.NetcodePatcher.Cli`.

Now, you just have to add the following Post-Build Event to your project:
Now, you just have to add the following `Target` element to your `.csproj`:

```cmd
cd <NetcodePatcher Folder Directory Here>
NetcodePatcher.dll $(TargetDir) deps/
```xml
<Target Name="NetcodePatch" AfterTargets="PostBuildEvent">
<Exec Command="netcode-patch &quot;$(TargetPath)&quot; @(ReferencePathWithRefAssemblies->'&quot;%(Identity)&quot;', ' ')"/>
</Target>
```

If this does not work, you can [follow the instructions on how to use Unity Netcode Patcher from the command line.](https://github.com/EvaisaDev/UnityNetcodePatcher#usage-from-command-line)
If this does not work, you can [follow the instructions on how to use Unity Netcode Patcher.](https://github.com/EvaisaDev/UnityNetcodePatcher#usage-from-command-line)

## Conclusion

Expand Down
33 changes: 33 additions & 0 deletions docs/dev/apis/csync.md
Original file line number Diff line number Diff line change
@@ -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).<br>
This will automatically include both an assembly reference and documentation as opposed to Thunderstore.

### NuGet (Recommended)
**1**. Open the terminal in Visual Studio.<br>
**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`.<br>
**2**. Search and select the `Owen3H.BepInEx.CSync` package.<br>
**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/).<br>
**2**. Extract the zip into your game directory root.<br>
**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.
47 changes: 47 additions & 0 deletions docs/dev/apis/csync/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -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.<br>
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.<br>
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();
}
}
```
162 changes: 162 additions & 0 deletions docs/dev/apis/csync/usage-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
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`.<br>
We must then add the `[DataContract]` attribute for this to be synced with clients.

```cs
[DataContract]
public class Config : SyncedInstance<Config>
```

Within this class, we can now begin writing out our config entries that we want to sync.<br>
We must also mark them with the `[DataMember]` attribute for the serializer to recognize them.

```cs
[DataContract]
public class Config : SyncedInstance<Config> {
public ConfigEntry<float> DISPLAY_DEBUG_INFO { get; private set; }

[DataMember] public SyncedEntry<float> MOVEMENT_SPEED { get; private set; }
[DataMember] public SyncedEntry<float> 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**.<br>
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.8"]
```

::: info NOTE
Please ensure your manifest contains the latest version, the one seen above may be outdated!
:::
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ If you have absolutely no experience with Blender, the 5 first parts of this ser
## Materials, Texturing & UV Unwrapping

::: info
Unity does not understand Blender's shader node system. If you use it for anything other than the princibled BSDF, you will have to bake your material as a texture before it will work in Unity. Also make note of the fact that Lethal Company automatically adds its own "style" to everything, so you don't need to worry about that. However, textures are not necessary so you can basically skip this section entirely.
Unity does not understand Blender's shader node system. If you use it for anything other than the principled BSDF, you will have to bake your material as a texture before it will work in Unity. Also make note of the fact that Lethal Company automatically adds its own "style" to everything, so you don't need to worry about that. However, textures are not necessary so you can basically skip this section entirely.
:::

- [Blender 4.0 - Texture Painting quick start guide](https://youtu.be/iwWoXMWzC_c) - Jamie Dunbar
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/apis/lethallib/custom-enemies/coding-ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ We've now converted our AI into a state machine by using an enum! This helps you
### Networking - Unity Docs

::: warning IMPORTANT
We are using [Unity Netcode Patcher](https://github.com/EvaisaDev/UnityNetcodePatcher) MSBuild plugin to make our custom Rpc methods work.
We are using [Unity Netcode Patcher](https://github.com/EvaisaDev/UnityNetcodePatcher) to make our custom Rpc methods work.
:::

[ClientRpc](https://docs-multiplayer.unity3d.com/netcode/current/advanced-topics/message-system/clientrpc/)
Expand Down
Loading

0 comments on commit 05d7349

Please sign in to comment.