diff --git a/Intersect.Server.Core/Entities/Player.Database.cs b/Intersect.Server.Core/Entities/Player.Database.cs index 68dd68ed99..afe3bb8cc9 100644 --- a/Intersect.Server.Core/Entities/Player.Database.cs +++ b/Intersect.Server.Core/Entities/Player.Database.cs @@ -33,6 +33,26 @@ public partial class Player [NotMapped, JsonIgnore] public long SaveTimer { get; set; } = Timing.Global.Milliseconds + Options.Instance.Processing.PlayerSaveInterval; + [NotMapped, JsonIgnore] + public bool IsSaving + { + get + { + lock (_pendingLogoutLock) + { + if (_pendingLogouts.Contains(Id)) + { + return true; + } + } + + lock (_savingLock) + { + return _saving; + } + } + } + #endregion #region Entity Framework @@ -165,15 +185,6 @@ public static bool PlayerExists(string name) public bool LoadRelationships(PlayerContext playerContext, bool loadBags = false) { - lock (_savingLock) - { - if (_saving) - { - Log.Warn($"Skipping loading relationships for player {Id} because it is being saved."); - return false; - } - } - var entityEntry = playerContext.Players.Attach(this); entityEntry.Collection(p => p.Bank).Load(); entityEntry.Collection(p => p.Hotbar).Load(); diff --git a/Intersect.Server.Core/Entities/Player.cs b/Intersect.Server.Core/Entities/Player.cs index 8c81eadbbf..b5724d5dc8 100644 --- a/Intersect.Server.Core/Entities/Player.cs +++ b/Intersect.Server.Core/Entities/Player.cs @@ -493,6 +493,11 @@ private void Logout(bool softLogout = false) { lock (_savingLock) { + lock (_pendingLogoutLock) + { + _pendingLogouts.Add(Id); + } + _saving = true; } @@ -591,14 +596,17 @@ private void Logout(bool softLogout = false) var stackTrace = default(string); #endif var logoutOperationId = Guid.NewGuid(); - DbInterface.Pool.QueueWorkItem(CompleteLogout, logoutOperationId, stackTrace); + DbInterface.Pool.QueueWorkItem(CompleteLogout, logoutOperationId, softLogout, stackTrace); } #if DIAGNOSTIC private int _logoutCounter = 0; #endif - public void CompleteLogout(Guid logoutOperationId, string? stackTrace = default) + private static readonly HashSet _pendingLogouts = []; + private static readonly object _pendingLogoutLock = new(); + + public void CompleteLogout(Guid logoutOperationId, bool softLogout, string? stackTrace = default) { if (logoutOperationId != default) { @@ -648,10 +656,20 @@ public void CompleteLogout(Guid logoutOperationId, string? stackTrace = default) lock (_savingLock) { + var logoutType = softLogout ? "soft" : "hard"; + Log.Info($"[Player.CompleteLogout] Done saving {Name} ({logoutType} logout, {Id})"); _saving = false; - } - Dispose(); + if (!softLogout) + { + Dispose(); + } + + lock (_pendingLogoutLock) + { + _pendingLogouts.Remove(Id); + } + } #if DIAGNOSTIC Log.Debug($"Finished {nameof(CompleteLogout)}() #{currentExecutionId} on {Name} ({User?.Name})"); diff --git a/Intersect.Server.Core/Localization/Strings.cs b/Intersect.Server.Core/Localization/Strings.cs index def1cee6d6..914e96eb2b 100644 --- a/Intersect.Server.Core/Localization/Strings.cs +++ b/Intersect.Server.Core/Localization/Strings.cs @@ -67,6 +67,9 @@ public sealed partial class AccountNamespace : LocaleNamespace [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public readonly LocalizedString LoadFail = @"Failed to load account. Please try logging in again."; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public readonly LocalizedString PlayerSavingTryAgainLater = @"'{00}' is currently being saved, please try again later."; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public readonly LocalizedString MaxCharacters = @"You have already created the maximum number of characters. Delete one before creating a new one."; diff --git a/Intersect.Server.Core/Networking/PacketHandler.cs b/Intersect.Server.Core/Networking/PacketHandler.cs index 994d13e570..4e0e7e34c0 100644 --- a/Intersect.Server.Core/Networking/PacketHandler.cs +++ b/Intersect.Server.Core/Networking/PacketHandler.cs @@ -2508,13 +2508,33 @@ public void HandlePacket(Client client, SelectCharacterPacket packet) return; } + if (character.IsSaving) + { + PacketSender.SendError( + client, + Strings.Account.PlayerSavingTryAgainLater.ToString(character.Name), + Strings.General.NoticeError + ); + return; + } + + ObjectDisposedException.ThrowIf(character.IsDisposed, character); + client.LoadCharacter(character); - UserActivityHistory.LogActivity(client.User?.Id ?? Guid.Empty, client?.Entity?.Id ?? Guid.Empty, client?.Ip, UserActivityHistory.PeerType.Client, UserActivityHistory.UserAction.SelectPlayer, $"{client?.Name},{client?.Entity?.Name}"); + UserActivityHistory.LogActivity( + client.User?.Id ?? Guid.Empty, + client?.Entity?.Id ?? Guid.Empty, + client?.Ip, + UserActivityHistory.PeerType.Client, + UserActivityHistory.UserAction.SelectPlayer, + $"{client?.Name},{client?.Entity?.Name}" + ); try { client.Entity?.SetOnline(); + PacketSender.SendJoinGame(client); } catch (Exception exception)