RunUO Community

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Suggestion: Account mobiles collection

ZixThree

Wanderer
Suggestion: Account mobiles collection

While I was roaming in core source code around the Account.cs, I thought it would perhaps be a good idea for a better clarity around the Account class (and IAccount interface) to use a Mobile collection anchored in the Account class
instead of asking directly the account.

This would allow us to change a code like this (from Scripts Accounting\Account.cs):
Code:
for ( int i = 0; !hasAccess && i < this.Length; ++i )
{
	Mobile m = this[i];

	if ( m != null && m.AccessLevel >= level )
		hasAccess = true;
}

for a simpler to read:

Code:
foreach(Mobile m in Mobiles)
{
	if(m.AccessLevel >= level)
	{
		hasAccess = true;
		break;
	}
}


This is only an example, but, overall, I think it would be better for readability and it would mean what it's supposed to be (as, I don't think an Account should be a collection, but it could/should contain a collection). If you are interested to check a working implementation of this, it is following:

Code:
// Scripts: Accounting\Account.cs
using System;
using System.Security;
using System.Security.Cryptography;
using System.Net;
using System.Collections;
using System.Xml;
using System.Text;
using Server;
using Server.Misc;
using Server.Network;
using Server.Mobiles;

namespace Server.Accounting
{
    public class Account : IAccount
    {
        public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0);
        private const int MaximumCharacterPerAccount = 6;

        private string m_Username, m_PlainPassword, m_CryptPassword;
        private AccessLevel m_AccessLevel;
        private int m_Flags;
        private DateTime m_Created, m_LastLogin;
        private TimeSpan m_TotalGameTime;
        private ArrayList m_Comments;
        private ArrayList m_Tags;
        private AccountMobileCollection m_Mobiles;
        private string[] m_IPRestrictions;
        private IPAddress[] m_LoginIPs;
        private HardwareInfo m_HardwareInfo;

        /// <summary>
        /// Deletes the account, all characters of the account, and all houses of those characters
        /// </summary>
        public void Delete()
        {
            foreach (Mobile m in m_Mobiles) // Delete houses
            {
                ArrayList list = Multis.BaseHouse.GetHouses(m);

                for (int j = 0; j < list.Count; ++j)
                    ((Item)list[j]).Delete();
            }

            for (int i = 0; i < m_Mobiles.Capacity; i++) // Delete player characters
            {
                if (m_Mobiles[i] != null)
                    m_Mobiles[i].Delete();
            }

            m_Mobiles.Clear(); // Should be done. Just to be sure.

            Accounts.Table.Remove(m_Username);
        }

        /// <summary>
        /// Object detailing information about the hardware of the last person to log into this account
        /// </summary>
        public HardwareInfo HardwareInfo
        {
            get { return m_HardwareInfo; }
            set { m_HardwareInfo = value; }
        }

        /// <summary>
        /// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP addresses are allowed.
        /// </summary>
        public string[] IPRestrictions
        {
            get { return m_IPRestrictions; }
            set { m_IPRestrictions = value; }
        }

        /// <summary>
        /// List of IP addresses which have successfully logged into this account.
        /// </summary>
        public IPAddress[] LoginIPs
        {
            get { return m_LoginIPs; }
            set { m_LoginIPs = value; }
        }

        /// <summary>
        /// List of account comments. Type of contained objects is AccountComment.
        /// </summary>
        public ArrayList Comments
        {
            get { return m_Comments; }
        }

        /// <summary>
        /// List of account tags. Type of contained objects is AccountTag.
        /// </summary>
        public ArrayList Tags
        {
            get { return m_Tags; }
        }

        /// <summary>
        /// Account username. Case insensitive validation.
        /// </summary>
        public string Username
        {
            get { return m_Username; }
            set { m_Username = value; }
        }

        /// <summary>
        /// Account password. Plain text. Case sensitive validation. May be null.
        /// </summary>
        public string PlainPassword
        {
            get { return m_PlainPassword; }
            set { m_PlainPassword = value; }
        }

        /// <summary>
        /// Account password. Hashed with MD5. May be null.
        /// </summary>
        public string CryptPassword
        {
            get { return m_CryptPassword; }
            set { m_CryptPassword = value; }
        }

        /// <summary>
        /// Initial AccessLevel for new characters created on this account.
        /// </summary>
        public AccessLevel AccessLevel
        {
            get { return m_AccessLevel; }
            set { m_AccessLevel = value; }
        }

        /// <summary>
        /// Internal bitfield of account flags. Consider using direct access properties (Banned, Young), or GetFlag/SetFlag methods
        /// </summary>
        public int Flags
        {
            get { return m_Flags; }
            set { m_Flags = value; }
        }

        /// <summary>
        /// Gets or sets a flag indiciating if this account is banned.
        /// </summary>
        public bool Banned
        {
            get
            {
                bool isBanned = GetFlag(0);

                if (!isBanned)
                    return false;

                DateTime banTime;
                TimeSpan banDuration;

                if (GetBanTags(out banTime, out banDuration))
                {
                    if (banDuration != TimeSpan.MaxValue && DateTime.Now >= (banTime + banDuration))
                    {
                        SetUnspecifiedBan(null); // clear
                        Banned = false;
                        return false;
                    }
                }

                return true;
            }
            set { SetFlag(0, value); }
        }

        /// <summary>
        /// Gets or sets a flag indicating if the characters created on this account will have the young status.
        /// </summary>
        public bool Young
        {
            get { return !GetFlag(1); }
            set
            {
                SetFlag(1, !value);

                if (m_YoungTimer != null)
                {
                    m_YoungTimer.Stop();
                    m_YoungTimer = null;
                }
            }
        }

        /// <summary>
        /// The date and time of when this account was created.
        /// </summary>
        public DateTime Created
        {
            get { return m_Created; }
        }

        /// <summary>
        /// Gets or sets the date and time when this account was last accessed.
        /// </summary>
        public DateTime LastLogin
        {
            get { return m_LastLogin; }
            set { m_LastLogin = value; }
        }

        /// <summary>
        /// Gets the total game time of this account, also considering the game time of characters
        /// that have been deleted.
        /// </summary>
        public TimeSpan TotalGameTime
        {
            get
            {
                for (int i = 0; i < m_Mobiles.Capacity; i++)
                {
                    PlayerMobile m = m_Mobiles[i] as PlayerMobile;

                    if (m != null && m.NetState != null)
                        return m_TotalGameTime + (DateTime.Now - m.SessionStart);
                }

                return m_TotalGameTime;
            }
        }

        /// <summary>
        /// Gets the value of a specific flag in the Flags bitfield.
        /// </summary>
        /// <param name="index">The zero-based flag index.</param>
        public bool GetFlag(int index)
        {
            return (m_Flags & (1 << index)) != 0;
        }

        /// <summary>
        /// Sets the value of a specific flag in the Flags bitfield.
        /// </summary>
        /// <param name="index">The zero-based flag index.</param>
        /// <param name="value">The value to set.</param>
        public void SetFlag(int index, bool value)
        {
            if (value)
                m_Flags |= (1 << index);
            else
                m_Flags &= ~(1 << index);
        }

        /// <summary>
        /// Adds a new tag to this account. This method does not check for duplicate names.
        /// </summary>
        /// <param name="name">New tag name.</param>
        /// <param name="value">New tag value.</param>
        public void AddTag(string name, string value)
        {
            m_Tags.Add(new AccountTag(name, value));
        }

        /// <summary>
        /// Removes all tags with the specified name from this account.
        /// </summary>
        /// <param name="name">Tag name to remove.</param>
        public void RemoveTag(string name)
        {
            for (int i = m_Tags.Count - 1; i >= 0; --i)
            {
                if (i >= m_Tags.Count)
                    continue;

                AccountTag tag = (AccountTag)m_Tags[i];

                if (tag.Name == name)
                    m_Tags.RemoveAt(i);
            }
        }

        /// <summary>
        /// Modifies an existing tag or adds a new tag if no tag exists.
        /// </summary>
        /// <param name="name">Tag name.</param>
        /// <param name="value">Tag value.</param>
        public void SetTag(string name, string value)
        {
            for (int i = 0; i < m_Tags.Count; ++i)
            {
                AccountTag tag = (AccountTag)m_Tags[i];

                if (tag.Name == name)
                {
                    tag.Value = value;
                    return;
                }
            }

            AddTag(name, value);
        }

        /// <summary>
        /// Gets the value of a tag -or- null if there are no tags with the specified name.
        /// </summary>
        /// <param name="name">Name of the desired tag value.</param>
        public string GetTag(string name)
        {
            for (int i = 0; i < m_Tags.Count; ++i)
            {
                AccountTag tag = (AccountTag)m_Tags[i];

                if (tag.Name == name)
                    return tag.Value;
            }

            return null;
        }

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

            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");

            if (tagTime != null)
                banTime = Accounts.GetDateTime(tagTime, DateTime.MinValue);
            else
                banTime = DateTime.MinValue;

            if (tagDuration == "Infinite")
            {
                banDuration = TimeSpan.MaxValue;
            }
            else if (tagDuration != null)
            {
                try { banDuration = TimeSpan.Parse(tagDuration); }
                catch { banDuration = TimeSpan.Zero; }
            }
            else
            {
                banDuration = TimeSpan.Zero;
            }

            return (banTime != DateTime.MinValue && banDuration != TimeSpan.Zero);
        }

        private static MD5CryptoServiceProvider m_HashProvider;
        private static byte[] m_HashBuffer;

        public static string HashPassword(string plainPassword)
        {
            if (m_HashProvider == null)
                m_HashProvider = new MD5CryptoServiceProvider();

            if (m_HashBuffer == null)
                m_HashBuffer = new byte[256];

            int length = Encoding.ASCII.GetBytes(plainPassword, 0, plainPassword.Length > 256 ? 256 : plainPassword.Length, m_HashBuffer, 0);
            byte[] hashed = m_HashProvider.ComputeHash(m_HashBuffer, 0, length);

            return BitConverter.ToString(hashed);
        }

        public void SetPassword(string plainPassword)
        {
            if (AccountHandler.ProtectPasswords)
            {
                m_CryptPassword = HashPassword(plainPassword);
                m_PlainPassword = null;
            }
            else
            {
                m_CryptPassword = null;
                m_PlainPassword = plainPassword;
            }
        }

        public bool CheckPassword(string plainPassword)
        {
            if (m_PlainPassword != null)
                return (m_PlainPassword == plainPassword);

            return (m_CryptPassword == HashPassword(plainPassword));
        }

        private Timer m_YoungTimer;

        public static void Initialize()
        {
            EventSink.Connected += new ConnectedEventHandler(EventSink_Connected);
            EventSink.Disconnected += new DisconnectedEventHandler(EventSink_Disconnected);
            EventSink.Login += new LoginEventHandler(EventSing_Login);
        }

        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)
            {
                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)
                return;

            acc.m_TotalGameTime += DateTime.Now - m.SessionStart;
        }

        private static void EventSing_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)
            {
                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" : "");
            }
        }

        public void RemoveYoungStatus(int message)
        {
            this.Young = false;

            for (int i = 0; i < m_Mobiles.Capacity; i++)
            {
                PlayerMobile m = m_Mobiles[i] as PlayerMobile;

                if (m != null && m.Young)
                {
                    m.Young = false;

                    if (m.NetState != null)
                    {
                        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!
        }

        private class YoungTimer : Timer
        {
            private Account m_Account;

            public YoungTimer(Account account)
                : base(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0))
            {
                m_Account = account;

                Priority = TimerPriority.FiveSeconds;
            }

            protected override void OnTick()
            {
                m_Account.CheckYoung();
            }
        }

        /// <summary>
        /// Constructs a new Account instance with a specific username and password. Intended to be only called from Accounts.AddAccount.
        /// </summary>
        /// <param name="username">Initial username for this account.</param>
        /// <param name="password">Initial password for this account.</param>
        public Account(string username, string password)
        {
            m_Username = username;
            SetPassword(password);

            m_AccessLevel = AccessLevel.Player;

            m_Created = m_LastLogin = DateTime.Now;
            m_TotalGameTime = TimeSpan.Zero;

            m_Comments = new ArrayList();
            m_Tags = new ArrayList();

            m_Mobiles = new AccountMobileCollection(this, MaximumCharacterPerAccount);

            m_IPRestrictions = new string[0];
            m_LoginIPs = new IPAddress[0];
        }

        /// <summary>
        /// Deserializes an Account instance from an xml element. Intended only to be called from Accounts.Load.
        /// </summary>
        /// <param name="node">The XmlElement instance from which to deserialize.</param>
        public Account(XmlElement node)
        {
            m_Username = Accounts.GetText(node["username"], "empty");

            string plainPassword = Accounts.GetText(node["password"], null);
            string cryptPassword = Accounts.GetText(node["cryptPassword"], null);

            if (AccountHandler.ProtectPasswords)
            {
                if (cryptPassword != null)
                    m_CryptPassword = cryptPassword;
                else if (plainPassword != null)
                    SetPassword(plainPassword);
                else
                    SetPassword("empty");
            }
            else
            {
                if (plainPassword == null)
                    plainPassword = "empty";

                SetPassword(plainPassword);
            }

            m_AccessLevel = (AccessLevel)Enum.Parse(typeof(AccessLevel), Accounts.GetText(node["accessLevel"], "Player"), true);
            m_Flags = Accounts.GetInt32(Accounts.GetText(node["flags"], "0"), 0);
            m_Created = Accounts.GetDateTime(Accounts.GetText(node["created"], null), DateTime.Now);
            m_LastLogin = Accounts.GetDateTime(Accounts.GetText(node["lastLogin"], null), DateTime.Now);

            m_Mobiles = new AccountMobileCollection(this, MaximumCharacterPerAccount);
            LoadMobiles(node, m_Mobiles);
            m_Comments = LoadComments(node);
            m_Tags = LoadTags(node);
            m_LoginIPs = LoadAddressList(node);
            m_IPRestrictions = LoadAccessCheck(node);

            TimeSpan totalGameTime = Accounts.GetTimeSpan(Accounts.GetText(node["totalGameTime"], null), TimeSpan.Zero);
            if (totalGameTime == TimeSpan.Zero)
            {
                IEnumerator enumerator = m_Mobiles.GetEnumerator(typeof(PlayerMobile));
                while (enumerator.MoveNext())
                {
                    PlayerMobile m = (PlayerMobile)enumerator.Current;

                    totalGameTime += m.GameTime;
                }
            }
            m_TotalGameTime = totalGameTime;

            if (this.Young)
                CheckYoung();
        }

        /// <summary>
        /// Deserializes a list of string values from an xml element. Null values are not added to the list.
        /// </summary>
        /// <param name="node">The XmlElement from which to deserialize.</param>
        /// <returns>String list. Value will never be null.</returns>
        public static string[] LoadAccessCheck(XmlElement node)
        {
            string[] stringList;
            XmlElement accessCheck = node["accessCheck"];

            if (accessCheck != null)
            {
                ArrayList list = new ArrayList();

                foreach (XmlElement ip in accessCheck.GetElementsByTagName("ip"))
                {
                    string text = Accounts.GetText(ip, null);

                    if (text != null)
                        list.Add(text);
                }

                stringList = (string[])list.ToArray(typeof(string));
            }
            else
            {
                stringList = new string[0];
            }

            return stringList;
        }

        /// <summary>
        /// Deserializes a list of IPAddress values from an xml element.
        /// </summary>
        /// <param name="node">The XmlElement from which to deserialize.</param>
        /// <returns>Address list. Value will never be null.</returns>
        public static IPAddress[] LoadAddressList(XmlElement node)
        {
            IPAddress[] list;
            XmlElement addressList = node["addressList"];

            if (addressList != null)
            {
                int count = Accounts.GetInt32(Accounts.GetAttribute(addressList, "count", "0"), 0);

                list = new IPAddress[count];

                count = 0;

                foreach (XmlElement ip in addressList.GetElementsByTagName("ip"))
                {
                    try
                    {
                        if (count < list.Length)
                        {
                            list[count] = IPAddress.Parse(Accounts.GetText(ip, null));
                            count++;
                        }
                    }
                    catch
                    {
                    }
                }

                if (count != list.Length)
                {
                    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;
        }

        /// <summary>
        /// Deserializes a list of AccountTag instances from an xml element.
        /// </summary>
        /// <param name="node">The XmlElement from which to deserialize.</param>
        /// <returns>Tag list. Value will never be null.</returns>
        public static ArrayList LoadTags(XmlElement node)
        {
            ArrayList list = new ArrayList();
            XmlElement tags = node["tags"];

            if (tags != null)
            {
                foreach (XmlElement tag in tags.GetElementsByTagName("tag"))
                {
                    try { list.Add(new AccountTag(tag)); }
                    catch { }
                }
            }

            return list;
        }

        /// <summary>
        /// Deserializes a list of AccountComment instances from an xml element.
        /// </summary>
        /// <param name="node">The XmlElement from which to deserialize.</param>
        /// <returns>Comment list. Value will never be null.</returns>
        public static ArrayList LoadComments(XmlElement node)
        {
            ArrayList list = new ArrayList();
            XmlElement comments = node["comments"];

            if (comments != null)
            {
                foreach (XmlElement comment in comments.GetElementsByTagName("comment"))
                {
                    try { list.Add(new AccountComment(comment)); }
                    catch { }
                }
            }

            return list;
        }

        /// <summary>
        /// Deserializes a list of Mobile instances from an xml element.
        /// </summary>
        /// <param name="node">The XmlElement instance from which to deserialize.</param>
        /// <returns>Mobile list. Value will never be null.</returns>
        public static void LoadMobiles(XmlElement node, AccountMobileCollection list)
        {
            //Mobile[] list = new Mobile[6];
            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)
            {
                foreach (XmlElement ele in chars.GetElementsByTagName("char"))
                {
                    try
                    {
                        int index = Accounts.GetInt32(Accounts.GetAttribute(ele, "index", "0"), 0);
                        int serial = Accounts.GetInt32(Accounts.GetText(ele, "0"), 0);

                        if (index >= 0 && index < list.Capacity)
                            list[index] = World.FindMobile(serial);
                    }
                    catch
                    {
                    }
                }
            }
        }

        /// <summary>
        /// Checks if a specific NetState is allowed access to this account.
        /// </summary>
        /// <param name="ns">NetState instance to check.</param>
        /// <returns>True if allowed, false if not.</returns>
        public bool HasAccess(NetState ns)
        {
            if (ns == null)
                return false;

            AccessLevel level = Misc.AccountHandler.LockdownLevel;

            if (level > AccessLevel.Player)
            {
                bool hasAccess = false;

                if (m_AccessLevel >= level)
                {
                    hasAccess = true;
                }
                else
                {
                    foreach (Mobile m in m_Mobiles)
                    {
                        if (m.AccessLevel >= level)
                        {
                            hasAccess = true;
                            break;
                        }
                    }
                }

                if (!hasAccess)
                    return false;
            }

            IPAddress ipAddress;

            try { ipAddress = ((IPEndPoint)ns.Socket.RemoteEndPoint).Address; }
            catch { return false; }

            bool accessAllowed = (m_IPRestrictions.Length == 0);

            for (int i = 0; !accessAllowed && i < m_IPRestrictions.Length; ++i)
                accessAllowed = Utility.IPMatch(m_IPRestrictions[i], ipAddress);

            return accessAllowed;
        }

        /// <summary>
        /// Records the IP address of 'ns' in its 'LoginIPs' list.
        /// </summary>
        /// <param name="ns">NetState instance to record.</param>
        public void LogAccess(NetState ns)
        {
            if (ns == null)
                return;

            IPAddress ipAddress;

            try { ipAddress = ((IPEndPoint)ns.Socket.RemoteEndPoint).Address; }
            catch { return; }

            bool contains = false;

            for (int i = 0; !contains && i < m_LoginIPs.Length; ++i)
                contains = m_LoginIPs[i].Equals(ipAddress);

            if (contains)
                return;

            IPAddress[] old = m_LoginIPs;
            m_LoginIPs = new IPAddress[old.Length + 1];

            for (int i = 0; i < old.Length; ++i)
                m_LoginIPs[i] = old[i];

            m_LoginIPs[old.Length] = ipAddress;
        }

        /// <summary>
        /// Checks if a specific NetState is allowed access to this account. If true, the NetState IPAddress is added to the address list.
        /// </summary>
        /// <param name="ns">NetState instance to check.</param>
        /// <returns>True if allowed, false if not.</returns>
        public bool CheckAccess(NetState ns)
        {
            if (!HasAccess(ns))
                return false;

            LogAccess(ns);
            return true;
        }

        /// <summary>
        /// Serializes this Account instance to an XmlTextWriter.
        /// </summary>
        /// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
        public void Save(XmlTextWriter xml)
        {
            xml.WriteStartElement("account");

            xml.WriteStartElement("username");
            xml.WriteString(m_Username);
            xml.WriteEndElement();

            if (m_PlainPassword != null)
            {
                xml.WriteStartElement("password");
                xml.WriteString(m_PlainPassword);
                xml.WriteEndElement();
            }

            if (m_CryptPassword != null)
            {
                xml.WriteStartElement("cryptPassword");
                xml.WriteString(m_CryptPassword);
                xml.WriteEndElement();
            }

            if (m_AccessLevel != AccessLevel.Player)
            {
                xml.WriteStartElement("accessLevel");
                xml.WriteString(m_AccessLevel.ToString());
                xml.WriteEndElement();
            }

            if (m_Flags != 0)
            {
                xml.WriteStartElement("flags");
                xml.WriteString(XmlConvert.ToString(m_Flags));
                xml.WriteEndElement();
            }

            xml.WriteStartElement("created");
            xml.WriteString(XmlConvert.ToString(m_Created));
            xml.WriteEndElement();

            xml.WriteStartElement("lastLogin");
            xml.WriteString(XmlConvert.ToString(m_LastLogin));
            xml.WriteEndElement();

            xml.WriteStartElement("totalGameTime");
            xml.WriteString(XmlConvert.ToString(TotalGameTime));
            xml.WriteEndElement();

            xml.WriteStartElement("chars");

            //xml.WriteAttributeString( "length", m_Mobiles.Length.ToString() );	//Legacy, Not used anymore

            for (int i = 0; i < m_Mobiles.Capacity; ++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.Count > 0)
            {
                xml.WriteStartElement("comments");

                for (int i = 0; i < m_Comments.Count; ++i)
                    ((AccountComment)m_Comments[i]).Save(xml);

                xml.WriteEndElement();
            }

            if (m_Tags.Count > 0)
            {
                xml.WriteStartElement("tags");

                for (int i = 0; i < m_Tags.Count; ++i)
                    ((AccountTag)m_Tags[i]).Save(xml);

                xml.WriteEndElement();
            }

            if (m_LoginIPs.Length > 0)
            {
                xml.WriteStartElement("addressList");

                xml.WriteAttributeString("count", m_LoginIPs.Length.ToString());

                for (int i = 0; i < m_LoginIPs.Length; ++i)
                {
                    xml.WriteStartElement("ip");
                    xml.WriteString(m_LoginIPs[i].ToString());
                    xml.WriteEndElement();
                }

                xml.WriteEndElement();
            }

            if (m_IPRestrictions.Length > 0)
            {
                xml.WriteStartElement("accessCheck");

                for (int i = 0; i < m_IPRestrictions.Length; ++i)
                {
                    xml.WriteStartElement("ip");
                    xml.WriteString(m_IPRestrictions[i]);
                    xml.WriteEndElement();
                }

                xml.WriteEndElement();
            }

            xml.WriteEndElement();
        }

        /// <summary>
        /// Gets the current number of characters on this account.
        /// </summary>
        [Obsolete("Use Mobiles.Count instead.")]
        public int Count
        {
            get
            {
                return m_Mobiles.Count;
            }
        }

        /// <summary>
        /// Gets the maximum amount of characters allowed to be created on this account. Values other than 1, 5, or 6 are not supported.
        /// </summary>
        public int Limit
        {
            get { return 5; }
        }

        /// <summary>
        /// Gets the maxmimum amount of characters that this account can hold.
        /// </summary>
        [Obsolete("Use Mobiles.Capacity instead.")]
        public int Length
        {
            get { return m_Mobiles.Capacity; }
        }

        public AccountMobileCollection Mobiles
        {
            get
            {
                return m_Mobiles;
            }
        }

        /// <summary>
        /// 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.
        /// </summary>
        [Obsolete("Use Mobiles instead.")]
        public Mobile this[int index]
        {
            get
            {
                if (index >= 0 && index < m_Mobiles.Capacity)
                {
                    return m_Mobiles[index];
                }

                return null;
            }
            set
            {
                if (index >= 0 && index < m_Mobiles.Capacity)
                {
                    m_Mobiles[index] = value;
                }
            }
        }

        public override string ToString()
        {
            return m_Username;
        }
    }
}

Code:
// Core: Accounting\Account.cs
/***************************************************************************
 *                                 Account.cs
 *                            -------------------
 *   begin                : May 1, 2002
 *   copyright            : (C) The RunUO Software Team
 *   email                : [email protected]
 *
 *   $Id: Account.cs,v 1.5 2005/01/22 04:25:04 krrios Exp $
 *   $Author: krrios $
 *   $Date: 2005/01/22 04:25:04 $
 *
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

using System;
using System.Collections;
using System.Xml;

namespace Server.Accounting
{
	public interface IAccount
	{
		[Obsolete("Use Mobiles instead.")]
		int Length{ get; }
		int Limit{ get; }
		[Obsolete("Use Mobiles.Count instead.")]
		int Count{ get; }
		[Obsolete("Use Mobiles instead.")]
		Mobile this[int index]{ get; set; }
		AccountMobileCollection Mobiles{ get; }
	}

	/// <summary>
	/// Represent the default implementation of a <see cref="Mobile"/> collection for an <see cref="IAccount"/>.
	/// </summary>
	public class AccountMobileCollection : IList, ICollection, IEnumerable
	{
		private Mobile[] m_Mobiles;

		private object m_SyncRoot;

		private int m_Version;
		private int m_Count = 0;
		private int m_Capacity;

		private IAccount m_Owner;

		/// <summary>
		/// Initialize a new instance of <see cref="AccountMobileCollection"/>
		/// </summary>
		/// <param name="owner">The owner <see cref="IAccount"/></param>
		/// <param name="maximumCapacity">The maximum capacity of <see cref="Mobile"/> for this <see cref="IAccount"/></param>
		public AccountMobileCollection(IAccount owner, int maximumCapacity)
		{
			if(owner == null)
				throw new ArgumentNullException("owner");

			if(maximumCapacity < 0)
				throw new ArgumentOutOfRangeException("maximumCapacity");

			m_Mobiles = new Mobile[maximumCapacity];
			m_Owner = owner;
			m_Capacity = maximumCapacity;
		}

		/// <summary>
		/// Gets the maximum number of elements the <see cref="AccountMobileCollection"/> can contain.
		/// </summary>
		public int Capacity
		{
			get { return m_Capacity; }
		}

		#region IList Members

		/// <summary>
		/// Gets a value indicating whether the <see cref="AccountMobileCollection"/> is read-only.
		/// </summary>
		public bool IsReadOnly { get { return false; } }

		/// <summary>
		/// Gets or sets the element at the specified index.
		/// </summary>
		public Mobile this[int index]
		{
			get
			{
				if(index >= m_Capacity || index < 0)
					throw new ArgumentOutOfRangeException("index");

				return m_Mobiles[index];
			}
			set
			{
				if(index >= m_Capacity || index < 0)
					throw new ArgumentOutOfRangeException("index");

				if(m_Mobiles[index] == value)
					return;

				if(value == null)
				{
					m_Count --;
					m_Mobiles[index].Account = null;
					m_Mobiles[index] = null;
				}
				else
				{
					if(m_Mobiles[index] != null)
					{
						m_Count --;
						m_Mobiles[index].Account = null;
					}

					m_Mobiles[index] = value;
					m_Mobiles[index].Account = m_Owner;
					m_Count ++;
				}

				m_Version++;
			}
		}

		object System.Collections.IList.this[int index]
		{
			get
			{
				return this[index];
			}
			set
			{
				if(value == null || value is Mobile)
					this[index] = (Mobile)value;
				else
					throw new ArgumentException("value");
			}
		}


		/// <summary>
		/// Remove the <see cref="AccountMobileCollection"/> item at the specified index.
		/// </summary>
		/// <param name="index">The zero-base index of the item to remove</param>
		public void RemoveAt(int index)
		{
			this[index] = null;
		}

		void IList.Insert(int index, object value)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Remove the first occurence of a specific <see cref="Mobile"/> from the <see cref="AccountMobileCollection"/>
		/// </summary>
		/// <param name="m">The <see cref="Mobile"/> to remove from the <see cref="AccountMobileCollection"/></param>
		public void Remove(Mobile m)
		{
			int index = IndexOf(m);
			if(index != -1)
				RemoveAt(index);
		}

		void IList.Remove(object value)
		{
			if(value is Mobile)
				Remove((Mobile)value);
		}

		/// <summary>
		/// Determines whether <see cref="AccountMobileCollection"/> contains a specific value
		/// </summary>
		/// <param name="value">The <see cref="Mobile"/> to locate in the <see cref="AccountMobileCollection"/></param>
		/// <returns>true if the <see cref="Mobile"/> is found in the <see cref="AccountMobileCollection"/>; otherwise, false.</returns>
		public bool Contains(Mobile value)
		{
			return IndexOf(value) >= 0;
		}

		bool IList.Contains(object value)
		{
			if(value is Mobile)
				return Contains((Mobile)value);

			return false;
		}

		/// <summary>
		/// Removes all items from the <see cref="AccountMobileCollection"/>
		/// </summary>
		public void Clear()
		{
			if(m_Count == 0)
				return;

			for(int i = 0; i < m_Count; i++)
				if(m_Mobiles[i] != null)
					m_Mobiles[i].Account = null;

			Array.Clear(m_Mobiles, 0, m_Count);
			m_Count = 0;
			m_Version ++;
		}

		/// <summary>
		/// Determines the index of a specific item in the <see cref="AccountMobileCollection"/>
		/// </summary>
		/// <param name="value">The <see cref="Mobile"/> to locate in the <see cref="AccountMobileCollection"/></param>
		/// <returns>The index of value if found in the list; otherwise, -1.</returns>
		public int IndexOf(Mobile value)
		{
			return Array.IndexOf(m_Mobiles, value);
		}

		int IList.IndexOf(object value)
		{
			if(value is Mobile)
				return IndexOf((Mobile)value);
			
			return -1;
		}

		int IList.Add(object value)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Gets a value indicating whether the <see cref="AccountMobileCollection"/> is read-only
		/// </summary>
		public bool IsFixedSize { get { return true; } }

		#endregion

		#region ICollection Members

		/// <summary>
		/// Gets a value indicating whether access to the <see cref="AccountMobileCollection"/> is synchronized (thread-safe)
		/// </summary>
		public bool IsSynchronized { get { return false; } }

		/// <summary>
		/// Gets the number of elements contained in the <see cref="AccountMobileCollection"/>
		/// </summary>
		public int Count { get { return m_Count; } }

		/// <summary>
		/// Copies the element of the <see cref="AccountMobileCollection"/> to an <see cref="Array"/>,
		/// starting at a particular <b>Array</b> index.
		/// </summary>
		/// <param name="array"></param>
		/// <param name="index"></param>
		public void CopyTo(Array array, int index)
		{
			Array.Copy(m_Mobiles, 0, array, index, m_Count);
		}

		/// <summary>
		/// Gets an object that can be used to synchronize access to the <see cref="AccountMobileCollection"/>.
		/// </summary>
		public object SyncRoot
		{
			get
			{
				if(m_SyncRoot != null)
				{
					System.Threading.Interlocked.CompareExchange(ref m_SyncRoot, new Object(), null);
				}

				return m_SyncRoot;
			}
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// Supports a simple iteration over a <see cref="AccountMobileCollection"/>.
		/// </summary>
		public class AccountMobileEnumerator : IEnumerator
		{
			private int m_Version;
			private int m_Index;
			private AccountMobileCollection m_Mobiles;
			private bool m_AtEnd;
			private bool m_ConsiderNull;
			private Type m_Type;
			private Mobile m_Current;

			/// <summary>
			/// Initialize a new instance of <see cref="AccountMobileEnumerator"/>
			/// </summary>
			/// <param name="collection">The <see cref="AccountMobileCollection"/> instance to iterate over</param>
			/// <param name="returnOnly">A value that tells the iterator to return only values that are of the specified type.</param>
			/// <param name="considerNull">A value that tells the iterator to also return null values</param>
			internal AccountMobileEnumerator(AccountMobileCollection collection, Type returnOnly, bool considerNull)
			{
				m_Mobiles = collection;
				m_Version = m_Mobiles.m_Version;
				m_ConsiderNull = considerNull;
				m_Type = returnOnly;
				Reset();
			}

			/// <summary>
			/// Sets the enumerator to its initial position, which is before the first element in the collection.
			/// </summary>
			public void Reset()
			{
				if(m_Version != m_Mobiles.m_Version)
					throw new InvalidOperationException("Collection has been modified.");

				m_Index = -1;
				m_AtEnd = true;
			}

			/// <summary>
			/// Gets the current element in the collection.
			/// </summary>
			public Mobile Current
			{
				get
				{
					if(!m_AtEnd)
						return m_Current;

					if(m_Index < m_Mobiles.Count)
						throw new InvalidOperationException("Enumeration not started.");
					else
						throw new InvalidOperationException("Enumeration ended.");
				}
			}

			object IEnumerator.Current
			{
				get
				{
					return Current;
				}
			}

			/// <summary>
			/// Advances the enumerator to the next element of the collection.
			/// </summary>
			/// <returns>true if the enumerator was successfully advanced to the next element;
			/// false if the enumerator has passed the end of the collection.</returns>
			public bool MoveNext()
			{
				if(m_Version != m_Mobiles.m_Version)
					throw new InvalidOperationException("Collection has been modified.");

				do
				{
					m_Index ++;

					if(m_Index < m_Mobiles.Count)
					{
						m_Current = m_Mobiles[m_Index];
						if(m_Type != null && !m_Type.IsInstanceOfType(m_Current))
							m_Current = null;
					}
					else
					{
						m_AtEnd = true;
						return false;
					}

				} while(m_Current != null && !m_ConsiderNull);

				m_AtEnd = false;
				return true;
			}
		}

		/// <summary>
		/// Returns an enumerator that can iterate through a collection.
		/// This enumerator will return all non-null value only.
		/// </summary>
		/// <returns>An <see cref="AccountMobileEnumerator"/> that can be used to iterate through the collection.</returns>
		public AccountMobileEnumerator GetEnumerator()
		{
			return new AccountMobileEnumerator(this, null, false);
		}

		/// <summary>
		/// Returns an enumerator that can iterate through a collection.
		/// </summary>
		/// <param name="alsoReturnNull">A value that tells if the enumerator will also return null value.</param>
		/// <returns>An <see cref="AccountMobileEnumerator"/> that can be used to iterate through the collection.</returns>
		public AccountMobileEnumerator GetEnumerator(bool alsoReturnNull)
		{
			return new AccountMobileEnumerator(this, null, alsoReturnNull);
		}

		/// <summary>
		/// Returns an enumerator that can iterate through a collection.
		/// This enumerator will return all non-null value only.
		/// </summary>
		/// <param name="returnOnlyType">A value that tells what which type to return values
		/// or null to return values from all type. All values not the specified type are skipped.</param>
		/// <returns>An <see cref="AccountMobileEnumerator"/> that can be used to iterate through the collection.</returns>
		public AccountMobileEnumerator GetEnumerator(Type returnOnlyType)
		{
			return new AccountMobileEnumerator(this, returnOnlyType, false);
		}

		/// <summary>
		/// Returns an enumerator that can iterate through a collection.
		/// </summary>
		/// <param name="returnOnlyType">A value that tells what which type to return values
		/// or null to return values from all type. All values not the specified type are skipped.</param>
		/// <param name="alsoReturnNull">A value that tells if the enumerator will also return null value.</param>
		/// <returns>An <see cref="AccountMobileEnumerator"/> that can be used to iterate through the collection.</returns>
		public AccountMobileEnumerator GetEnumerator(Type returnOnlyType, bool alsoReturnNull)
		{
			return new AccountMobileEnumerator(this, returnOnlyType, alsoReturnNull);
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
	}
}

Code:
// Core: Mobile.cs:line 3254
			if ( m_Account != null )
				m_Account.Mobiles.Remove( this );

With this, everything should compile and work fine without modifying any more of the existing code (for a smooth change ;)).

Anyway, this is just a suggestion, if you have any comment (except bad ones without justification), I will be listening!

ZixThree
 

Kamron

Knight
if I remember correctly.. foreach loops are REALLLY slow in comparison to for loops.. I would assume thats why they didn't use them.
 

ZixThree

Wanderer
I know that they are slower but I didn't see any critical section related to Mobile manipulation in an Account, and, more importantly, all this is called refactoring and is for better understanding and readability.

Also, in the above example, the foreach was a way to hide underlying collection complexity while returning only valid non-null mobile.

Another example that is more interesting:

Searching for an empty spot in the account mobile list:

Before:
Code:
// Scripts Misc\CharacterCreation.cs:line 579
private static Mobile CreateMobile( Account a )
{
	if ( a.Count >= a.Limit )
		return null;

	for ( int i = 0; i < a.Length; ++i )
	{
		if ( a[i] == null )
			return (a[i] = new PlayerMobile());
	}

	return null;
}

In between: (with only smallest modifications possible)
Code:
// Scripts Misc\CharacterCreation.cs:line 579
private static Mobile CreateMobile( Account a )
{
	if ( a.Mobiles.Count >= a.Limit )
		return null;

	for ( int i = 0; i < a.Mobiles.Capacity; ++i )
	{
		if ( a.Mobiles[i] == null )
			return (a.Mobiles[i] = new PlayerMobile());
	}

	return null;
}

The one above is really helping my understanding since Account should not have any length nor any count and we must know what it is related to to understand. Instead, when using a.Mobiles, you directly know you are dealing with a list of the mobiles.

After:
Code:
private static Mobile CreateMobile( Account a )
{
	if ( a.Mobiles.Count >= a.Limit )
		return null;

	int freeIndex = a.Mobiles.IndexOf(null);

	if(freeIndex != -1)
		return (a[freeIndex] = new PlayerMobile());

	return null;
}


PS: While I think about it, the example in my first post was the one to be almost a critical section since it is used at each login(if memory serves well).
 
Top