#region References using Server.Commands; using Server.Items; using Server.Misc; using Server.Mobiles; using Server.Multis; using Server.Network; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Cryptography; using System.Text; using System.Xml; #endregion namespace Server.Accounting { [PropertyObject] public class Account : IAccount, IComparable, IComparable { public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0); public static readonly TimeSpan InactiveDuration = TimeSpan.FromDays(180.0); public static readonly TimeSpan EmptyInactiveDuration = TimeSpan.FromDays(30.0); private static MD5CryptoServiceProvider m_MD5HashProvider; private static SHA1CryptoServiceProvider m_SHA1HashProvider; private static SHA512CryptoServiceProvider m_SHA512HashProvider; private static byte[] m_HashBuffer; public static void Configure() { CommandSystem.Register("ConvertCurrency", AccessLevel.Owner, ConvertCurrency); } private static void ConvertCurrency(CommandEventArgs e) { e.Mobile.SendMessage( "Converting All Banked Gold from {0} to {1}. Please wait...", AccountGold.Enabled ? "checks and coins" : "account treasury", AccountGold.Enabled ? "account treasury" : "checks and coins"); NetState.Pause(); double found = 0.0, converted = 0.0; try { BankBox box; List gold; List checks; long share = 0, shared; int diff; foreach (Account a in Accounts.GetAccounts().OfType().Where(a => a.Count > 0)) { try { if (!AccountGold.Enabled) { share = (int)Math.Truncate((a.TotalCurrency / a.Count) * CurrencyThreshold); found += a.TotalCurrency * CurrencyThreshold; } foreach (Mobile m in a.m_Mobiles.Where(m => m != null)) { box = m.FindBankNoCreate(); if (box == null) { continue; } if (AccountGold.Enabled) { foreach (BankCheck o in checks = box.FindItemsByType()) { found += o.Worth; if (!a.DepositGold(o.Worth)) { break; } converted += o.Worth; o.Delete(); } checks.Clear(); checks.TrimExcess(); foreach (Gold o in gold = box.FindItemsByType()) { found += o.Amount; if (!a.DepositGold(o.Amount)) { break; } converted += o.Amount; o.Delete(); } gold.Clear(); gold.TrimExcess(); } else { shared = share; while (shared > 0) { if (shared > 60000) { diff = (int)Math.Min(10000000, shared); if (a.WithdrawGold(diff)) { box.DropItem(new BankCheck(diff)); } else { break; } } else { diff = (int)Math.Min(60000, shared); if (a.WithdrawGold(diff)) { box.DropItem(new Gold(diff)); } else { break; } } converted += diff; shared -= diff; } } box.UpdateTotals(); } } catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } } } catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } NetState.Resume(); e.Mobile.SendMessage("Operation complete: {0:#,0} of {1:#,0} Gold has been converted in total.", converted, found); } private readonly Mobile[] m_Mobiles; private AccessLevel m_AccessLevel; private List m_Comments; private List m_Tags; private TimeSpan m_TotalGameTime; private Timer m_YoungTimer; public Account(string username, string password) { Username = username; SetPassword(password); m_AccessLevel = AccessLevel.Player; Created = LastLogin = DateTime.UtcNow; m_TotalGameTime = TimeSpan.Zero; m_Mobiles = new Mobile[7]; IPRestrictions = new string[0]; LoginIPs = new IPAddress[0]; Accounts.Add(this); } public Account(XmlElement node) { Username = Utility.GetText(node["username"], "empty"); string plainPassword = Utility.GetText(node["password"], null); string MD5Password = Utility.GetText(node["cryptPassword"], null); string SHA1Password = Utility.GetText(node["newCryptPassword"], null); string SHA512Password = Utility.GetText(node["newSecureCryptPassword"], null); switch (AccountHandler.ProtectPasswords) { case PasswordProtection.None: { if (plainPassword != null) { SetPassword(plainPassword); } else if (SHA512Password != null) { _SHA512Password = SHA512Password; } else if (SHA1Password != null) { _SHA1Password = SHA1Password; } else if (MD5Password != null) { _MD5Password = MD5Password; } else { SetPassword("empty"); } break; } case PasswordProtection.Crypt: { if (MD5Password != null) { _MD5Password = MD5Password; } else if (plainPassword != null) { SetPassword(plainPassword); } else if (SHA1Password != null) { _SHA1Password = SHA1Password; } else if (SHA512Password != null) { _SHA512Password = SHA512Password; } else { SetPassword("empty"); } break; } case PasswordProtection.NewCrypt: { if (SHA1Password != null) { _SHA1Password = SHA1Password; } else if (plainPassword != null) { SetPassword(plainPassword); } else if (MD5Password != null) { _MD5Password = MD5Password; } else if (SHA512Password != null) { _SHA512Password = SHA512Password; } else { SetPassword("empty"); } break; } default: // PasswordProtection.NewSecureCrypt { if (SHA512Password != null) { _SHA512Password = SHA512Password; } else if (plainPassword != null) { SetPassword(plainPassword); } else if (SHA1Password != null) { _SHA1Password = SHA1Password; } else if (MD5Password != null) { _MD5Password = MD5Password; } else { SetPassword("empty"); } break; } } Enum.TryParse(Utility.GetText(node["accessLevel"], "Player"), true, out m_AccessLevel); Flags = Utility.GetXMLInt32(Utility.GetText(node["flags"], "0"), 0); Created = Utility.GetXMLDateTime(Utility.GetText(node["created"], null), DateTime.UtcNow); LastLogin = Utility.GetXMLDateTime(Utility.GetText(node["lastLogin"], null), DateTime.UtcNow); TotalCurrency = Utility.GetXMLDouble(Utility.GetText(node["totalCurrency"], "0"), 0); Sovereigns = Utility.GetXMLInt32(Utility.GetText(node["sovereigns"], "0"), 0); m_Mobiles = LoadMobiles(node); m_Comments = LoadComments(node); m_Tags = LoadTags(node); LoginIPs = LoadAddressList(node); IPRestrictions = LoadAccessCheck(node); foreach (Mobile m in m_Mobiles.Where(m => m != null)) { m.Account = this; } TimeSpan totalGameTime = Utility.GetXMLTimeSpan(Utility.GetText(node["totalGameTime"], null), TimeSpan.Zero); if (totalGameTime == TimeSpan.Zero) { totalGameTime = m_Mobiles.OfType().Aggregate(totalGameTime, (current, m) => current + m.GameTime); } m_TotalGameTime = totalGameTime; if (Young) { CheckYoung(); } LoadSecureAccounts(node); Accounts.Add(this); } /// /// Deserializes a list of secure account balances, and converts it to a dictionary containing the account characters /// /// public void LoadSecureAccounts(XmlElement node) { int[] list = new int[7]; XmlElement chars = node["SecureAccounts"]; if (chars != null) { foreach (XmlElement ele in chars.GetElementsByTagName("char")) { try { int index = Utility.GetXMLInt32(Utility.GetAttribute(ele, "index", "0"), 0); int balance = Utility.GetXMLInt32(Utility.GetText(ele, "0"), 0); if (balance > 0 && index >= 0 && index < list.Length && index < m_Mobiles.Length) { if (SecureAccounts == null) SecureAccounts = new Dictionary(); SecureAccounts[m_Mobiles[index]] = balance; } } catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); } } } } /// /// Object detailing information about the hardware of the last person to log into this account /// [CommandProperty(AccessLevel.Administrator)] public HardwareInfo HardwareInfo { get; set; } /// /// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP /// addresses are allowed. /// public string[] IPRestrictions { get; set; } /// /// List of IP addresses which have successfully logged into this account. /// public IPAddress[] LoginIPs { get; set; } /// /// List of account comments. Type of contained objects is AccountComment. /// public List Comments => m_Comments ?? (m_Comments = new List()); /// /// List of account tags. Type of contained objects is AccountTag. /// public List Tags => m_Tags ?? (m_Tags = new List()); /// /// Account password. Plain text. Case sensitive validation. May be null. /// public string PlainPassword { get; set; } /// /// Account password. Hashed with MD5. May be null. /// public string _MD5Password { get; set; } /// /// Account username and password hashed with SHA1. May be null. /// public string _SHA1Password { get; set; } /// /// Account username and password hashed with SHA512. May be null. /// public string _SHA512Password { get; set; } /// /// Internal bitfield of account flags. Consider using direct access properties (Banned, Young), or GetFlag/SetFlag /// methods /// public int Flags { get; set; } /// /// Gets or sets a flag indiciating if this account is banned. /// [CommandProperty(AccessLevel.Administrator)] public bool Banned { get { bool isBanned = GetFlag(0); if (!isBanned) { return false; } DateTime banTime; TimeSpan banDuration; if (!GetBanTags(out banTime, out banDuration) || banDuration == TimeSpan.MaxValue || DateTime.UtcNow < (banTime + banDuration)) { return true; } SetUnspecifiedBan(null); // clear Banned = false; return false; } set { SetFlag(0, value); } } /// /// Gets or sets a flag indicating if the characters created on this account will have the young status. /// [CommandProperty(AccessLevel.Administrator)] public bool Young { get { return !GetFlag(1); } set { SetFlag(1, !value); if (m_YoungTimer == null) { return; } m_YoungTimer.Stop(); m_YoungTimer = null; } } /// /// The date and time of when this account was created. /// [CommandProperty(AccessLevel.Administrator, true)] public DateTime Created { get; set; } [CommandProperty(AccessLevel.Administrator)] public TimeSpan Age => DateTime.UtcNow - Created; /// /// Gets or sets the date and time when this account was last accessed. /// [CommandProperty(AccessLevel.Administrator)] public DateTime LastLogin { get; set; } /// /// An account is considered inactive based upon LastLogin and InactiveDuration. If the account is empty, it is based /// upon EmptyInactiveDuration /// [CommandProperty(AccessLevel.Administrator)] public bool Inactive { get { if (AccessLevel >= AccessLevel.Counselor) { return false; } TimeSpan inactiveLength = DateTime.UtcNow - LastLogin; return inactiveLength > (Count == 0 ? EmptyInactiveDuration : InactiveDuration); } } /// /// Gets the total game time of this account, also considering the game time of characters /// that have been deleted. /// [CommandProperty(AccessLevel.Administrator)] public TimeSpan TotalGameTime { get { var online = m_Mobiles.OfType().FirstOrDefault(pm => pm.NetState != null); if (online != null) { return m_TotalGameTime + (DateTime.UtcNow - online.SessionStart); } return m_TotalGameTime; } } /// /// Account username. Case insensitive validation. /// [CommandProperty(AccessLevel.Administrator, true)] public string Username { get; set; } /// /// Account email address. Case insensitive validation. /// [CommandProperty(AccessLevel.Administrator, true)] public string Email { get; set; } /// /// Initial AccessLevel for new characters created on this account. /// [CommandProperty(AccessLevel.Administrator, AccessLevel.Owner)] public AccessLevel AccessLevel { get { return m_AccessLevel; } set { m_AccessLevel = value; } } /// /// Gets the current number of characters on this account. /// [CommandProperty(AccessLevel.Administrator)] public int Count { get { int count = 0; for (int i = 0; i < Length; ++i) { if (this[i] != null) { ++count; } } return count; } } /// /// Gets the maximum amount of characters allowed to be created on this account. Values other than 1, 5, 6, or 7 are /// not supported by the client. /// [CommandProperty(AccessLevel.Administrator)] public int Limit => (Siege.SiegeShard ? Siege.CharacterSlots : 7); /// /// Gets the maxmimum amount of characters that this account can hold. /// [CommandProperty(AccessLevel.Administrator)] public int Length => m_Mobiles.Length; /// /// Gets or sets the character at a specified index for this account. /// Out of bound index values are handled; null returned for get, ignored for set. /// public Mobile this[int index] { get { if (index < 0 || index >= m_Mobiles.Length) { return null; } Mobile m = m_Mobiles[index]; if (m == null || !m.Deleted) { return m; } m.Account = null; m_Mobiles[index] = null; return null; } set { if (index < 0 || index >= m_Mobiles.Length) { return; } if (m_Mobiles[index] != null) { m_Mobiles[index].Account = null; } m_Mobiles[index] = value; if (m_Mobiles[index] != null) { m_Mobiles[index].Account = this; } } } /// /// Deletes the account, all characters of the account, and all houses of those characters /// public void Delete() { for (int i = 0; i < Length; ++i) { Mobile m = this[i]; if (m == null) { continue; } List list = BaseHouse.GetHouses(m); foreach (BaseHouse h in list) { h.Delete(); } ColUtility.Free(list); m.Delete(); m.Account = null; m_Mobiles[i] = null; } if (LoginIPs.Length != 0 && AccountHandler.IPTable.ContainsKey(LoginIPs[0])) { --AccountHandler.IPTable[LoginIPs[0]]; } Accounts.Remove(Username); } public void SetPassword(string plainPassword) { switch (AccountHandler.ProtectPasswords) { case PasswordProtection.None: { PlainPassword = plainPassword; _MD5Password = null; _SHA1Password = null; _SHA512Password = null; } break; case PasswordProtection.Crypt: { PlainPassword = null; _MD5Password = HashMD5(plainPassword); _SHA1Password = null; _SHA512Password = null; } break; case PasswordProtection.NewCrypt: { PlainPassword = null; _MD5Password = null; _SHA1Password = HashSHA1(Username + plainPassword); _SHA512Password = null; } break; default: // PasswordProtection.NewSecureCrypt { PlainPassword = null; _MD5Password = null; _SHA1Password = null; _SHA512Password = HashSHA512(Username + plainPassword); } break; } } public bool CheckPassword(string plainPassword) { bool ok; PasswordProtection curProt; if (PlainPassword != null) { ok = (PlainPassword == plainPassword); curProt = PasswordProtection.None; } else if (_MD5Password != null) { ok = (_MD5Password == HashMD5(plainPassword)); curProt = PasswordProtection.Crypt; } else if (_SHA1Password != null) { ok = (_SHA1Password == HashSHA1(Username + plainPassword)); curProt = PasswordProtection.NewCrypt; } else { ok = (_SHA512Password == HashSHA512(Username + plainPassword)); curProt = PasswordProtection.NewSecureCrypt; } if (ok && curProt != AccountHandler.ProtectPasswords) { SetPassword(plainPassword); } return ok; } public int CompareTo(IAccount other) { if (other == null) { return 1; } return string.Compare(Username, other.Username, StringComparison.Ordinal); } public int CompareTo(object obj) { if (obj is Account) { return CompareTo((Account)obj); } throw new ArgumentException(); } public int CompareTo(Account other) { if (other == null) { return 1; } return string.Compare(Username, other.Username, StringComparison.Ordinal); } public static string HashMD5(string phrase) { if (m_MD5HashProvider == null) { m_MD5HashProvider = new MD5CryptoServiceProvider(); } if (m_HashBuffer == null) { m_HashBuffer = new byte[256]; } int length = Encoding.ASCII.GetBytes(phrase, 0, phrase.Length > 256 ? 256 : phrase.Length, m_HashBuffer, 0); byte[] hashed = m_MD5HashProvider.ComputeHash(m_HashBuffer, 0, length); return BitConverter.ToString(hashed); } public static string HashSHA1(string phrase) { if (m_SHA1HashProvider == null) { m_SHA1HashProvider = new SHA1CryptoServiceProvider(); } if (m_HashBuffer == null) { m_HashBuffer = new byte[256]; } int length = Encoding.ASCII.GetBytes(phrase, 0, phrase.Length > 256 ? 256 : phrase.Length, m_HashBuffer, 0); byte[] hashed = m_SHA1HashProvider.ComputeHash(m_HashBuffer, 0, length); return BitConverter.ToString(hashed); } public static string HashSHA512(string phrase) { if (m_SHA512HashProvider == null) { m_SHA512HashProvider = new SHA512CryptoServiceProvider(); } if (m_HashBuffer == null) { m_HashBuffer = new byte[256]; } int length = Encoding.ASCII.GetBytes(phrase, 0, phrase.Length > 256 ? 256 : phrase.Length, m_HashBuffer, 0); byte[] hashed = m_SHA512HashProvider.ComputeHash(m_HashBuffer, 0, length); return BitConverter.ToString(hashed); } public static void Initialize() { EventSink.Connected += EventSink_Connected; EventSink.Disconnected += EventSink_Disconnected; EventSink.Login += EventSink_Login; } /// /// Deserializes a list of string values from an xml element. Null values are not added to the list. /// /// The XmlElement from which to deserialize. /// String list. Value will never be null. public static string[] LoadAccessCheck(XmlElement node) { string[] stringList; XmlElement accessCheck = node["accessCheck"]; if (accessCheck != null) { stringList = accessCheck.GetElementsByTagName("ip") .Cast() .Select(ip => Utility.GetText(ip, null)) .Where(text => text != null) .ToArray(); } else { stringList = new string[0]; } return stringList; } /// /// Deserializes a list of IPAddress values from an xml element. /// /// The XmlElement from which to deserialize. /// Address list. Value will never be null. public static IPAddress[] LoadAddressList(XmlElement node) { IPAddress[] list; XmlElement addressList = node["addressList"]; if (addressList != null) { int count = Utility.GetXMLInt32(Utility.GetAttribute(addressList, "count", "0"), 0); list = new IPAddress[count]; count = 0; foreach (XmlElement ip in addressList.GetElementsByTagName("ip").Cast().Where(ip => count < list.Length)) { IPAddress address; if (!IPAddress.TryParse(Utility.GetText(ip, null), out address)) { continue; } list[count] = Utility.Intern(address); count++; } if (count == list.Length) { return list; } IPAddress[] old = list; list = new IPAddress[count]; for (int i = 0; i < count && i < old.Length; ++i) { list[i] = old[i]; } } else { list = new IPAddress[0]; } return list; } /// /// Deserializes a list of Mobile instances from an xml element. /// /// The XmlElement instance from which to deserialize. /// Mobile list. Value will never be null. public static Mobile[] LoadMobiles(XmlElement node) { Mobile[] list = new Mobile[7]; XmlElement chars = node["chars"]; //int length = Accounts.GetInt32( Accounts.GetAttribute( chars, "length", "6" ), 6 ); //list = new Mobile[length]; //Above is legacy, no longer used if (chars == null) { return list; } foreach (XmlElement ele in chars.GetElementsByTagName("char")) { try { int index = Utility.GetXMLInt32(Utility.GetAttribute(ele, "index", "0"), 0); int serial = Utility.GetXMLInt32(Utility.GetText(ele, "0"), 0); if (index >= 0 && index < list.Length) { list[index] = World.FindMobile(serial); } } catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } } return list; } /// /// Deserializes a list of AccountComment instances from an xml element. /// /// The XmlElement from which to deserialize. /// Comment list. Value will never be null. public static List LoadComments(XmlElement node) { XmlElement comments = node["comments"]; if (comments == null) { return null; } List list = new List(); foreach (XmlElement comment in comments.GetElementsByTagName("comment")) { try { list.Add(new AccountComment(comment)); } catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } } return list; } /// /// Deserializes a list of AccountTag instances from an xml element. /// /// The XmlElement from which to deserialize. /// Tag list. Value will never be null. public static List LoadTags(XmlElement node) { XmlElement tags = node["tags"]; if (tags == null) { return null; } List list = new List(); foreach (XmlElement tag in tags.GetElementsByTagName("tag")) { try { list.Add(new AccountTag(tag)); } catch (Exception e) { Diagnostics.ExceptionLogging.LogException(e); } } return list; } /// /// Gets the value of a specific flag in the Flags bitfield. /// /// The zero-based flag index. public bool GetFlag(int index) { return (Flags & (1 << index)) != 0; } /// /// Sets the value of a specific flag in the Flags bitfield. /// /// The zero-based flag index. /// The value to set. public void SetFlag(int index, bool value) { if (value) { Flags |= (1 << index); } else { Flags &= ~(1 << index); } } /// /// Adds a new tag to this account. This method does not check for duplicate names. /// /// New tag name. /// New tag value. public void AddTag(string name, string value) { Tags.Add(new AccountTag(name, value)); } /// /// Removes all tags with the specified name from this account. /// /// Tag name to remove. public void RemoveTag(string name) { for (int i = Tags.Count - 1; i >= 0; --i) { if (i >= Tags.Count) { continue; } AccountTag tag = Tags[i]; if (tag.Name == name) { Tags.RemoveAt(i); } } } /// /// Modifies an existing tag or adds a new tag if no tag exists. /// /// Tag name. /// Tag value. public void SetTag(string name, string value) { var tag = Tags.FirstOrDefault(t => t.Name == name); if (tag != null) { tag.Value = value; } else { AddTag(name, value); } } /// /// Gets the value of a tag -or- null if there are no tags with the specified name. /// /// Name of the desired tag value. public string GetTag(string name) { return Tags.Where(tag => tag.Name == name).Select(tag => tag.Value).FirstOrDefault(); } public void SetUnspecifiedBan(Mobile from) { SetBanTags(from, DateTime.MinValue, TimeSpan.Zero); } public void SetBanTags(Mobile from, DateTime banTime, TimeSpan banDuration) { if (from == null) { RemoveTag("BanDealer"); } else { SetTag("BanDealer", from.ToString()); } if (banTime == DateTime.MinValue) { RemoveTag("BanTime"); } else { SetTag("BanTime", XmlConvert.ToString(banTime, XmlDateTimeSerializationMode.Utc)); } if (banDuration == TimeSpan.Zero) { RemoveTag("BanDuration"); } else { SetTag("BanDuration", banDuration.ToString()); } } public bool GetBanTags(out DateTime banTime, out TimeSpan banDuration) { string tagTime = GetTag("BanTime"); string tagDuration = GetTag("BanDuration"); banTime = tagTime != null ? Utility.GetXMLDateTime(tagTime, DateTime.MinValue) : DateTime.MinValue; if (tagDuration == "Infinite") { banDuration = TimeSpan.MaxValue; } else if (tagDuration != null) { banDuration = Utility.ToTimeSpan(tagDuration); } else { banDuration = TimeSpan.Zero; } return banTime != DateTime.MinValue && banDuration != TimeSpan.Zero; } public void RemoveYoungStatus(int message) { Young = false; foreach (PlayerMobile m in m_Mobiles.OfType().Where(m => m.Young)) { m.Young = false; if (m.NetState == null) { continue; } if (message > 0) { m.SendLocalizedMessage(message); } m.SendLocalizedMessage(1019039); // You are no longer considered a young player of Ultima Online, and are no longer subject to the limitations and benefits of being in that caste. } } public void CheckYoung() { if (TotalGameTime >= YoungDuration) { RemoveYoungStatus(1019038); // You are old enough to be considered an adult, and have outgrown your status as a young player! } } /// /// Checks if a specific NetState is allowed access to this account. /// /// NetState instance to check. /// True if allowed, false if not. public bool HasAccess(NetState ns) { return (ns != null && HasAccess(ns.Address)); } public bool HasAccess(IPAddress ipAddress) { AccessLevel level = AccountHandler.LockdownLevel; if (level >= AccessLevel.Counselor) { bool hasAccess = false; if (m_AccessLevel >= level) { hasAccess = true; } else { for (int i = 0; !hasAccess && i < Length; ++i) { Mobile m = this[i]; if (m != null && m.AccessLevel >= level) { hasAccess = true; } } } if (!hasAccess) { return false; } } bool accessAllowed = IPRestrictions.Length == 0 || IPLimiter.IsExempt(ipAddress); for (int i = 0; !accessAllowed && i < IPRestrictions.Length; ++i) { accessAllowed = Utility.IPMatch(IPRestrictions[i], ipAddress); } return accessAllowed; } /// /// Records the IP address of 'ns' in its 'LoginIPs' list. /// /// NetState instance to record. public void LogAccess(NetState ns) { if (ns != null) { LogAccess(ns.Address); } } public void LogAccess(IPAddress ipAddress) { if (IPLimiter.IsExempt(ipAddress)) { return; } if (LoginIPs.Length == 0) { if (AccountHandler.IPTable.ContainsKey(ipAddress)) { AccountHandler.IPTable[ipAddress]++; } else { AccountHandler.IPTable[ipAddress] = 1; } } bool contains = false; for (int i = 0; !contains && i < LoginIPs.Length; ++i) { contains = LoginIPs[i].Equals(ipAddress); } if (contains) { return; } IPAddress[] old = LoginIPs; LoginIPs = new IPAddress[old.Length + 1]; for (int i = 0; i < old.Length; ++i) { LoginIPs[i] = old[i]; } LoginIPs[old.Length] = ipAddress; } /// /// Checks if a specific NetState is allowed access to this account. If true, the NetState IPAddress is added to the /// address list. /// /// NetState instance to check. /// True if allowed, false if not. public bool CheckAccess(NetState ns) { return (ns != null && CheckAccess(ns.Address)); } public bool CheckAccess(IPAddress ipAddress) { bool hasAccess = HasAccess(ipAddress); if (hasAccess) { LogAccess(ipAddress); } return hasAccess; } /// /// Serializes this Account instance to an XmlTextWriter. /// /// The XmlTextWriter instance from which to serialize. public void Save(XmlTextWriter xml) { xml.WriteStartElement("account"); xml.WriteStartElement("username"); xml.WriteString(Username); xml.WriteEndElement(); if (PlainPassword != null) { xml.WriteStartElement("password"); xml.WriteString(PlainPassword); xml.WriteEndElement(); } if (_MD5Password != null) { xml.WriteStartElement("cryptPassword"); xml.WriteString(_MD5Password); xml.WriteEndElement(); } if (_SHA1Password != null) { xml.WriteStartElement("newCryptPassword"); xml.WriteString(_SHA1Password); xml.WriteEndElement(); } if (_SHA512Password != null) { xml.WriteStartElement("newSecureCryptPassword"); xml.WriteString(_SHA512Password); xml.WriteEndElement(); } if (m_AccessLevel >= AccessLevel.Counselor) { xml.WriteStartElement("accessLevel"); xml.WriteString(m_AccessLevel.ToString()); xml.WriteEndElement(); } if (Flags != 0) { xml.WriteStartElement("flags"); xml.WriteString(XmlConvert.ToString(Flags)); xml.WriteEndElement(); } xml.WriteStartElement("created"); xml.WriteString(XmlConvert.ToString(Created, XmlDateTimeSerializationMode.Utc)); xml.WriteEndElement(); xml.WriteStartElement("lastLogin"); xml.WriteString(XmlConvert.ToString(LastLogin, XmlDateTimeSerializationMode.Utc)); xml.WriteEndElement(); xml.WriteStartElement("totalGameTime"); xml.WriteString(XmlConvert.ToString(TotalGameTime)); xml.WriteEndElement(); xml.WriteStartElement("chars"); for (int i = 0; i < m_Mobiles.Length; ++i) { Mobile m = m_Mobiles[i]; if (m != null && !m.Deleted) { xml.WriteStartElement("char"); xml.WriteAttributeString("index", i.ToString()); xml.WriteString(m.Serial.Value.ToString()); xml.WriteEndElement(); } } xml.WriteEndElement(); if (m_Comments != null && m_Comments.Count > 0) { xml.WriteStartElement("comments"); foreach (AccountComment c in m_Comments) { c.Save(xml); } xml.WriteEndElement(); } if (m_Tags != null && m_Tags.Count > 0) { xml.WriteStartElement("tags"); foreach (AccountTag t in m_Tags) { t.Save(xml); } xml.WriteEndElement(); } if (LoginIPs.Length > 0) { xml.WriteStartElement("addressList"); xml.WriteAttributeString("count", LoginIPs.Length.ToString()); foreach (IPAddress ip in LoginIPs) { xml.WriteStartElement("ip"); xml.WriteString(ip.ToString()); xml.WriteEndElement(); } xml.WriteEndElement(); } if (IPRestrictions.Length > 0) { xml.WriteStartElement("accessCheck"); foreach (string ip in IPRestrictions) { xml.WriteStartElement("ip"); xml.WriteString(ip); xml.WriteEndElement(); } xml.WriteEndElement(); } xml.WriteStartElement("totalCurrency"); xml.WriteString(XmlConvert.ToString(TotalCurrency)); xml.WriteEndElement(); xml.WriteStartElement("sovereigns"); xml.WriteString(XmlConvert.ToString(Sovereigns)); xml.WriteEndElement(); if (SecureAccounts != null) { xml.WriteStartElement("SecureAccounts"); for (int i = 0; i < m_Mobiles.Length; ++i) { Mobile m = m_Mobiles[i]; int balance = GetSecureAccountAmount(m); if (m != null && !m.Deleted && balance > 0) { xml.WriteStartElement("char"); xml.WriteAttributeString("index", i.ToString()); xml.WriteString(balance.ToString()); xml.WriteEndElement(); } } xml.WriteEndElement(); } xml.WriteEndElement(); } public override string ToString() { return Username; } private static void EventSink_Connected(ConnectedEventArgs e) { Account acc = e.Mobile.Account as Account; if (acc == null) { return; } if (!acc.Young || acc.m_YoungTimer != null) { return; } acc.m_YoungTimer = new YoungTimer(acc); acc.m_YoungTimer.Start(); } private static void EventSink_Disconnected(DisconnectedEventArgs e) { Account acc = e.Mobile.Account as Account; if (acc == null) { return; } if (acc.m_YoungTimer != null) { acc.m_YoungTimer.Stop(); acc.m_YoungTimer = null; } PlayerMobile m = e.Mobile as PlayerMobile; if (m != null) { acc.m_TotalGameTime += DateTime.UtcNow - m.SessionStart; } } private static void EventSink_Login(LoginEventArgs e) { PlayerMobile m = e.Mobile as PlayerMobile; if (m == null) { return; } Account acc = m.Account as Account; if (acc == null) { return; } if (!m.Young || !acc.Young) { return; } TimeSpan ts = YoungDuration - acc.TotalGameTime; int hours = Math.Max((int)ts.TotalHours, 0); m.SendAsciiMessage( "You will enjoy the benefits and relatively safe status of a young player for {0} more hour{1}.", hours, hours != 1 ? "s" : ""); } private class YoungTimer : Timer { private readonly Account _Account; public YoungTimer(Account account) : base(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0)) { _Account = account; Priority = TimerPriority.FiveSeconds; } protected override void OnTick() { _Account.CheckYoung(); } } #region Gold Account /// /// This amount specifies the value at which point Gold turns to Platinum. /// By default, when 1,000,000,000 Gold is accumulated, it will transform /// into 1 Platinum. /// public static int CurrencyThreshold { get { return AccountGold.CurrencyThreshold; } set { AccountGold.CurrencyThreshold = value; } } /// /// This amount represents the total amount of currency owned by the player. /// It is cumulative of both Gold and Platinum, the absolute total amount of /// Gold owned by the player can be found by multiplying this value by the /// CurrencyThreshold value. /// [CommandProperty(AccessLevel.Administrator, true)] public double TotalCurrency { get; private set; } /// /// This amount represents the current amount of Gold owned by the player. /// The value does not include the value of Platinum and ranges from /// 0 to 999,999,999 by default. /// [CommandProperty(AccessLevel.Administrator)] public int TotalGold => (int)Math.Floor((TotalCurrency - Math.Truncate(TotalCurrency)) * Math.Max(1.0, CurrencyThreshold)); /// /// This amount represents the current amount of Platinum owned by the player. /// The value does not include the value of Gold and ranges from /// 0 to 2,147,483,647 by default. /// One Platinum represents the value of CurrencyThreshold in Gold. /// [CommandProperty(AccessLevel.Administrator)] public int TotalPlat => (int)Math.Truncate(TotalCurrency); /// /// Attempts to deposit the given amount of Gold and Platinum into this account. /// /// Amount to deposit. /// True if successful, false if amount given is less than or equal to zero. public bool DepositCurrency(double amount) { if (amount <= 0) { return false; } double oldAmount = TotalCurrency; TotalCurrency += amount; EventSink.InvokeAccountGoldChange(new AccountGoldChangeEventArgs(this, oldAmount, TotalCurrency)); return true; } /// /// Attempts to deposit the given amount of Gold into this account. /// If the given amount is greater than the CurrencyThreshold, /// Platinum will be deposited to offset the difference. /// /// Amount to deposit. /// True if successful, false if amount given is less than or equal to zero. public bool DepositGold(int amount) { return DepositCurrency(amount / Math.Max(1.0, CurrencyThreshold)); } /// /// Attempts to deposit the given amount of Gold into this account. /// If the given amount is greater than the CurrencyThreshold, /// Platinum will be deposited to offset the difference. /// /// Amount to deposit. /// True if successful, false if amount given is less than or equal to zero. public bool DepositGold(long amount) { return DepositCurrency(amount / Math.Max(1.0, CurrencyThreshold)); } /// /// Attempts to deposit the given amount of Platinum into this account. /// /// Amount to deposit. /// True if successful, false if amount given is less than or equal to zero. public bool DepositPlat(int amount) { return DepositCurrency(amount); } /// /// Attempts to deposit the given amount of Platinum into this account. /// /// Amount to deposit. /// True if successful, false if amount given is less than or equal to zero. public bool DepositPlat(long amount) { return DepositCurrency(amount); } /// /// Attempts to withdraw the given amount of Platinum and Gold from this account. /// /// Amount to withdraw. /// True if successful, false if balance was too low. public bool WithdrawCurrency(double amount) { if (amount <= 0) { return true; } if (amount > TotalCurrency) { return false; } double oldAmount = TotalCurrency; TotalCurrency -= amount; EventSink.InvokeAccountGoldChange(new AccountGoldChangeEventArgs(this, oldAmount, TotalCurrency)); return true; } /// /// Attempts to withdraw the given amount of Gold from this account. /// If the given amount is greater than the CurrencyThreshold, /// Platinum will be withdrawn to offset the difference. /// /// Amount to withdraw. /// True if successful, false if balance was too low. public bool WithdrawGold(int amount) { return WithdrawCurrency(amount / Math.Max(1.0, CurrencyThreshold)); } /// /// Attempts to withdraw the given amount of Gold from this account. /// If the given amount is greater than the CurrencyThreshold, /// Platinum will be withdrawn to offset the difference. /// /// Amount to withdraw. /// True if successful, false if balance was too low. public bool WithdrawGold(long amount) { return WithdrawCurrency(amount / Math.Max(1.0, CurrencyThreshold)); } /// /// Attempts to withdraw the given amount of Platinum from this account. /// /// Amount to withdraw. /// True if successful, false if balance was too low. public bool WithdrawPlat(int amount) { return WithdrawCurrency(amount); } /// /// Attempts to withdraw the given amount of Platinum from this account. /// /// Amount to withdraw. /// True if successful, false if balance was too low. public bool WithdrawPlat(long amount) { return WithdrawCurrency(amount); } /// /// Gets the total balance of Gold for this account. /// /// Gold value, Platinum exclusive /// Gold value, Platinum inclusive public void GetGoldBalance(out int gold, out double totalGold) { gold = TotalGold; totalGold = TotalCurrency * Math.Max(1.0, CurrencyThreshold); } /// /// Gets the total balance of Gold for this account. /// /// Gold value, Platinum exclusive /// Gold value, Platinum inclusive public void GetGoldBalance(out long gold, out double totalGold) { gold = TotalGold; totalGold = TotalCurrency * Math.Max(1.0, CurrencyThreshold); } /// /// Gets the total balance of Platinum for this account. /// /// Platinum value, Gold exclusive /// Platinum value, Gold inclusive public void GetPlatBalance(out int plat, out double totalPlat) { plat = TotalPlat; totalPlat = TotalCurrency; } /// /// Gets the total balance of Platinum for this account. /// /// Platinum value, Gold exclusive /// Platinum value, Gold inclusive public void GetPlatBalance(out long plat, out double totalPlat) { plat = TotalPlat; totalPlat = TotalCurrency; } /// /// Gets the total balance of Gold and Platinum for this account. /// /// Gold value, Platinum exclusive /// Gold value, Platinum inclusive /// Platinum value, Gold exclusive /// Platinum value, Gold inclusive public void GetBalance(out int gold, out double totalGold, out int plat, out double totalPlat) { GetGoldBalance(out gold, out totalGold); GetPlatBalance(out plat, out totalPlat); } /// /// Gets the total balance of Gold and Platinum for this account. /// /// Gold value, Platinum exclusive /// Gold value, Platinum inclusive /// Platinum value, Gold exclusive /// Platinum value, Gold inclusive public void GetBalance(out long gold, out double totalGold, out long plat, out double totalPlat) { GetGoldBalance(out gold, out totalGold); GetPlatBalance(out plat, out totalPlat); } public bool HasGoldBalance(double amount) { long gold; double totalGold; GetGoldBalance(out gold, out totalGold); return amount <= totalGold; } public bool HasPlatBalance(double amount) { long plat; double totalPlat; GetPlatBalance(out plat, out totalPlat); return amount <= totalPlat; } #endregion #region Secure Account public Dictionary SecureAccounts; public static readonly int MaxSecureAmount = 100000000; public int GetSecureAccountAmount(Mobile m) { for (int i = 0; i < Length; i++) { Mobile mob = m_Mobiles[i]; if (mob == null) continue; if (mob == m) { if (SecureAccounts != null && SecureAccounts.ContainsKey(m)) return SecureAccounts[m]; } } return 0; } public bool DepositToSecure(Mobile m, int amount) { for (int i = 0; i < Length; i++) { Mobile mob = m_Mobiles[i]; if (mob == null) continue; if (mob == m) { if (SecureAccounts == null) SecureAccounts = new Dictionary(); if (!SecureAccounts.ContainsKey(m)) SecureAccounts[m] = Math.Min(MaxSecureAmount, amount); else SecureAccounts[m] = Math.Min(MaxSecureAmount, SecureAccounts[m] + amount); return true; } } return false; } public bool WithdrawFromSecure(Mobile m, int amount) { for (int i = 0; i < Length; i++) { Mobile mob = m_Mobiles[i]; if (mob == null) continue; if (m == mob) { if (SecureAccounts == null || !SecureAccounts.ContainsKey(m) || SecureAccounts[m] < amount) return false; SecureAccounts[m] -= amount; return true; } } return false; } #endregion #region Sovereigns /// /// Sovereigns which can be used at the shard owners disposal. On EA, they are used for curerncy with the Ultima Store /// [CommandProperty(AccessLevel.Administrator, true)] public int Sovereigns { get; private set; } public void SetSovereigns(int amount) { Sovereigns = amount; } public bool DepositSovereigns(int amount) { if (amount <= 0) { return false; } Sovereigns += amount; return true; } public bool WithdrawSovereigns(int amount) { if (amount <= 0) { return true; } if (amount > Sovereigns) { return false; } Sovereigns -= amount; return true; } #endregion } }