Skip to content

Commit

Permalink
Allow togglable client addons
Browse files Browse the repository at this point in the history
Fixes #91
  • Loading branch information
Extremelyd1 committed Apr 23, 2023
1 parent 005f761 commit edba1bd
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 43 deletions.
15 changes: 13 additions & 2 deletions HKMP/Api/Client/ClientAddon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@ public abstract class ClientAddon : Addon.Addon {
/// <summary>
/// The client API interface.
/// </summary>
protected IClientApi ClientApi { get; private set; }
private IClientApi _clientApi;

/// <inheritdoc cref="_clientApi" />
protected IClientApi ClientApi {
get {
if (this is TogglableClientAddon { Disabled: true }) {
throw new InvalidOperationException("Addon is disabled, cannot use Client API in this state");
}

return _clientApi;
}
}

/// <summary>
/// The logger for logging information.
Expand All @@ -37,7 +48,7 @@ public abstract class ClientAddon : Addon.Addon {
/// </summary>
/// <param name="clientApi">The client API instance.</param>
internal void InternalInitialize(IClientApi clientApi) {
ClientApi = clientApi;
_clientApi = clientApi;

Initialize(clientApi);
}
Expand Down
80 changes: 76 additions & 4 deletions HKMP/Api/Client/ClientAddonManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Hkmp.Api.Client.Networking;
using Hkmp.Game.Settings;
using Hkmp.Logging;
using Hkmp.Networking.Packet.Data;

Expand All @@ -27,6 +28,11 @@ internal class ClientAddonManager {
/// </summary>
private readonly ClientApi _clientApi;

/// <summary>
/// The mod settings instance for storing disabled addons.
/// </summary>
private readonly ModSettings _modSettings;

/// <summary>
/// A list of all loaded addons, the order is important as it is the exact order
/// in which we sent it to the server and are expected to act on when receiving a response.
Expand All @@ -49,8 +55,10 @@ static ClientAddonManager() {
/// Construct the addon manager with the client API.
/// </summary>
/// <param name="clientApi">The client API instance.</param>
public ClientAddonManager(ClientApi clientApi) {
/// <param name="modSettings">The mod setting instance.</param>
public ClientAddonManager(ClientApi clientApi, ModSettings modSettings) {
_clientApi = clientApi;
_modSettings = modSettings;

_addons = new List<ClientAddon>();
_networkedAddons = new Dictionary<(string, string), ClientAddon>();
Expand Down Expand Up @@ -93,6 +101,14 @@ public void LoadAddons() {
continue;
}

// Check if this addon was saved in the mod settings as disabled and then re-disable it
if (
addon is TogglableClientAddon togglableAddon &&
_modSettings.DisabledAddons.Contains(addon.GetName())
) {
togglableAddon.Disabled = true;
}

_addons.Add(addon);

if (addon.NeedsNetwork) {
Expand Down Expand Up @@ -122,12 +138,22 @@ public List<AddonData> GetNetworkedAddonData() {
var addonData = new List<AddonData>();

foreach (var addon in _networkedAddons.Values) {
if (addon is TogglableClientAddon { Disabled: true }) {
continue;
}

addonData.Add(new AddonData(addon.GetName(), addon.GetVersion()));
}

return addonData;
}

/// <summary>
/// Get a read-only list of all loaded addons.
/// </summary>
/// <returns>A read-only list of <see cref="ClientAddon"/> instances.</returns>
public IReadOnlyList<ClientAddon> GetLoadedAddons() => _addons;

/// <summary>
/// Updates the order of all networked addons according to the given order.
/// </summary>
Expand All @@ -137,9 +163,9 @@ public void UpdateNetworkedAddonOrder(byte[] addonOrder) {

// The order of the addons in our local list should stay the same
// between connection and obtaining the addon order from the server
foreach (var addon in _addons) {
// Skip all non-networked addons
if (!addon.NeedsNetwork) {
foreach (var addon in _networkedAddons.Values) {
// Skip addons that are disabled
if (addon is TogglableClientAddon { Disabled: true }) {
continue;
}

Expand Down Expand Up @@ -172,6 +198,52 @@ public void ClearNetworkedAddonIds() {
}
}

/// <summary>
/// Try to enable the addon with the given name.
/// </summary>
/// <param name="addonName">The name of the addon to enable.</param>
/// <returns>True if the addon with the given name was enabled; otherwise false.</returns>
public bool TryEnableAddon(string addonName) {
foreach (var addon in _addons) {
if (addon.GetName() == addonName) {
if (addon is not TogglableClientAddon togglableAddon) {
return false;
}

togglableAddon.Disabled = false;

_modSettings.DisabledAddons.Remove(addon.GetName());

return true;
}
}

return false;
}

/// <summary>
/// Try to disable the addon with the given name.
/// </summary>
/// <param name="addonName">The name of the addon to disable.</param>
/// <returns>True if the addon with the given name was disable; otherwise false.</returns>
public bool TryDisableAddon(string addonName) {
foreach (var addon in _addons) {
if (addon.GetName() == addonName) {
if (addon is not TogglableClientAddon togglableAddon) {
return false;
}

togglableAddon.Disabled = true;

_modSettings.DisabledAddons.Add(addon.GetName());

return true;
}
}

return false;
}

/// <summary>
/// Register an addon class from outside of HKMP.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions HKMP/Api/Client/IClientManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public interface IClientManager {
/// </summary>
IReadOnlyCollection<IClientPlayer> Players { get; }

/// <summary>
/// Disconnect the local client from the server.
/// </summary>
void Disconnect();

/// <summary>
/// Get a specific player by their ID.
/// </summary>
Expand Down
51 changes: 51 additions & 0 deletions HKMP/Api/Client/TogglableClientAddon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;

namespace Hkmp.Api.Client;

/// <summary>
/// Abstract class for a client addon that can be toggled. Extends <see cref="ClientAddon"/>.
/// </summary>
public abstract class TogglableClientAddon : ClientAddon {
/// <summary>
/// Whether this addon is disabled, meaning network is restricted
/// </summary>
private bool _disabled;

/// <inheritdoc cref="_disabled" />
public bool Disabled {
get => _disabled;
internal set {
var valueChanged = _disabled != value;

_disabled = value;

if (!valueChanged) {
return;
}

if (value) {
try {
OnDisable();
} catch (Exception e) {
Logger.Error($"Exception was thrown while calling OnDisable for addon '{GetName()}':\n{e}");
}
} else {
try {
OnEnable();
} catch (Exception e) {
Logger.Error($"Exception was thrown while calling OnEnable for addon '{GetName()}':\n{e}");
}
}
}
}

/// <summary>
/// Callback method for when this addon gets enabled.
/// </summary>
protected abstract void OnEnable();

/// <summary>
/// Callback method for when this addon gets disabled.
/// </summary>
protected abstract void OnDisable();
}
77 changes: 41 additions & 36 deletions HKMP/Game/Client/ClientManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,11 @@ ModSettings modSettings

_commandManager = new ClientCommandManager();
var eventAggregator = new EventAggregator();
RegisterCommands();

var clientApi = new ClientApi(this, _commandManager, uiManager, netClient, eventAggregator);
_addonManager = new ClientAddonManager(clientApi);
_addonManager = new ClientAddonManager(clientApi, _modSettings);

RegisterCommands();

ModHooks.FinishedLoadingModsHook += _addonManager.LoadAddons;

Expand Down Expand Up @@ -229,7 +230,7 @@ ModSettings modSettings

UiManager.InternalChatBox.ChatInputEvent += OnChatInput;

netClient.ConnectEvent += response => uiManager.OnSuccessfulConnect();
netClient.ConnectEvent += _ => uiManager.OnSuccessfulConnect();
netClient.ConnectFailedEvent += OnConnectFailed;

// Register the Hero Controller Start, which is when the local player spawns
Expand Down Expand Up @@ -266,6 +267,7 @@ ModSettings modSettings
private void RegisterCommands() {
_commandManager.RegisterCommand(new ConnectCommand(this));
_commandManager.RegisterCommand(new HostCommand(_serverManager));
_commandManager.RegisterCommand(new AddonCommand(_addonManager, _netClient));
}

/// <summary>
Expand Down Expand Up @@ -296,44 +298,43 @@ public void Connect(string address, int port, string username) {
);
}

/// <summary>
/// Disconnect the local client from the server.
/// </summary>
/// <param name="sendDisconnect">Whether to tell the server we are disconnecting.</param>
public void Disconnect(bool sendDisconnect = true) {
/// <inheritdoc />
public void Disconnect() {
if (_netClient.IsConnected) {
if (sendDisconnect) {
// First send the server that we are disconnecting
Logger.Info("Sending PlayerDisconnect packet");
_netClient.UpdateManager.SetPlayerDisconnect();
}
// Send the server that we are disconnecting
Logger.Info("Sending PlayerDisconnect packet");
_netClient.UpdateManager.SetPlayerDisconnect();

// Then actually disconnect
_netClient.Disconnect();
InternalDisconnect();
}
}

// Let the player manager know we disconnected
_playerManager.OnDisconnect();
/// <summary>
/// Internal logic for disconnecting from the server.
/// </summary>
private void InternalDisconnect() {
_netClient.Disconnect();

// Clear the player data dictionary
_playerData.Clear();
// Let the player manager know we disconnected
_playerManager.OnDisconnect();

_uiManager.OnClientDisconnect();
// Clear the player data dictionary
_playerData.Clear();

_addonManager.ClearNetworkedAddonIds();
_uiManager.OnClientDisconnect();

// Check whether the game is in the pause menu and reset timescale to 0 in that case
if (UIManager.instance.uiState.Equals(UIState.PAUSED)) {
PauseManager.SetTimeScale(0);
}
_addonManager.ClearNetworkedAddonIds();

try {
DisconnectEvent?.Invoke();
} catch (Exception e) {
Logger.Warn(
$"Exception thrown while invoking Disconnect event:\n{e}");
}
} else {
Logger.Warn("Could not disconnect client, it was not connected");
// Check whether the game is in the pause menu and reset timescale to 0 in that case
if (UIManager.instance.uiState.Equals(UIState.PAUSED)) {
PauseManager.SetTimeScale(0);
}

try {
DisconnectEvent?.Invoke();
} catch (Exception e) {
Logger.Warn(
$"Exception thrown while invoking Disconnect event:\n{e}");
}
}

Expand All @@ -358,8 +359,12 @@ private void OnConnectFailed(ConnectFailedResult result) {
var addonVersion = addonData.Version;
var message = $" {addonName} v{addonVersion}";

if (_addonManager.TryGetNetworkedAddon(addonName, addonVersion, out _)) {
message += " (installed)";
if (_addonManager.TryGetNetworkedAddon(addonName, addonVersion, out var addon)) {
if (addon is TogglableClientAddon { Disabled: true }) {
message += " (disabled)";
} else {
message += " (installed)";
}
} else {
message += " (missing)";
}
Expand Down Expand Up @@ -521,7 +526,7 @@ private void OnDisconnect(ServerClientDisconnect disconnect) {
}

// Disconnect without sending the server that we disconnect, because the server knows that already
Disconnect(false);
InternalDisconnect();
}

/// <summary>
Expand Down
Loading

0 comments on commit edba1bd

Please sign in to comment.