// // 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); } } }