// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.GameLogic; using System.Diagnostics; using System.Threading; using MUnique.OpenMU.GameLogic.Attributes; using MUnique.OpenMU.Pathfinding; using Nito.AsyncEx; /// /// Money which got dropped on the ground of a map. /// public sealed class DroppedMoney : AsyncDisposable, ILocateable { /// /// Gets the pickup lock. Used to synchronize pick up requests from the players. /// private readonly AsyncLock _pickupLock; private Timer? _removeTimer; private bool _availableToPick = true; /// /// Initializes a new instance of the class. /// /// The amount. /// The position where the item was dropped on the map. /// The map. public DroppedMoney(uint amount, Point position, GameMap map) { this.Amount = amount; this._pickupLock = new(); this.Position = position; this.CurrentMap = map; this._removeTimer = new Timer(this.OnTimerTimeout, null, (int)map.ItemDropDuration.TotalMilliseconds, Timeout.Infinite); } /// /// Gets the money item. /// public uint Amount { get; } /// public Point Position { get; set; } /// /// Gets or sets the identifier. /// public ushort Id { get; set; } /// public GameMap CurrentMap { get; } /// /// Tries to pick the money by the specified player. /// /// The player. /// /// The success. /// /// /// Can be overwritten, for example for quest items which dropped only for a specific player. /// public async ValueTask TryPickUpByAsync(Player player) { player.Logger.LogDebug("Player {0} tries to pick up {1}", player, this); using (await this._pickupLock.LockAsync()) { if (!this._availableToPick) { player.Logger.LogDebug("Picked up by another player in the mean time, Player {0}, Money {1}", player, this); return false; } this._availableToPick = false; } if (player.Party is { } party) { var partyMembers = party.PartyList .OfType() .Where(p => p.CurrentMap == player.CurrentMap && !p.IsAtSafezone() && p.Attributes is { }) .ToList(); if (partyMembers.Count > 0) { var share = (int)(this.Amount / partyMembers.Count); foreach (var member in partyMembers) { member.TryAddMoney((int)(share * member.Attributes![Stats.MoneyAmountRate])); } } } else { var clampMoneyOnPickup = player.GameContext?.Configuration?.ClampMoneyOnPickup ?? false; if (clampMoneyOnPickup) { var maxMoney = player.GameContext?.Configuration?.MaximumInventoryMoney ?? int.MaxValue; var currentMoney = player.Money; var amountToAdd = (int)Math.Min(this.Amount, (uint)Math.Max(0, maxMoney - currentMoney)); if (amountToAdd <= 0) { player.Logger.LogDebug("Player is at maximum money limit, Player {0}, Money {1}", player, this); return false; } if (!player.TryAddMoney(amountToAdd)) { player.Logger.LogDebug("Money could not be added to the inventory, Player {0}, Money {1}", player, this); return false; } } else { if (!player.TryAddMoney((int)this.Amount)) { player.Logger.LogDebug("Money could not be added to the inventory, Player {0}, Money {1}", player, this); return false; } } } await this.DisposeAsync().ConfigureAwait(false); return true; } /// public override string ToString() { return $"Money: {this.Amount} at {this.CurrentMap.Definition.Name} ({this.Position})"; } /// protected override async ValueTask DisposeAsyncCore() { if (this._removeTimer is { } timer) { try { this._removeTimer = null; await timer.DisposeAsync().ConfigureAwait(false); await this.CurrentMap.RemoveAsync(this).ConfigureAwait(false); } catch (Exception e) { Debug.Fail(e.Message, e.StackTrace); } } await base.DisposeAsyncCore().ConfigureAwait(false); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "Catching all Exceptions.")] private async void OnTimerTimeout(object? state) { try { await this.DisposeAsync().ConfigureAwait(false); } catch (Exception ex) { Debug.Fail(ex.Message, ex.StackTrace); } } }