using System; using System.Xml.Linq; using Barotrauma.Networking; namespace Barotrauma { internal readonly struct WalletChangedEvent { public readonly Option Owner; public readonly Wallet Wallet; public readonly WalletInfo Info; public readonly WalletChangedData ChangedData; public WalletChangedEvent(Wallet wallet, WalletChangedData changedData, WalletInfo info) { Wallet = wallet; Info = info; ChangedData = changedData; Owner = wallet.Owner; } } [NetworkSerialize] internal struct WalletInfo : INetSerializableStruct { public int RewardDistribution; public int Balance; } /// /// Network message for the server to update wallet values to clients /// internal struct NetWalletUpdate : INetSerializableStruct { [NetworkSerialize(ArrayMaxSize = 256)] public NetWalletTransaction[] Transactions; } /// /// Network message for the client to transfer money between wallets /// [NetworkSerialize] internal struct NetWalletTransfer : INetSerializableStruct { public Option Sender; public Option Receiver; public int Amount; } /// /// Network message for the client to set the salary of someone /// internal struct NetWalletSetSalaryUpdate : INetSerializableStruct { [NetworkSerialize] public ushort Target; [NetworkSerialize(MinValueInt = 0, MaxValueInt = 100)] public int NewRewardDistribution; } /// /// Represents the difference in balance and salary when a wallet gets updated /// Not really used right now but could be used for notifications when receiving funds similar to how talents do it /// [NetworkSerialize] internal struct WalletChangedData : INetSerializableStruct { public Option RewardDistributionChanged; public Option BalanceChanged; public WalletChangedData MergeInto(WalletChangedData other) { other.BalanceChanged = AddOptionalInt(other.BalanceChanged, BalanceChanged); other.RewardDistributionChanged = AddOptionalInt(other.RewardDistributionChanged, RewardDistributionChanged); other.BalanceChanged = TurnToNoneIfZero(other.BalanceChanged); other.RewardDistributionChanged = TurnToNoneIfZero(other.RewardDistributionChanged); return other; static Option AddOptionalInt(Option a, Option b) { return a switch { Some some1 => b switch { Some some2 => Option.Some(some1.Value + some2.Value), None _ => Option.Some(some1.Value), _ => throw new ArgumentOutOfRangeException(nameof(b)) }, None _ => b switch { Some some1 => Option.Some(some1.Value), None _ => Option.None(), _ => throw new ArgumentOutOfRangeException(nameof(b)) }, _ => throw new ArgumentOutOfRangeException(nameof(a)) }; } static Option TurnToNoneIfZero(Option option) { return option switch { Some s => s.Value == 0 ? Option.None() : option, None _ => option, _ => throw new ArgumentOutOfRangeException(nameof(option)) }; } } } /// /// Represents an update that changed the amount of money or salary of the wallet /// [NetworkSerialize] internal struct NetWalletTransaction : INetSerializableStruct { public Option CharacterID; public WalletChangedData ChangedData; public WalletInfo Info; } // ReSharper disable ValueParameterNotUsed internal sealed class InvalidWallet : Wallet { public InvalidWallet(): base(Option.None()) { } public override int Balance { get => 0; set => new InvalidOperationException("Tried to set the balance on an invalid wallet"); } public override int RewardDistribution { get => 0; set => new InvalidOperationException("Tried to set the reward distribution on an invalid wallet"); } } internal partial class Wallet { public static readonly Wallet Invalid = new InvalidWallet(); public const string LowerCaseSaveElementName = "wallet"; private const string AttributeNameBalance = "balance", AttrubuteNameRewardDistribution = "rewarddistribution", SaveElementName = "Wallet"; public readonly Option Owner; private int balance; public virtual int Balance { get => balance; set => balance = ClampBalance(value); } private int rewardDistribution; public virtual int RewardDistribution { get => rewardDistribution; set => rewardDistribution = ClampRewardDistribution(value); } public Wallet(Option owner) { Owner = owner; } public Wallet(Option owner, XElement element): this(owner) { balance = ClampBalance(element.GetAttributeInt(AttributeNameBalance, 0)); rewardDistribution = ClampBalance(element.GetAttributeInt(AttrubuteNameRewardDistribution, 0)); } public XElement Save() { XElement element = new XElement(SaveElementName, new XAttribute(AttributeNameBalance, Balance), new XAttribute(AttrubuteNameRewardDistribution, RewardDistribution)); return element; } public bool TryDeduct(int price) { if (!CanAfford(price)) { return false; } Deduct(price); return true; } public bool CanAfford(int price) => Balance >= price; public void Refund(int price) => Give(price); public void Give(int amount) { Balance += amount; SettingsChanged(balanceChanged: Option.Some(amount), rewardChanged: Option.None()); } public void Deduct(int price) { Balance -= price; SettingsChanged(balanceChanged: Option.Some(-price), rewardChanged: Option.None()); } public void SetRewardDistribution(int value) { int oldValue = RewardDistribution; RewardDistribution = value; SettingsChanged(balanceChanged: Option.None(), rewardChanged: Option.Some(RewardDistribution - oldValue)); } public WalletInfo CreateWalletInfo() { return new WalletInfo { Balance = Balance, RewardDistribution = RewardDistribution }; } public string GetOwnerLogName() => Owner switch { Some { Value: var character } => character.Name, None _ => "the bank", _ => throw new ArgumentOutOfRangeException(nameof(Owner)) }; partial void SettingsChanged(Option balanceChanged, Option rewardChanged); private static int ClampBalance(int value) => Math.Clamp(value, 0, CampaignMode.MaxMoney); private static int ClampRewardDistribution(int value) => Math.Clamp(value, 0, 100); } }