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.