Skip to content

Commit

Permalink
Merge pull request #441 from searica/sync_mode_feature
Browse files Browse the repository at this point in the history
Add SynchronizationModeAttribute to allow mod authors to control how AdminOnly configs are handled.
  • Loading branch information
MSchmoecker authored Nov 30, 2024
2 parents 0d3098d + ca4fc98 commit 01ac931
Show file tree
Hide file tree
Showing 11 changed files with 628 additions and 124 deletions.
17 changes: 17 additions & 0 deletions JotunnLib/Documentation/tutorials/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ Local settings will be overriden by the servers values as long as the client is

Changing the configs at runtime will sync the changes to all clients connected to the server.

## Admin Only Strictness

Usually, the `IsAdminOnly` flag is enforcing players to be admin on the server to change the configuration.
However, without having Jotunn installed on the server the admin status cannot be detected reliably.

To change this behaviour, the `SynchronizationMode` can be set to `AdminOnlyStrictness.IfOnServer`.
This means `IsAdminOnly` configs are only synced and enforced if the server has Jotunn installed.
If not installed on the server, all players are free to change any config values.
This can useful for mods that want to behave like client-only mods on vanilla servers but still sync configs on modded servers.
```cs
[SynchronizationMode(AdminOnlyStrictness.IfOnServer)]
internal class TestMod : BaseUnityPlugin
{
...
}
```

## Synced admin status

Upon connection to a server, Jötunn checks the admin status of the connecting player on that server, given that Jötunn is installed on both sides.
Expand Down
26 changes: 26 additions & 0 deletions JotunnLib/Extensions/PluginExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Linq;
using BepInEx;
using Jotunn.Utils;

namespace Jotunn.Extensions
{
internal static class PluginExtensions
{
internal static NetworkCompatibilityAttribute GetNetworkCompatibilityAttribute(this BaseUnityPlugin plugin)
{
return plugin.GetType()
.GetCustomAttributes(typeof(NetworkCompatibilityAttribute), true)
.Cast<NetworkCompatibilityAttribute>()
.FirstOrDefault();
}

internal static SynchronizationModeAttribute GetSynchronizationModeAttribute(this BaseUnityPlugin plugin)
{
return plugin.GetType()
.GetCustomAttributes(typeof(SynchronizationModeAttribute), true)
.Cast<SynchronizationModeAttribute>()
.FirstOrDefault();
}
}
}
75 changes: 75 additions & 0 deletions JotunnLib/Managers/SynchronizationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using BepInEx.Configuration;
using HarmonyLib;
using Jotunn.Entities;
using Jotunn.Extensions;
using Jotunn.Utils;
using UnityEngine;
using UnityEngine.SceneManagement;
Expand Down Expand Up @@ -464,6 +465,10 @@ private void InvokeOnAdminStatusChanged()
OnAdminStatusChanged?.SafeInvoke();
}

/// <summary>
/// Gets an IEnumerable of all default and custom config files that associated with plugins that have Jotunn as a dependency.
/// </summary>
/// <returns></returns>
private IEnumerable<ConfigFile> GetConfigFiles()
{
var loadedPlugins = BepInExUtils.GetDependentPlugins(true);
Expand All @@ -479,11 +484,71 @@ private IEnumerable<ConfigFile> GetConfigFiles()
}
}

/// <summary>
/// Checks if AdminOnly config entries should be locked based the AdminOnlyStrictness value for the plugin that the
/// config file is attached to (including custom config files) and whether the plugin is installed on the server or not.
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
private bool ShouldManageConfig(ConfigFile config)
{
if (!GetPluginGUID(config, out var pluginGUID))
{
return false;
}

if (!BepInExUtils.GetDependentPlugins().TryGetValue(pluginGUID, out var plugin))
{
return false;
}

return ShouldManageConfig(plugin);
}

/// <summary>
/// Checks if AdminOnly config entries should be locked based the AdminOnlyStrictness value for the plugin
/// and whether the plugin is installed on the server or not.
/// </summary>
/// <param name="plugin"></param>
/// <returns></returns>
private bool ShouldManageConfig(BaseUnityPlugin plugin)
{
if (ModCompatibility.IsModuleOnServer(plugin))
{
return true;
}

// Current behaviour is that AdminOnly config entries are always locked
// if Jotunn is not on the server. So if SynchronizationModeAttribute has
// not been set for the mod then return true to mimic current behaviour and
// maintain backwards compatibility.
SynchronizationModeAttribute syncMode = plugin.GetSynchronizationModeAttribute();
return syncMode == null || syncMode.ShouldAlwaysEnforceAdminOnly();
}

private static string GetFileIdentifier(ConfigFile config)
{
return config.ConfigFilePath.Replace(BepInEx.Paths.ConfigPath, "").Replace("\\", "/").Trim('/');
}

/// <summary>
/// Gets the corresponding Plugin GUID for a config file (works for custom config files)
/// and returns a boolean indicating success or failure.
/// </summary>
/// <param name="config"></param>
/// <param name="pluginGUID"></param>
private bool GetPluginGUID(ConfigFile config, out string pluginGUID)
{
var configFileIdentifier = GetFileIdentifier(config);
return GetPluginGUID(configFileIdentifier, out pluginGUID);
}

/// <summary>
/// Gets the corresponding Plugin GUID for a config file identifier (works for custom config files)
/// and returns a boolean indicating success or failure.
/// </summary>
/// <param name="configFileIdentifier"></param>
/// <param name="pluginGUID"></param>
private bool GetPluginGUID(string configFileIdentifier, out string pluginGUID)
{
if (IsDefaultModConfig(configFileIdentifier, out pluginGUID))
Expand Down Expand Up @@ -556,6 +621,11 @@ private void LockConfigurationEntries()
{
foreach (var config in GetConfigFiles())
{
if (!ShouldManageConfig(config))
{
continue;
}

foreach (var configDefinition in config.Keys)
{
var configEntry = config[configDefinition.Section, configDefinition.Key];
Expand Down Expand Up @@ -808,6 +878,11 @@ private void SetToDefaultConfigEntries()
{
foreach (var config in GetConfigFiles())
{
if (!ShouldManageConfig(config))
{
continue;
}

foreach (var configDefinition in config.Keys)
{
var configEntry = config[configDefinition.Section, configDefinition.Key];
Expand Down
Loading

0 comments on commit 01ac931

Please sign in to comment.