/// <summary> /// Returns whether a transfer is allowed to succeed or not. /// </summary> public static bool TransferMaySucceed(XBankAccount FromAccount, XBankAccount ToAccount, Money MoneyNeeded, Journal.BankAccountTransferOptions Options) { if (FromAccount == null || ToAccount == null) { return false; } return ((FromAccount.IsSystemAccount || FromAccount.IsPluginAccount || ((Options & Journal.BankAccountTransferOptions.AllowDeficitOnNormalAccount) == Journal.BankAccountTransferOptions.AllowDeficitOnNormalAccount)) || (FromAccount.Balance >= MoneyNeeded && MoneyNeeded > 0)); }
/// <summary> /// Adds an XBankAccount to the bank account collection. /// </summary> public static XBankAccount AddBankAccount(XBankAccount Account) { lock (__staticLock) { Account.BankAccountK = RandomString(8); XmlJournal.XPathSelectElement("/Journal/BankAccounts").Add((XElement)Account); return Account; } }
/// <summary> /// Asynchronously transfers to another account. /// </summary> public Task<BankTransferEventArgs> TransferToAsync(XBankAccount ToAccount, Money Amount, BankAccountTransferOptions Options, string TransactionMessage, string JournalMessage) { Guid profile = SEconomyPlugin.Profiler.Enter(string.Format("transferAsync: {0} to {1}", this.UserAccountName, ToAccount.UserAccountName)); return Task.Factory.StartNew<BankTransferEventArgs>(() => { BankTransferEventArgs args = TransferTo(ToAccount, Amount, Options, TransactionMessage, JournalMessage); return args; }).ContinueWith((task) => { SEconomyPlugin.Profiler.ExitLog(profile); return task.Result; }); }
/// <summary> /// Adds an XBankAccount to the bank account collection. /// </summary> public static XBankAccount AddBankAccount(XBankAccount Account) { lock (__staticLock) { Account.BankAccountK = RandomString(8); XmlJournal.XPathSelectElement("/Journal/BankAccounts").Add((XElement)Account); return(Account); } }
/// <summary> /// Asynchronously transfers to another account. /// </summary> public Task <BankTransferEventArgs> TransferToAsync(XBankAccount ToAccount, Money Amount, BankAccountTransferOptions Options, string Message = "") { Guid profile = SEconomyPlugin.Profiler.Enter(string.Format("transferAsync: {0} to {1}", this.UserAccountName, ToAccount.UserAccountName)); return(Task.Factory.StartNew <BankTransferEventArgs>(() => { BankTransferEventArgs args = TransferTo(ToAccount, Amount, Options, UseProfiler: false, Message: Message); return args; }).ContinueWith((task) => { SEconomyPlugin.Profiler.ExitLog(profile); return task.Result; })); }
/// <summary> /// Returns a world account for the current running world. If it does not exist, one gets created and then returned. /// </summary> public static XBankAccount EnsureWorldAccountExists() { XBankAccount worldAccount = null; lock (__staticLock) { //World account matches the current world, ignore. if (SEconomyPlugin.WorldAccount != null && SEconomyPlugin.WorldAccount.WorldID == Terraria.Main.worldID) { return(null); } if (Terraria.Main.worldID > 0) { worldAccount = (from i in BankAccounts where (i.Flags & Journal.BankAccountFlags.SystemAccount) == Journal.BankAccountFlags.SystemAccount && (i.Flags & Journal.BankAccountFlags.PluginAccount) == 0 && i.WorldID == Terraria.Main.worldID select i).FirstOrDefault(); //world account does not exist for this world ID, create one if (worldAccount == null) { //This account is always enabled, locked to the world it's in and a system account (ie. can run into deficit) but not a plugin account XBankAccount newWorldAcc = new XBankAccount("SYSTEM", Terraria.Main.worldID, Journal.BankAccountFlags.Enabled | Journal.BankAccountFlags.LockedToWorld | Journal.BankAccountFlags.SystemAccount, "World account for world " + Terraria.Main.worldName); worldAccount = AddBankAccount(newWorldAcc); } if (worldAccount != null && !string.IsNullOrEmpty(worldAccount.BankAccountK)) { //Is this account listed as enabled? bool accountEnabled = (worldAccount.Flags & Journal.BankAccountFlags.Enabled) == Journal.BankAccountFlags.Enabled; if (!accountEnabled) { TShockAPI.Log.ConsoleError("The world account for world " + Terraria.Main.worldName + " is disabled. Currency will not work for this game."); return(null); } } else { TShockAPI.Log.ConsoleError("There was an error loading the bank account for this world. Currency will not work for this game."); } } } return(worldAccount); }
XTransaction FinishEndTransaction(string SourceBankTransactionKey, XBankAccount ToAccount, Money Amount, string Message) { XTransaction destTran = new XTransaction(Amount); destTran.BankAccountFK = ToAccount.BankAccountK; destTran.Flags = Journal.BankAccountTransactionFlags.FundsAvailable; destTran.TransactionDateUtc = DateTime.UtcNow; destTran.Amount = Amount; destTran.BankAccountTransactionFK = SourceBankTransactionKey; if (!string.IsNullOrEmpty(Message)) { destTran.Message = Message; } lock (TransactionJournal.XmlJournal) { return Journal.TransactionJournal.AddTransaction(destTran); } }
XTransaction FinishEndTransaction(string SourceBankTransactionKey, XBankAccount ToAccount, Money Amount, string Message) { XTransaction destTran = new XTransaction(Amount); destTran.BankAccountFK = ToAccount.BankAccountK; destTran.Flags = Journal.BankAccountTransactionFlags.FundsAvailable; destTran.TransactionDateUtc = DateTime.UtcNow; destTran.Amount = Amount; destTran.BankAccountTransactionFK = SourceBankTransactionKey; if (!string.IsNullOrEmpty(Message)) { destTran.Message = Message; } lock (TransactionJournal.XmlJournal) { return(Journal.TransactionJournal.AddTransaction(destTran)); } }
/// <summary> /// Deletes all transactions for an account, effectively returning their balance back to 0. /// </summary> public static void ResetAccountTransactions(string BankAccountK) { lock (__staticLock) { do { XElement trans = XmlJournal.XPathSelectElement(string.Format("/Journal/Transactions/Transaction[@BankAccountFK=\"{0}\"]", BankAccountK)); if (trans == null) { break; } var sourceTransactionFK = trans.Attribute("BankAccountTransactionFK").Value; XElement sourceTrans = XmlJournal.XPathSelectElement(string.Format("/Journal/Transactions/Transaction[@BankAccountTransactionK=\"{0}\"]", sourceTransactionFK)); trans.Remove(); sourceTrans.Remove(); } while (true); XBankAccount account = GetBankAccount(BankAccountK); account.SyncBalanceAsync(); } }
/// <summary> /// Occurs when a player's bank account flags change /// </summary> void BankAccount_BankAccountFlagsChanged(object sender, Journal.BankAccountChangedEventArgs e) { Journal.XBankAccount bankAccount = sender as Journal.XBankAccount; Economy.EconomyPlayer player = GetEconomyPlayerByBankAccountNameSafe(bankAccount.UserAccountName); //You can technically make payments to anyone even if they are offline. //This serves as a basic online check as we don't give a f**k about informing //an offline person that their account has been disabled or not. if (player != null) { bool enabled = (e.NewFlags & Journal.BankAccountFlags.Enabled) == Journal.BankAccountFlags.Enabled; TSPlayer caller = TShock.Players[e.CallerID]; if (player.TSPlayer.Name == caller.Name) { player.TSPlayer.SendInfoMessageFormat("bank: Your bank account has been {0}d.", enabled ? "enable" : "disable"); } else { player.TSPlayer.SendInfoMessageFormat("bank: {1} {0}d your account.", enabled ? "enable" : "disable", caller.Name); } } }
/// <summary> /// Returns a world account for the current running world. If it does not exist, one gets created and then returned. /// </summary> public static XBankAccount EnsureWorldAccountExists() { XBankAccount worldAccount = null; lock (__staticLock) { //World account matches the current world, ignore. if (SEconomyPlugin.WorldAccount != null && SEconomyPlugin.WorldAccount.WorldID == Terraria.Main.worldID) { return null; } if (Terraria.Main.worldID > 0) { worldAccount = (from i in BankAccounts where (i.Flags & Journal.BankAccountFlags.SystemAccount) == Journal.BankAccountFlags.SystemAccount && (i.Flags & Journal.BankAccountFlags.PluginAccount) == 0 && i.WorldID == Terraria.Main.worldID select i).FirstOrDefault(); //world account does not exist for this world ID, create one if (worldAccount == null) { //This account is always enabled, locked to the world it's in and a system account (ie. can run into deficit) but not a plugin account XBankAccount newWorldAcc = new XBankAccount("SYSTEM", Terraria.Main.worldID, Journal.BankAccountFlags.Enabled | Journal.BankAccountFlags.LockedToWorld | Journal.BankAccountFlags.SystemAccount, "World account for world " + Terraria.Main.worldName); worldAccount = AddBankAccount(newWorldAcc); } if (worldAccount != null && !string.IsNullOrEmpty(worldAccount.BankAccountK)) { //Is this account listed as enabled? bool accountEnabled = (worldAccount.Flags & Journal.BankAccountFlags.Enabled) == Journal.BankAccountFlags.Enabled; if (!accountEnabled) { TShockAPI.Log.ConsoleError("The world account for world " + Terraria.Main.worldName + " is disabled. Currency will not work for this game."); return null; } } else { TShockAPI.Log.ConsoleError("There was an error loading the bank account for this world. Currency will not work for this game."); } } } return worldAccount; }
/// <summary> /// Transfers money from this account to the destination account, if negative, takes money from the destination account into this account. /// </summary> public BankTransferEventArgs TransferTo(XBankAccount ToAccount, Money Amount, BankAccountTransferOptions Options, bool UseProfiler = true, string Message = "") { lock (__tranlock) { BankTransferEventArgs args = new BankTransferEventArgs(); Guid profile = Guid.Empty; if (UseProfiler) { profile = SEconomyPlugin.Profiler.Enter(string.Format("transfer: {0} to {1}", this.UserAccountName, ToAccount.UserAccountName)); } if (ToAccount != null && TransferMaySucceed(this, ToAccount, Amount, Options)) { args.Amount = Amount; args.SenderAccount = this; args.ReceiverAccount = ToAccount; args.TransferOptions = Options; args.TransferSucceeded = false; //insert the source negative transaction XTransaction sourceTran = BeginSourceTransaction(Amount, Message); if (sourceTran != null && !string.IsNullOrEmpty(sourceTran.BankAccountTransactionK)) { //insert the destination inverse transaction XTransaction destTran = FinishEndTransaction(sourceTran.BankAccountTransactionK, ToAccount, Amount, Message); if (destTran != null && !string.IsNullOrEmpty(destTran.BankAccountTransactionK)) { //perform the double-entry binding BindTransactions(ref sourceTran, ref destTran); args.TransactionID = sourceTran.BankAccountTransactionK; //update balances this.Balance += (Amount * (-1)); ToAccount.Balance += Amount; //transaction complete args.TransferSucceeded = true; } } } else { args.TransferSucceeded = false; if (!ToAccount.IsSystemAccount && !ToAccount.IsPluginAccount) { if (Amount < 0) { this.Owner.TSPlayer.SendErrorMessageFormat("Invalid amount."); } else { this.Owner.TSPlayer.SendErrorMessageFormat("You need {0} more money to make this payment.", ((Money)(this.Balance - Amount)).ToLongString()); } } } //raise the transfer event OnBankTransferComplete(args); if (UseProfiler) { SEconomyPlugin.Profiler.ExitLog(profile); } return(args); } }
/// <summary> /// Returns whether a transfer is allowed to succeed or not. /// </summary> public static bool TransferMaySucceed(XBankAccount FromAccount, XBankAccount ToAccount, Money MoneyNeeded, Journal.BankAccountTransferOptions Options) { return((FromAccount.IsSystemAccount || FromAccount.IsPluginAccount || ((Options & Journal.BankAccountTransferOptions.AllowDeficitOnNormalAccount) == Journal.BankAccountTransferOptions.AllowDeficitOnNormalAccount)) || (FromAccount.Balance >= MoneyNeeded && MoneyNeeded > 0)); }
/// <summary> /// Transfers money from this account to the destination account, if negative, takes money from the destination account into this account. /// </summary> public BankTransferEventArgs TransferTo(XBankAccount ToAccount, Money Amount, BankAccountTransferOptions Options, string TransactionMessage, string JournalMessage) { BankTransferEventArgs args = new BankTransferEventArgs(); Guid profile = Guid.Empty; try { lock (__tranlock) { if (ToAccount != null) { if (TransferMaySucceed(this, ToAccount, Amount, Options)) { if (SEconomyPlugin.Configuration.EnableProfiler) { profile = SEconomyPlugin.Profiler.Enter(string.Format("transfer: {0} to {1}", !string.IsNullOrEmpty(this.UserAccountName) ? this.UserAccountName : "Unknown", ToAccount != null && !string.IsNullOrEmpty(ToAccount.UserAccountName) ? ToAccount.UserAccountName : "Unknown")); } args.Amount = Amount; args.SenderAccount = this; args.ReceiverAccount = ToAccount; args.TransferOptions = Options; args.TransferSucceeded = false; args.TransactionMessage = TransactionMessage; //insert the source negative transaction XTransaction sourceTran = BeginSourceTransaction(Amount, JournalMessage); if (sourceTran != null && !string.IsNullOrEmpty(sourceTran.BankAccountTransactionK)) { //insert the destination inverse transaction XTransaction destTran = FinishEndTransaction(sourceTran.BankAccountTransactionK, ToAccount, Amount, JournalMessage); if (destTran != null && !string.IsNullOrEmpty(destTran.BankAccountTransactionK)) { //perform the double-entry binding BindTransactions(ref sourceTran, ref destTran); args.TransactionID = sourceTran.BankAccountTransactionK; //update balances this.Balance += (Amount * (-1)); ToAccount.Balance += Amount; //transaction complete args.TransferSucceeded = true; } } } else { args.TransferSucceeded = false; //concept: ?????? //if the amount coming from "this" account is a negative then the "sender account" needs to know the transfer failed. //if the amount coming from "this" acount is a positive then the "reciever account" needs to know the transfer failed. if (!ToAccount.IsSystemAccount && !ToAccount.IsPluginAccount) { if (this.Owner != null) { if (Amount < 0) { this.Owner.TSPlayer.SendErrorMessageFormat("Invalid amount."); } else { this.Owner.TSPlayer.SendErrorMessageFormat("You need {0} more money to make this payment.", ((Money)(this.Balance - Amount)).ToLongString()); } } } } } //raise the transfer event OnBankTransferComplete(args); if (SEconomyPlugin.Configuration.EnableProfiler) { SEconomyPlugin.Profiler.ExitLog(profile); } } } catch (Exception ex) { args.Exception = ex; args.TransferSucceeded = false; } return args; }
/// <summary> /// Compresses all transactions into one line. Doing this is going to remove all transaction history but you gain space and processing speed /// </summary> public static void SquashJournal() { int bankAccountCount = BankAccounts.Count(); int tranCount = Transactions.Count(); XDocument newJournal = NewJournal(); bool responsibleForTurningBackupsBackOn = false; Console.WriteLine("seconomy xml: beginning Squash"); if (SEconomyPlugin.BackupCanRun == true) { SEconomyPlugin.BackupCanRun = false; responsibleForTurningBackupsBackOn = true; } for (int i = 0; i < bankAccountCount; i++) { XBankAccount account = BankAccounts.ElementAtOrDefault(i); if (account != null) { //update account balance account.SyncBalance(); string line = string.Format("\r [squash] {0:p} {1}", (double)i / (double)bankAccountCount, account.UserAccountName); SEconomyPlugin.FillWithSpaces(ref line); Console.Write(line); //copy the bank account from the old journal into the new one newJournal.Element("Journal").Element("BankAccounts").Add((XElement)account); //Add the squished summary XTransaction transSummary = new XTransaction(account.Balance); transSummary.BankAccountTransactionK = RandomString(13); transSummary.BankAccountFK = account.BankAccountK; transSummary.Flags = BankAccountTransactionFlags.FundsAvailable | BankAccountTransactionFlags.Squashed; transSummary.Message = "Transaction squash"; transSummary.TransactionDateUtc = DateTime.UtcNow; newJournal.Element("Journal").Element("Transactions").Add((XElement)transSummary); } } //abandon the old journal and assign the squashed one XmlJournal = newJournal; Console.WriteLine("re-syncing online accounts."); foreach (XBankAccount account in BankAccounts) { XBankAccount runtimeAccount = GetBankAccount(account.BankAccountK); if (runtimeAccount != null && runtimeAccount.Owner != null) { Console.WriteLine("re-syncing {0}", runtimeAccount.Owner.TSPlayer.Name); runtimeAccount.SyncBalance(); } } SaveXml(Configuration.JournalPath); //the backups could already have been disabled by something else. We don't want to be the ones turning it back on if (responsibleForTurningBackupsBackOn) { SEconomyPlugin.BackupCanRun = true; } }