diff --git a/SSMP/Api/Addon/AddonLoader.cs b/SSMP/Api/Addon/AddonLoader.cs
index 6529ee9..b6ddcbb 100644
--- a/SSMP/Api/Addon/AddonLoader.cs
+++ b/SSMP/Api/Addon/AddonLoader.cs
@@ -27,7 +27,7 @@ internal abstract class AddonLoader {
];
///
- /// Get the paths for all assembly files in the HKMP directory.
+ /// Get the paths for all assembly files in the SSMP directory.
///
/// A string array containing file paths.
private static string[] GetAssemblyPaths() {
diff --git a/SSMP/Api/Client/IClientManager.cs b/SSMP/Api/Client/IClientManager.cs
index 3f0c73e..93d09ff 100644
--- a/SSMP/Api/Client/IClientManager.cs
+++ b/SSMP/Api/Client/IClientManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using SSMP.Api.Server;
using SSMP.Game;
namespace SSMP.Api.Client;
@@ -8,6 +9,11 @@ namespace SSMP.Api.Client;
/// Client manager that handles the local client and related data.
///
public interface IClientManager {
+ ///
+ /// Class that handles information about players.
+ ///
+ IPlayerManager PlayerManager { get; }
+
///
/// Class that manages player locations on the in-game map.
///
@@ -21,6 +27,7 @@ public interface IClientManager {
///
/// The current team of the local player.
///
+ [Obsolete("Use PlayerManager.LocalPlayerTeam instead.")]
Team Team { get; }
///
@@ -28,6 +35,16 @@ public interface IClientManager {
///
IReadOnlyCollection Players { get; }
+ ///
+ /// A read-only that contains the settings related to gameplay.
+ ///
+ IServerSettings ServerSettings { get; }
+
+ ///
+ /// A read-only that contains the settings related to the client.
+ ///
+ IModSettings ModSettings { get; }
+
///
/// Disconnect the local client from the server.
///
diff --git a/SSMP/Api/Client/IModSettings.cs b/SSMP/Api/Client/IModSettings.cs
new file mode 100644
index 0000000..babf0b8
--- /dev/null
+++ b/SSMP/Api/Client/IModSettings.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace SSMP.Api.Client;
+
+///
+/// Settings related to the client/mod that are accessible to addons.
+///
+public interface IModSettings {
+ ///
+ /// Event triggered whenever any of the mod settings are changed.
+ ///
+ event Action? ChangedEvent;
+
+ ///
+ /// The last used address to join a server.
+ ///
+ string ConnectAddress { get; }
+
+ ///
+ /// The last used port to join a server.
+ ///
+ int ConnectPort { get; }
+
+ ///
+ /// The last used username to join a server.
+ ///
+ string Username { get; }
+
+ ///
+ /// Whether to display a UI element for the ping.
+ ///
+ bool DisplayPing { get; }
+
+ ///
+ /// Whether full synchronisation of bosses, enemies, worlds, and saves is enabled.
+ ///
+ bool FullSynchronisation { get; }
+}
diff --git a/SSMP/Api/Client/IPlayerManager.cs b/SSMP/Api/Client/IPlayerManager.cs
new file mode 100644
index 0000000..23be777
--- /dev/null
+++ b/SSMP/Api/Client/IPlayerManager.cs
@@ -0,0 +1,25 @@
+using System;
+using SSMP.Game;
+
+namespace SSMP.Api.Client;
+
+///
+/// Player manager that handles information about players, such as the local player's team or changes to other players'
+/// teams.
+///
+public interface IPlayerManager {
+ ///
+ /// The team that our local player is on.
+ ///
+ Team LocalPlayerTeam { get; }
+
+ ///
+ /// Event that is called when the local player's team changes.
+ ///
+ event Action? LocalPlayerTeamChangeEvent;
+
+ ///
+ /// Event that is called when any player's (including the local player's) team changes.
+ ///
+ event Action? PlayerTeamChangeEvent;
+}
diff --git a/SSMP/Api/Server/IServerManager.cs b/SSMP/Api/Server/IServerManager.cs
index dc7cf25..c6f78bd 100644
--- a/SSMP/Api/Server/IServerManager.cs
+++ b/SSMP/Api/Server/IServerManager.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using SSMP.Api.Eventing.ServerEvents;
+using SSMP.Game;
using SSMP.Game.Settings;
using SSMP.Networking.Packet.Data;
@@ -97,6 +98,11 @@ public interface IServerManager {
///
event Action PlayerLeaveSceneEvent;
+ ///
+ /// Event that is called when a player's team changes.
+ ///
+ event Action PlayerTeamChangedEvent;
+
///
/// Event that is called when a player sends a chat message.
///
diff --git a/SSMP/Api/Server/IServerSettings.cs b/SSMP/Api/Server/IServerSettings.cs
index c3af1cc..0794d4d 100644
--- a/SSMP/Api/Server/IServerSettings.cs
+++ b/SSMP/Api/Server/IServerSettings.cs
@@ -1,3 +1,4 @@
+using System;
namespace SSMP.Api.Server;
@@ -5,35 +6,40 @@ namespace SSMP.Api.Server;
/// Settings related to gameplay that is shared between server and clients.
///
public interface IServerSettings {
+ ///
+ /// Event triggered whenever any of the server settings are changed.
+ ///
+ event Action? ChangeEvent;
+
///
/// Whether player vs. player damage is enabled.
///
- public bool IsPvpEnabled { get; }
+ bool IsPvpEnabled { get; }
///
/// Whether to always show map icons.
///
- public bool AlwaysShowMapIcons { get; }
+ bool AlwaysShowMapIcons { get; }
///
/// Whether to only broadcast the map icon of a player if they have wayward compass equipped.
///
- public bool OnlyBroadcastMapIconWithCompass { get; }
+ bool OnlyBroadcastMapIconWithCompass { get; }
///
/// Whether to display player names above the player objects.
///
- public bool DisplayNames { get; }
+ bool DisplayNames { get; }
///
/// Whether teams are enabled.
///
- public bool TeamsEnabled { get; }
+ bool TeamsEnabled { get; }
///
/// Whether skins are allowed.
///
- public bool AllowSkins { get; }
+ bool AllowSkins { get; }
// ///
// /// Whether other player's attacks can be parried.
diff --git a/SSMP/Game/Client/ClientManager.cs b/SSMP/Game/Client/ClientManager.cs
index efa2070..483bfb3 100644
--- a/SSMP/Game/Client/ClientManager.cs
+++ b/SSMP/Game/Client/ClientManager.cs
@@ -5,6 +5,7 @@
using Steamworks;
using SSMP.Animation;
using SSMP.Api.Client;
+using SSMP.Api.Server;
using SSMP.Eventing;
using SSMP.Fsm;
using SSMP.Game.Client.Entity;
@@ -167,28 +168,21 @@ internal class ClientManager : IClientManager {
///
private bool _sceneHostDetermined;
- ///
- /// Event for when the server settings change after being received from the server.
- /// The parameter for the action is a copy of the last received server settings.
- ///
- public event Action? ServerSettingsChangedEvent;
-
- ///
- /// Event for when the player's team changes after being received from the server.
- ///
- public event Action? TeamChangedEvent;
-
- ///
- /// Event for when the player's skin changes after being received from the server.
- ///
- public event Action? SkinChangedEvent;
-
#endregion
#region IClientManager properties
+ ///
+ public IPlayerManager PlayerManager => _playerManager;
+
///
public IMapManager MapManager => _mapManager;
+
+ ///
+ public IServerSettings ServerSettings => _serverSettings;
+
+ ///
+ public IModSettings ModSettings => _modSettings;
///
public string Username => !_netClient.IsConnected ? throw new Exception("Client is not connected, username is undefined") : _username!;
@@ -664,10 +658,8 @@ private void OnClientConnect(ServerInfo serverInfo) {
// Update the locally stored server settings
_serverSettings.SetAllProperties(serverInfo.ServerSettingsUpdate.ServerSettings);
- // Call the event that the settings were updated
- ServerSettingsChangedEvent?.Invoke(serverInfo.ServerSettingsUpdate.ServerSettings);
- // Note whether full synchronisation is enabled
+ // Note whether full synchronization is enabled
_fullSynchronisation = serverInfo.FullSynchronisation;
// Register hooks and packet handlers before we load into the game
@@ -1126,8 +1118,6 @@ private void OnServerSettingsUpdated(ServerSettingsUpdate update) {
// Update the settings so callbacks can read updated values
_serverSettings.SetAllProperties(newServerSettings);
- // Call the event that the settings were updated
- ServerSettingsChangedEvent?.Invoke(newServerSettings);
// Only update the player manager if the either PvP or body damage have been changed
if (displayNamesChanged) {
@@ -1145,8 +1135,6 @@ private void OnServerSettingsUpdated(ServerSettingsUpdate update) {
// If the team setting was disabled, we reset all teams and call the event
if (!_serverSettings.TeamsEnabled) {
_playerManager.ResetAllTeams();
-
- TeamChangedEvent?.Invoke(Team.None);
}
// _uiManager.OnTeamSettingChange();
@@ -1156,8 +1144,6 @@ private void OnServerSettingsUpdated(ServerSettingsUpdate update) {
// event
if (allowSkinsChanged && !_serverSettings.AllowSkins) {
_playerManager.ResetAllPlayerSkins();
-
- SkinChangedEvent?.Invoke(0);
}
}
@@ -1300,8 +1286,6 @@ private void OnPlayerSettingUpdate(ClientPlayerSettingUpdate settingUpdate) {
if (settingUpdate.UpdateTypes.Contains(PlayerSettingUpdateType.Team)) {
if (settingUpdate.Self) {
_playerManager.OnPlayerTeamUpdate(true, settingUpdate.Team);
-
- TeamChangedEvent?.Invoke(settingUpdate.Team);
} else {
_playerManager.OnPlayerTeamUpdate(false, settingUpdate.Team, settingUpdate.Id);
}
@@ -1310,8 +1294,6 @@ private void OnPlayerSettingUpdate(ClientPlayerSettingUpdate settingUpdate) {
if (settingUpdate.UpdateTypes.Contains(PlayerSettingUpdateType.Skin)) {
if (settingUpdate.Self) {
_playerManager.OnPlayerSkinUpdate(true, settingUpdate.SkinId);
-
- SkinChangedEvent?.Invoke(settingUpdate.SkinId);
} else {
_playerManager.OnPlayerSkinUpdate(false, settingUpdate.SkinId, settingUpdate.Id);
}
diff --git a/SSMP/Game/Client/ClientPlayerData.cs b/SSMP/Game/Client/ClientPlayerData.cs
index d9acda0..34be73e 100644
--- a/SSMP/Game/Client/ClientPlayerData.cs
+++ b/SSMP/Game/Client/ClientPlayerData.cs
@@ -1,3 +1,4 @@
+using System;
using SSMP.Api.Client;
using SSMP.Internals;
using UnityEngine;
diff --git a/SSMP/Game/Client/PlayerManager.cs b/SSMP/Game/Client/PlayerManager.cs
index a8cdc31..13d7771 100644
--- a/SSMP/Game/Client/PlayerManager.cs
+++ b/SSMP/Game/Client/PlayerManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using SSMP.Api.Client;
using SSMP.Fsm;
using SSMP.Game.Client.Skin;
using SSMP.Game.Settings;
@@ -17,7 +18,7 @@ namespace SSMP.Game.Client;
///
/// Class that manages player objects, spawning and recycling thereof.
///
-internal class PlayerManager {
+internal class PlayerManager : IPlayerManager {
///
/// The name of the game object for the player container prefab.
///
@@ -59,11 +60,6 @@ internal class PlayerManager {
///
private readonly Dictionary _playerData;
- ///
- /// The team that our local player is on.
- ///
- public Team LocalPlayerTeam { get; private set; } = Team.None;
-
///
/// The player container prefab GameObject.
///
@@ -79,6 +75,19 @@ internal class PlayerManager {
///
private readonly Dictionary _activePlayers;
+ ///
+ public Team LocalPlayerTeam { get; private set; } = Team.None;
+
+ ///
+ /// Event for when the local player's team changes after being received from the server.
+ ///
+ public event Action? LocalPlayerTeamChangeEvent;
+
+ ///
+ /// Event for when any player's team changes after being received from the server.
+ ///
+ public event Action? PlayerTeamChangeEvent;
+
public PlayerManager(
ServerSettings serverSettings,
Dictionary playerData
@@ -573,6 +582,9 @@ private void UpdatePlayerTeam(ushort id, Team team) {
Logger.Debug($"Tried to update team for ID {id} while player data did not exists");
return;
}
+
+ // Store the old team for invoking the event later
+ var oldTeam = playerData.Team;
// Update the team in the player data
playerData.Team = team;
@@ -590,6 +602,10 @@ private void UpdatePlayerTeam(ushort id, Team team) {
var textMeshObject = nameObject.GetComponent();
ChangeNameColor(textMeshObject, team);
+
+ if (oldTeam != team) {
+ PlayerTeamChangeEvent?.Invoke(playerData, team);
+ }
}
///
@@ -597,6 +613,7 @@ private void UpdatePlayerTeam(ushort id, Team team) {
///
/// The new team of the local player.
private void UpdateLocalPlayerTeam(Team team) {
+ var oldTeam = LocalPlayerTeam;
LocalPlayerTeam = team;
var nameObject = HeroController.instance.gameObject.FindGameObjectInChildren(UsernameObjectName);
@@ -608,6 +625,10 @@ private void UpdateLocalPlayerTeam(Team team) {
var textMeshObject = nameObject.GetComponent();
ChangeNameColor(textMeshObject, team);
+
+ if (oldTeam != team) {
+ LocalPlayerTeamChangeEvent?.Invoke(team);
+ }
}
///
diff --git a/SSMP/Game/Server/ServerManager.cs b/SSMP/Game/Server/ServerManager.cs
index e4c7d5f..b5c7aba 100644
--- a/SSMP/Game/Server/ServerManager.cs
+++ b/SSMP/Game/Server/ServerManager.cs
@@ -175,6 +175,9 @@ internal abstract class ServerManager : IServerManager {
///
public event Action? PlayerLeaveSceneEvent;
+ ///
+ public event Action? PlayerTeamChangedEvent;
+
///
public event Action? PlayerChatEvent;
@@ -1189,6 +1192,13 @@ public bool TryUpdatePlayerTeam(ushort id, Team team, [MaybeNullWhen(true)] out
return false;
}
+ if (playerData.Team == team) {
+ Logger.Info(" Team is the same as current, won't update team");
+
+ reason = "Already in team";
+ return false;
+ }
+
// Update the team in the player data
playerData.Team = team;
@@ -1205,6 +1215,8 @@ public bool TryUpdatePlayerTeam(ushort id, Team team, [MaybeNullWhen(true)] out
);
}
+ PlayerTeamChangedEvent?.Invoke(playerData, team);
+
reason = null;
return true;
}
diff --git a/SSMP/Game/Settings/ModSettings.cs b/SSMP/Game/Settings/ModSettings.cs
index 17318eb..8d9aae8 100644
--- a/SSMP/Game/Settings/ModSettings.cs
+++ b/SSMP/Game/Settings/ModSettings.cs
@@ -1,6 +1,7 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
+using SSMP.Api.Client;
using SSMP.Serialization;
using SSMP.Ui.Menu;
using SSMP.Util;
@@ -10,11 +11,14 @@ namespace SSMP.Game.Settings;
///
/// Settings class that stores user preferences.
///
-internal class ModSettings {
+internal class ModSettings : IModSettings {
///
/// The name of the file containing the mod settings.
///
private const string ModSettingsFileName = "modsettings.json";
+
+ ///
+ public event System.Action? ChangedEvent;
///
/// The authentication key for the user.
@@ -25,27 +29,47 @@ internal class ModSettings {
/// The keybinds for SSMP.
///
[JsonConverter(typeof(PlayerActionSetConverter))]
- public Keybinds Keybinds { get; set; } = new();
-
- ///
- /// The last used address to join a server.
- ///
- public string ConnectAddress { get; set; } = "";
-
- ///
- /// The last used port to join a server.
- ///
- public int ConnectPort { get; set; } = -1;
-
- ///
- /// The last used username to join a server.
- ///
- public string Username { get; set; } = "";
-
- ///
- /// Whether to display a UI element for the ping.
- ///
- public bool DisplayPing { get; set; } = true;
+ public Keybinds Keybinds { get; } = new();
+
+ ///
+ public string ConnectAddress {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ChangedEvent?.Invoke(nameof(ConnectAddress));
+ }
+ } = "";
+
+ ///
+ public int ConnectPort {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ChangedEvent?.Invoke(nameof(ConnectPort));
+ }
+ } = -1;
+
+ ///
+ public string Username {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ChangedEvent?.Invoke(nameof(Username));
+ }
+ } = "";
+
+ ///
+ public bool DisplayPing {
+ get;
+ init {
+ if (field == value) return;
+ field = value;
+ ChangedEvent?.Invoke(nameof(DisplayPing));
+ }
+ } = true;
///
/// Set of addon names for addons that are disabled by the user.
@@ -53,11 +77,16 @@ internal class ModSettings {
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public HashSet DisabledAddons { get; set; } = [];
- ///
- /// Whether full synchronisation of bosses, enemies, worlds, and saves is enabled.
- ///
+ ///
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
- public bool FullSynchronisation { get; set; } = false;
+ public bool FullSynchronisation {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ChangedEvent?.Invoke(nameof(FullSynchronisation));
+ }
+ }
///
/// The last used server settings in a hosted server.
diff --git a/SSMP/Game/Settings/ServerSettings.cs b/SSMP/Game/Settings/ServerSettings.cs
index 0cb8f4d..fb4414f 100644
--- a/SSMP/Game/Settings/ServerSettings.cs
+++ b/SSMP/Game/Settings/ServerSettings.cs
@@ -10,35 +10,80 @@ namespace SSMP.Game.Settings;
///
public class ServerSettings : IServerSettings, IEquatable {
+ ///
+ public event Action? ChangeEvent;
+
///
[SettingAlias("pvp")]
[ModMenuSetting("PvP", "Player versus Player damage")]
- public bool IsPvpEnabled { get; set; }
+ public bool IsPvpEnabled {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ChangeEvent?.Invoke(nameof(IsPvpEnabled));
+ }
+ }
///
[SettingAlias("globalmapicons")]
[ModMenuSetting("Global Map Icons", "Always show map icons for all players")]
- public bool AlwaysShowMapIcons { get; set; }
+ public bool AlwaysShowMapIcons {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ChangeEvent?.Invoke(nameof(AlwaysShowMapIcons));
+ }
+ }
///
[SettingAlias("compassicon", "compassicons")]
[ModMenuSetting("Compass Map Icons", "Only show map icons when Compass is equipped")]
- public bool OnlyBroadcastMapIconWithCompass { get; set; } = true;
+ public bool OnlyBroadcastMapIconWithCompass {
+ get;
+ init {
+ if (field == value) return;
+ field = value;
+ ChangeEvent?.Invoke(nameof(OnlyBroadcastMapIconWithCompass));
+ }
+ } = true;
///
[SettingAlias("names")]
[ModMenuSetting("Show Names", "Show names of player above their characters")]
- public bool DisplayNames { get; set; } = true;
+ public bool DisplayNames {
+ get;
+ init {
+ if (field == value) return;
+ field = value;
+ ChangeEvent?.Invoke(nameof(DisplayNames));
+ }
+ } = true;
///
[SettingAlias("teams")]
[ModMenuSetting("Teams", "Whether players can join teams")]
- public bool TeamsEnabled { get; set; }
+ public bool TeamsEnabled {
+ get;
+ set {
+ if (field == value) return;
+ field = value;
+ ChangeEvent?.Invoke(nameof(TeamsEnabled));
+ }
+ }
///
[SettingAlias("skins")]
[ModMenuSetting("Skins", "Whether players can have skins")]
- public bool AllowSkins { get; set; } = true;
+ public bool AllowSkins {
+ get;
+ init {
+ if (field == value) return;
+ field = value;
+ ChangeEvent?.Invoke(nameof(AllowSkins));
+ }
+ } = true;
// ///
// [SettingAlias("parries")]
diff --git a/SSMP/Networking/Packet/Data/ServerSettingsUpdate.cs b/SSMP/Networking/Packet/Data/ServerSettingsUpdate.cs
index e64664b..e0f5dab 100644
--- a/SSMP/Networking/Packet/Data/ServerSettingsUpdate.cs
+++ b/SSMP/Networking/Packet/Data/ServerSettingsUpdate.cs
@@ -1,4 +1,4 @@
-using SSMP.Game.Settings;
+using SSMP.Game.Settings;
using SSMP.Logging;
namespace SSMP.Networking.Packet.Data;
diff --git a/SSMP/Ui/Menu/ModMenu.cs b/SSMP/Ui/Menu/ModMenu.cs
index 45cd784..8f888df 100644
--- a/SSMP/Ui/Menu/ModMenu.cs
+++ b/SSMP/Ui/Menu/ModMenu.cs
@@ -20,7 +20,7 @@
namespace SSMP.Ui.Menu;
///
-/// Class for building the HKMP mod menu.
+/// Class for building the SSMP mod menu.
///
internal class ModMenu {
///
@@ -30,7 +30,7 @@ internal class ModMenu {
private const float SettingApplyDelay = 1.5f;
///
- /// The HKMP mod settings instance.
+ /// The SSMP mod settings instance.
///
private readonly ModSettings _modSettings;
@@ -55,9 +55,9 @@ internal class ModMenu {
private readonly List> _serverSettingsChangedCallbacks;
///
- /// The top-level HKMP mod menu.
+ /// The top-level SSMP mod menu.
///
- private MenuScreen _hkmpMenu;
+ private MenuScreen _ssmpMenu;
///
/// The menu containing the client settings. Needs to be a static variable here to allow it to be accessed by
/// lambdas and modified.
@@ -74,7 +74,7 @@ internal class ModMenu {
/// A local copy of the server settings for modification through the menu that will be used to either network to
/// the server or modify our own hosted servers.
///
- private ServerSettings _localServerSettings;
+ private readonly ServerSettings _localServerSettings;
///
/// Coroutine that delays applying new server settings until no more changes are made within a certain time period.