//
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//
namespace MUnique.OpenMU.GameLogic.MiniGames;
using System.Collections.Concurrent;
using System.Threading;
using Nito.Disposables.Internals;
///
/// The context of a devil square game.
///
public sealed class DevilSquareContext : MiniGameContext
{
private readonly ConcurrentDictionary _gameStates = new();
private IReadOnlyCollection<(string Name, int Score, int BonusMoney, int BonusExp)>? _highScoreTable;
///
/// Initializes a new instance of the class.
///
/// The key of this context.
/// The definition of the mini game.
/// The game context, to which this game belongs.
/// The map initializer, which is used when the event starts.
public DevilSquareContext(MiniGameMapKey key, MiniGameDefinition definition, IGameContext gameContext, IMapInitializer mapInitializer)
: base(key, definition, gameContext, mapInitializer)
{
}
///
protected override void OnMonsterDied(object? sender, DeathInformation e)
{
base.OnMonsterDied(sender, e);
if (this._gameStates.TryGetValue(e.KillerName, out var state))
{
state.AddScore(this.Definition.GameLevel);
// todo add money? -> in the original servers, in DS drops no money!
}
}
///
protected override async ValueTask OnGameStartAsync(ICollection players)
{
foreach (var player in players)
{
this._gameStates.TryAdd(player.Name, new PlayerGameState(player));
}
await base.OnGameStartAsync(players).ConfigureAwait(false);
}
///
protected override async ValueTask GameEndedAsync(ICollection finishers)
{
var sortedFinishers = finishers
.Select(f => this._gameStates[f.Name])
.WhereNotNull()
.OrderBy(state => state.Score)
.ToList();
var scoreList = new List<(string Name, int Score, int BonusMoney, int BonusExp)>();
int rank = 0;
foreach (var state in sortedFinishers)
{
rank++;
state.Rank = rank;
var (bonusScore, givenMoney) = await this.GiveRewardsAndGetBonusScoreAsync(state.Player, rank).ConfigureAwait(false);
state.AddScore(bonusScore);
scoreList.Add((
state.Player.Name,
state.Score,
givenMoney,
this.Definition.Rewards.FirstOrDefault(r => r.RewardType == MiniGameRewardType.Experience && (r.Rank is null || r.Rank == rank))?.RewardAmount ?? 0));
}
this._highScoreTable = scoreList.AsReadOnly();
await this.SaveRankingAsync(sortedFinishers.Select(state => (state.Rank, state.Player.SelectedCharacter!, state.Score))).ConfigureAwait(false);
await base.GameEndedAsync(finishers).ConfigureAwait(false);
}
///
protected override async ValueTask ShowScoreAsync(Player player)
{
if (this._highScoreTable is { } table
&& this._gameStates.TryGetValue(player.Name, out var state))
{
await player.InvokeViewPlugInAsync(p => p.ShowScoreTableAsync((byte)state.Rank, table)).ConfigureAwait(false);
}
}
private sealed class PlayerGameState
{
private int _score;
public PlayerGameState(Player player)
{
if (player.SelectedCharacter?.CharacterClass is null)
{
throw new InvalidOperationException($"The player '{player}' is in the wrong state");
}
this.Player = player;
}
public Player Player { get; }
public int Score => this._score;
public int Rank { get; set; }
public void AddScore(int value)
{
Interlocked.Add(ref this._score, value);
}
}
}