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):
for a simpler to read:
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:
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
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