Skip to content

Commit

Permalink
Merge pull request #454 from MUnique/dev/fix-playerstate-on-disconnect
Browse files Browse the repository at this point in the history
Fixed player state when leaving the game.
  • Loading branch information
sven-n authored Aug 14, 2024
2 parents 869a157 + 6ff2c78 commit a65f056
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 50 deletions.
2 changes: 1 addition & 1 deletion src/GameLogic/GameContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ private async void RecoverTimerElapsed(object? state)
{
await this.ForEachPlayerAsync(player =>
{
if (player.SelectedCharacter != null && player.PlayerState.CurrentState == PlayerState.EnteredWorld)
if (player.SelectedCharacter != null && !player.PlayerState.CurrentState.IsDisconnectedOrFinished())
{
return player.RegenerateAsync();
}
Expand Down
76 changes: 48 additions & 28 deletions src/GameLogic/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ public async ValueTask WarpToAsync(ExitGate gate)
this.PlaceAtGate(gate);
this.CurrentMap = null; // Will be set again, when the client acknowledged the map change by F3 12 packet.

if (this.PlayerState.CurrentState != GameLogic.PlayerState.Disconnected)
if (!this.PlayerState.CurrentState.IsDisconnectedOrFinished())
{
await this.InvokeViewPlugInAsync<IMapChangePlugIn>(p => p.MapChangeAsync()).ConfigureAwait(false);
}
Expand Down Expand Up @@ -1557,6 +1557,52 @@ public async ValueTask ResetPetBehaviorAsync()
}
}

/// <summary>
/// Removes the player from the game and saves its state.
/// </summary>
public async ValueTask RemoveFromGameAsync()
{
var moveToNextSafezone = false;
if (this._respawnAfterDeathCts is { IsCancellationRequested: false })
{
await this._respawnAfterDeathCts.CancelAsync().ConfigureAwait(false);
moveToNextSafezone = true;
}

if (this.CurrentMiniGame is { })
{
moveToNextSafezone = true;
}

if (this.DuelRoom is { })
{
moveToNextSafezone = true;
}

if (moveToNextSafezone)
{
await this.WarpToSafezoneAsync().ConfigureAwait(false);
}

await this.RemoveFromCurrentMapAsync().ConfigureAwait(false);
if (this.Party is { } party)
{
await party.KickMySelfAsync(this).ConfigureAwait(false);
}

await this.SetSelectedCharacterAsync(null).ConfigureAwait(false);
await this.MagicEffectList.ClearAllEffectsAsync().ConfigureAwait(false);

try
{
await this.PersistenceContext.SaveChangesAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Couldn't save when leaving the game. Player: {player}", this);
}
}

/// <inheritdoc />
protected override async ValueTask DisposeAsyncCore()
{
Expand Down Expand Up @@ -1592,33 +1638,7 @@ protected override async ValueTask DisposeAsyncCore()
/// </summary>
protected virtual async ValueTask InternalDisconnectAsync()
{
var moveToNextSafezone = false;
if (this._respawnAfterDeathCts is { IsCancellationRequested: false })
{
await this._respawnAfterDeathCts.CancelAsync().ConfigureAwait(false);
moveToNextSafezone = true;
}

if (this.CurrentMiniGame is { })
{
moveToNextSafezone = true;
}

if (this.DuelRoom is { })
{
moveToNextSafezone = true;
}

if (moveToNextSafezone)
{
await this.WarpToSafezoneAsync().ConfigureAwait(false);
}

await this.RemoveFromCurrentMapAsync().ConfigureAwait(false);
if (this.Party is { } party)
{
await party.KickMySelfAsync(this).ConfigureAwait(false);
}
await this.RemoveFromGameAsync().ConfigureAwait(false);
}

/// <summary>
Expand Down
14 changes: 1 addition & 13 deletions src/GameLogic/PlayerActions/LogoutAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,7 @@ public class LogoutAction
/// <param name="logoutType">Type of the logout.</param>
public async ValueTask LogoutAsync(Player player, LogoutType logoutType)
{
player.CurrentMap?.RemoveAsync(player);
player.Party?.KickMySelfAsync(player);
await player.SetSelectedCharacterAsync(null).ConfigureAwait(false);
await player.MagicEffectList.ClearAllEffectsAsync().ConfigureAwait(false);

try
{
await player.PersistenceContext.SaveChangesAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
player.Logger.LogError(ex, "Couldn't Save at Disconnect. Player: {player}", player);
}
await player.RemoveFromGameAsync().ConfigureAwait(false);

if (logoutType == LogoutType.CloseGame)
{
Expand Down
14 changes: 12 additions & 2 deletions src/GameLogic/PlayerActions/Trade/TradeAcceptAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,18 @@ public async ValueTask HandleTradeAcceptAsync(ITrader tradeAccepter, bool accept
}
else
{
await this.OpenTradeAsync(tradeAccepter).ConfigureAwait(false);
await this.OpenTradeAsync(tradePartner).ConfigureAwait(false);
try
{
await this.OpenTradeAsync(tradeAccepter).ConfigureAwait(false);
await this.OpenTradeAsync(tradePartner).ConfigureAwait(false);
}
catch (Exception ex)
{
tradeAccepter.Logger.LogError(ex, "Error while opening a trade between {tradeAccepter} and {tradePartner}", tradeAccepter, tradePartner);
await this.CancelTradeAsync(tradeAccepter).ConfigureAwait(false);
await this.CancelTradeAsync(tradePartner).ConfigureAwait(false);
}

}
}
else
Expand Down
14 changes: 13 additions & 1 deletion src/GameLogic/PlayerState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace MUnique.OpenMU.GameLogic;
/// <summary>
/// The states of a player, used by the <see cref="ITrader.PlayerState"/>.
/// </summary>
public class PlayerState
public static class PlayerState
{
static PlayerState()
{
Expand Down Expand Up @@ -80,6 +80,18 @@ static PlayerState()
}

/// <summary>
/// Determines whether the state is <see cref="Disconnected"/> or <see cref="Finished"/>.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>
/// <c>true</c> if the state is <see cref="Disconnected"/> or <see cref="Finished"/>; otherwise, <c>false</c>.
/// </returns>
public static bool IsDisconnectedOrFinished(this State state)
{
return state == Disconnected || state == Finished;
}

/// <summary>
/// Gets the finished state. When this state is active, the player session was saved and the player object can be safely removed from the game.
/// </summary>
public static State Finished { get; } = new (new Guid("AB24C7C4-4F37-40ED-B874-0F6C7984C471"))
Expand Down
3 changes: 1 addition & 2 deletions src/GameLogic/PlugIns/InvasionEvents/BaseInvasionPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ protected bool IsPlayerOnMap(Player player, bool checkForCurrentMap = false)
var state = this.GetStateByGameContext(player.GameContext);

return player.CurrentMap is { } map
&& player.PlayerState.CurrentState != PlayerState.Disconnected
&& player.PlayerState.CurrentState != PlayerState.Finished
&& !player.PlayerState.CurrentState.IsDisconnectedOrFinished()
&& (!checkForCurrentMap || map.MapId == state.MapId);
}

Expand Down
5 changes: 2 additions & 3 deletions src/GameLogic/PlugIns/PeriodicTasks/HappyHourPlugIn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async ValueTask PlayerStateChangedAsync(Player player, State previousStat
{
try
{
if (currentState == PlayerState.Disconnected)
if (currentState.IsDisconnectedOrFinished())
{
player.Attributes?.RemoveElement(this._happyHourExtraMultiplier, Stats.ExperienceRate);
player.Attributes?.RemoveElement(this._happyHourExtraMultiplier, Stats.MasterExperienceRate);
Expand Down Expand Up @@ -100,8 +100,7 @@ protected override ValueTask OnFinishedAsync(PeriodicTaskGameServerState state)
protected bool IsPlayerOnMap(Player player)
{
return player.CurrentMap is not null
&& player.PlayerState.CurrentState != PlayerState.Disconnected
&& player.PlayerState.CurrentState != PlayerState.Finished;
&& !player.PlayerState.CurrentState.IsDisconnectedOrFinished();
}

/// <summary>
Expand Down

0 comments on commit a65f056

Please sign in to comment.