fix(core): Fixes accounts and moves it to codegen (#644)

- [X] Fixes TimeSpan not working with codegen
- [X] Fixes bad check for generic classes with a serialize method and deserialize ctor
- [X] Moves Accounts to codegen so it is versioned
- [X] Fixes deserialization of old Accounts with no version variable.
- [X] Fixes deserialize seek not doing anything. 🙈 
- [X] Adds Email to serialization
This commit is contained in:
Kamron Batman 2021-06-05 19:39:16 -07:00 committed by GitHub
parent ac4d349caa
commit 75db56edc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 378 additions and 464 deletions

View file

@ -44,12 +44,11 @@ namespace SerializationGenerator
if (isOverride)
{
source.AppendLine();
source.AppendLine($"{indent}base.Serialize(writer);");
source.AppendLine();
}
// Version
source.AppendLine();
source.AppendLine($"{indent}writer.{(encodedVersion ? "WriteEncodedInt" : "Write")}(_version);");
foreach (var property in properties)

View file

@ -34,7 +34,7 @@ namespace SerializableMigration
out string[] ruleArguments
)
{
if (symbol.IsIpAddress(compilation))
if (symbol.IsIpAddress(compilation) || symbol.IsTimeSpan(compilation))
{
ruleArguments = Array.Empty<string>();
return true;
@ -91,6 +91,7 @@ namespace SerializableMigration
var argument = property.RuleArguments.Length >= 1 ? property.RuleArguments[0] : null;
const string ipAddress = SymbolMetadata.IPADDRESS_CLASS;
const string timeSpan = SymbolMetadata.TIMESPAN_STRUCT;
const string date = "System.DateTime";
var readMethod = property.Type switch
@ -111,7 +112,8 @@ namespace SerializableMigration
"decimal" => "ReadDecimal",
date when argument == "DeltaTime" => "ReadDeltaTime",
date => "ReadDateTime",
ipAddress => "ReadIPAddress"
ipAddress => "ReadIPAddress",
timeSpan => "ReadTimeSpan"
};
var readArgument = readMethod == "ReadString" && argument == "InternString" ? "true" : "";

View file

@ -15,6 +15,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
@ -63,7 +64,7 @@ namespace SerializableMigration
var argument = property.RuleArguments.Length >= 1 &&
property.RuleArguments[0] == "DeserializationRequiresParent" ? ", this" : "";
source.AppendLine($"{indent}{propertyName} = new {property.Type}(reader{argument})");
source.AppendLine($"{indent}{propertyName} = new {property.Type}(reader{argument});");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)

View file

@ -23,6 +23,13 @@ namespace SerializationGenerator
public const string HASHSET_CLASS = "System.Collections.Generic.HashSet`1";
public const string IPADDRESS_CLASS = "System.Net.IPAddress";
public const string KEYVALUEPAIR_STRUCT = "System.Collections.Generic.KeyValuePair";
public const string TIMESPAN_STRUCT = "System.TimeSpan";
public static bool IsTimeSpan(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(TIMESPAN_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsIpAddress(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(

View file

@ -73,10 +73,10 @@ namespace SerializationGenerator
m => !m.IsStatic &&
m.MethodKind == MethodKind.Constructor &&
m.Parameters.Length <= 2 &&
m.Parameters[0].Equals(genericReaderInterface, SymbolEqualityComparer.Default)
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericReaderInterface)
);
requiresParent = genericCtor?.Parameters.Length == 2 && genericCtor.Parameters[1].Equals(symbol, SymbolEqualityComparer.Default);
requiresParent = genericCtor?.Parameters.Length == 2 && SymbolEqualityComparer.Default.Equals(genericCtor.Parameters[1].Type, symbol);
return genericCtor != null;
}
@ -98,7 +98,7 @@ namespace SerializationGenerator
m => !m.IsStatic &&
m.ReturnsVoid &&
m.Parameters.Length == 1 &&
m.Parameters[0].Equals(genericWriterInterface, SymbolEqualityComparer.Default) &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericWriterInterface) &&
m.DeclaredAccessibility == Accessibility.Public
);
}

View file

@ -95,7 +95,7 @@ namespace Server.Accounting
long GetTotalGold();
}
public interface IAccount : IGoldAccount, IComparable<IAccount>, ISerializable
public interface IAccount : IGoldAccount, IComparable<IAccount>
{
string Username { get; set; }
string Email { get; set; }

View file

@ -164,6 +164,8 @@ namespace Server
throw new ArgumentException($"BufferReader does not support {nameof(offset)} beyond Int32.MaxValue");
}
_position = (int)position;
return _position;
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Xml;
using Server.Accounting.Security;
@ -10,57 +11,137 @@ using Server.Network;
namespace Server.Accounting
{
public partial class Account : IAccount, IComparable<Account>
[Serializable(2)]
public partial class Account : IAccount, IComparable<Account>, ISerializable
{
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 Mobile[] m_Mobiles;
private AccessLevel m_AccessLevel;
private List<AccountComment> m_Comments;
private PasswordProtectionAlgorithm m_PasswordAlgorithm;
private List<AccountTag> m_Tags;
private TimeSpan m_TotalGameTime;
[SerializableField(0)]
private string _username;
[SerializableField(1)]
private PasswordProtectionAlgorithm _passwordAlgorithm;
[SerializableField(2)]
private string _password;
[SerializableField(3)]
private AccessLevel _accessLevel;
[SerializableField(4)]
private int _flags;
[SerializableField(5, setter: "private")]
private DateTime _created;
[SerializableField(6)]
private DateTime _lastLogin;
/// <summary>
/// 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.
/// </summary>
[SerializableField(7, setter: "private")]
[SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")]
public int _totalGold;
/// <summary>
/// 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.
/// </summary>
[SerializableField(8, setter: "private")]
[SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")]
public int _totalPlat;
[SerializableField(9, "private", "private")]
private Mobile[] _mobiles;
[SerializableField(10, setter: "private")]
private List<AccountComment> _comments;
[SerializableField(10, setter: "private")]
private List<AccountTag> _tags;
[SerializableField(11)]
private IPAddress[] _loginIPs;
/// <summary>
/// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP addresses
/// are allowed.
/// </summary>
[SerializableField(12)]
private string[] _ipRestrictions;
private TimeSpan _totalGameTime;
/// <summary>
/// Gets the total game time of this account, also considering the game time of characters
/// that have been deleted.
/// </summary>
[SerializableField(13)]
public TimeSpan TotalGameTime
{
get
{
for (var i = 0; i < _mobiles.Length; i++)
{
if (_mobiles[i] is PlayerMobile m && m.NetState != null)
{
return _totalGameTime + (Core.Now - m.SessionStart);
}
}
return _totalGameTime;
}
private set
{
_totalGameTime = value;
((ISerializable)this).MarkDirty();
}
}
[SerializableField(14)]
[SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")]
private string _email;
private Timer m_YoungTimer;
public Account(string username, string password) : this(Accounts.NewAccount)
{
Username = username;
_username = username;
SetPassword(password);
m_AccessLevel = AccessLevel.Player;
_accessLevel = AccessLevel.Player;
Created = LastLogin = Core.Now;
m_TotalGameTime = TimeSpan.Zero;
_created = _lastLogin = Core.Now;
_totalGameTime = TimeSpan.Zero;
m_Mobiles = new Mobile[7];
_mobiles = new Mobile[7];
IPRestrictions = Array.Empty<string>();
LoginIPs = Array.Empty<IPAddress>();
_ipRestrictions = Array.Empty<string>();
_loginIPs = Array.Empty<IPAddress>();
Accounts.Add(this);
((ISerializable)this).MarkDirty();
}
public Account(Serial serial)
{
Serial = serial;
SetTypeRef(GetType());
}
public Account(XmlElement node)
{
Serial = Accounts.NewAccount;
SetTypeRef(GetType());
Username = Utility.GetText(node["username"], "empty");
_username = Utility.GetText(node["username"], "empty");
Enum.TryParse(Utility.GetText(node["passwordAlgorithm"], null), true, out m_PasswordAlgorithm);
Enum.TryParse(Utility.GetText(node["passwordAlgorithm"], null), true, out _passwordAlgorithm);
// Backward compatibility with RunUO/ServUO
if (m_PasswordAlgorithm == PasswordProtectionAlgorithm.None)
if (_passwordAlgorithm == PasswordProtectionAlgorithm.None)
{
var upgraded =
UpgradePassword(
@ -78,44 +159,44 @@ namespace Server.Accounting
}
else
{
Password = Utility.GetText(node["password"], null);
_password = Utility.GetText(node["password"], null);
}
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), Core.Now);
LastLogin = Utility.GetXMLDateTime(Utility.GetText(node["lastLogin"], null), Core.Now);
Enum.TryParse(Utility.GetText(node["accessLevel"], "Player"), true, out _accessLevel);
_flags = Utility.GetXMLInt32(Utility.GetText(node["flags"], "0"), 0);
_created = Utility.GetXMLDateTime(Utility.GetText(node["created"], null), Core.Now);
_lastLogin = Utility.GetXMLDateTime(Utility.GetText(node["lastLogin"], null), Core.Now);
TotalGold = Utility.GetXMLInt32(Utility.GetText(node["totalGold"], "0"), 0);
TotalPlat = Utility.GetXMLInt32(Utility.GetText(node["totalPlat"], "0"), 0);
_totalGold = Utility.GetXMLInt32(Utility.GetText(node["totalGold"], "0"), 0);
_totalPlat = Utility.GetXMLInt32(Utility.GetText(node["totalPlat"], "0"), 0);
m_Mobiles = LoadMobiles(node);
m_Comments = LoadComments(node);
m_Tags = LoadTags(node);
LoginIPs = LoadAddressList(node);
IPRestrictions = LoadAccessCheck(node);
_mobiles = LoadMobiles(node);
_comments = LoadComments(node);
_tags = LoadTags(node);
_loginIPs = LoadAddressList(node);
_ipRestrictions = LoadAccessCheck(node);
for (var i = 0; i < m_Mobiles.Length; ++i)
for (var i = 0; i < _mobiles.Length; ++i)
{
if (m_Mobiles[i] != null)
if (_mobiles[i] != null)
{
m_Mobiles[i].Account = this;
_mobiles[i].Account = this;
}
}
var totalGameTime = Utility.GetXMLTimeSpan(Utility.GetText(node["totalGameTime"], null), TimeSpan.Zero);
if (totalGameTime == TimeSpan.Zero)
{
for (var i = 0; i < m_Mobiles.Length; i++)
for (var i = 0; i < _mobiles.Length; i++)
{
if (m_Mobiles[i] is PlayerMobile m)
if (_mobiles[i] is PlayerMobile m)
{
totalGameTime += m.GameTime;
}
}
}
m_TotalGameTime = totalGameTime;
_totalGameTime = totalGameTime;
if (Young)
{
@ -123,6 +204,7 @@ namespace Server.Accounting
}
Accounts.Add(this);
((ISerializable)this).MarkDirty();
}
public void SetTypeRef(Type type)
@ -141,37 +223,6 @@ namespace Server.Accounting
/// </summary>
public HardwareInfo HardwareInfo { get; set; }
/// <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; set; }
/// <summary>
/// List of IP addresses which have successfully logged into this account.
/// </summary>
public IPAddress[] LoginIPs { get; set; }
/// <summary>
/// List of account comments. Type of contained objects is AccountComment.
/// </summary>
public List<AccountComment> Comments => m_Comments ?? (m_Comments = new List<AccountComment>());
/// <summary>
/// List of account tags. Type of contained objects is AccountTag.
/// </summary>
public List<AccountTag> Tags => m_Tags ?? (m_Tags = new List<AccountTag>());
/// <summary>
/// Account username and password. May be null.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Internal bitfield of account flags. Consider using direct access properties (Banned, Young), or GetFlag/SetFlag methods
/// </summary>
public int Flags { get; set; }
/// <summary>
/// Gets or sets a flag indicating if this account is banned.
/// </summary>
@ -216,16 +267,6 @@ namespace Server.Accounting
}
}
/// <summary>
/// The date and time of when this account was created.
/// </summary>
public DateTime Created { get; private set; }
/// <summary>
/// Gets or sets the date and time when this account was last accessed.
/// </summary>
public DateTime LastLogin { get; set; }
/// <summary>
/// An account is considered inactive based upon LastLogin and InactiveDuration. If the account is empty, it is based upon
/// EmptyInactiveDuration
@ -239,168 +280,110 @@ namespace Server.Accounting
return false;
}
var inactiveLength = Core.Now - LastLogin;
var inactiveLength = Core.Now - _lastLogin;
return inactiveLength > (Count == 0 ? EmptyInactiveDuration : InactiveDuration);
}
}
/// <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 (var i = 0; i < m_Mobiles.Length; i++)
{
if (m_Mobiles[i] is PlayerMobile m && m.NetState != null)
{
return m_TotalGameTime + (Core.Now - m.SessionStart);
}
}
return m_TotalGameTime;
}
}
long ISerializable.SavePosition { get; set; }
BufferWriter ISerializable.SaveBuffer { get; set; }
public int TypeRef { get; private set; }
public Serial Serial { get; set; }
public void Deserialize(IGenericReader reader)
[AfterDeserialization]
private void AfterDeserialization()
{
Username = reader.ReadString();
m_PasswordAlgorithm = (PasswordProtectionAlgorithm)reader.ReadInt();
Password = reader.ReadString();
m_AccessLevel = (AccessLevel)reader.ReadInt();
Flags = reader.ReadInt();
Created = reader.ReadDateTime();
LastLogin = reader.ReadDateTime();
TotalGold = reader.ReadInt();
TotalPlat = reader.ReadInt();
m_Mobiles = new Mobile[7];
var length = reader.ReadInt();
for (int i = 0; i < length; i++)
if (_comments.Count == 0)
{
m_Mobiles[i] = reader.ReadEntity<Mobile>();
_comments = null;
}
length = reader.ReadInt();
m_Comments = length > 0 ? new List<AccountComment>(length) : null;
for (int i = 0; i < length; i++)
if (_tags.Count == 0)
{
m_Comments!.Add(new AccountComment(reader));
_tags = null;
}
length = reader.ReadInt();
m_Tags = length > 0 ? new List<AccountTag>(length) : null;
for (int i = 0; i < length; i++)
for (var i = 0; i < _mobiles.Length; ++i)
{
m_Tags!.Add(new AccountTag(reader));
}
length = reader.ReadInt();
LoginIPs = new IPAddress[length];
for (int i = 0; i < length; i++)
if (_mobiles[i] != null)
{
if (IPAddress.TryParse(reader.ReadString(), out var address))
{
LoginIPs[i] = Utility.Intern(address);
_mobiles[i].Account = this;
}
}
length = reader.ReadInt();
IPRestrictions = new string[length];
for (int i = 0; i < length; i++)
if (_totalGameTime == TimeSpan.Zero)
{
IPRestrictions[i] = reader.ReadString();
}
for (var i = 0; i < m_Mobiles.Length; ++i)
for (var i = 0; i < _mobiles.Length; i++)
{
if (m_Mobiles[i] != null)
if (_mobiles[i] is PlayerMobile m)
{
m_Mobiles[i].Account = this;
}
}
var totalGameTime = reader.ReadTimeSpan();
if (totalGameTime == TimeSpan.Zero)
{
for (var i = 0; i < m_Mobiles.Length; i++)
{
if (m_Mobiles[i] is PlayerMobile m)
{
totalGameTime += m.GameTime;
_totalGameTime += m.GameTime;
}
}
}
m_TotalGameTime = totalGameTime;
if (Young)
{
CheckYoung();
}
}
public void Serialize(IGenericWriter writer)
// Handle old deserialization before codegen
private void Deserialize(IGenericReader reader, int version)
{
writer.Write(Username);
writer.Write((int)m_PasswordAlgorithm);
writer.Write(Password);
writer.Write((int)m_AccessLevel);
writer.Write(Flags);
writer.Write(Created);
writer.Write(LastLogin);
writer.Write(TotalGold);
writer.Write(TotalPlat);
// Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing
reader.Seek(0, SeekOrigin.Begin);
writer.Write(Count);
for (int i = 0; i < m_Mobiles.Length; i++)
{
var m = m_Mobiles[i];
if (m != null)
{
writer.Write(m);
}
}
_username = reader.ReadString();
_passwordAlgorithm = (PasswordProtectionAlgorithm)reader.ReadInt();
_password = reader.ReadString();
_accessLevel = (AccessLevel)reader.ReadInt();
_flags = reader.ReadInt();
_created = reader.ReadDateTime();
_lastLogin = reader.ReadDateTime();
var length = m_Comments?.Count ?? 0;
writer.Write(length);
_totalGold = reader.ReadInt();
_totalPlat = reader.ReadInt();
_mobiles = new Mobile[7];
var length = reader.ReadInt();
for (int i = 0; i < length; i++)
{
m_Comments![i].Serialize(writer);
_mobiles[i] = reader.ReadEntity<Mobile>();
}
length = m_Tags?.Count ?? 0;
writer.Write(length);
length = reader.ReadInt();
_comments = length > 0 ? new List<AccountComment>(length) : null;
for (int i = 0; i < length; i++)
{
m_Tags![i].Serialize(writer);
_comments!.Add(new AccountComment(reader));
}
writer.Write(LoginIPs.Length);
for (int i = 0; i < LoginIPs.Length; i++)
length = reader.ReadInt();
_tags = length > 0 ? new List<AccountTag>(length) : null;
for (int i = 0; i < length; i++)
{
writer.Write(LoginIPs[i].ToString());
_tags!.Add(new AccountTag(reader));
}
writer.Write(IPRestrictions.Length);
for (int i = 0; i < IPRestrictions.Length; i++)
length = reader.ReadInt();
_loginIPs = new IPAddress[length];
for (int i = 0; i < length; i++)
{
writer.Write(IPRestrictions[i]);
if (IPAddress.TryParse(reader.ReadString(), out var address))
{
_loginIPs[i] = Utility.Intern(address);
}
}
writer.Write(TotalGameTime);
length = reader.ReadInt();
_ipRestrictions = new string[length];
for (int i = 0; i < length; i++)
{
_ipRestrictions[i] = reader.ReadString();
}
_totalGameTime = reader.ReadTimeSpan();
}
/// <summary>
@ -427,12 +410,12 @@ namespace Server.Accounting
m.Delete();
m.Account = null;
m_Mobiles[i] = null;
_mobiles[i] = null;
}
if (LoginIPs.Length != 0 && AccountHandler.IPTable.ContainsKey(LoginIPs[0]))
if (_loginIPs.Length != 0 && AccountHandler.IPTable.ContainsKey(_loginIPs[0]))
{
--AccountHandler.IPTable[LoginIPs[0]];
--AccountHandler.IPTable[_loginIPs[0]];
}
Deleted = true;
@ -441,45 +424,26 @@ namespace Server.Accounting
public bool Deleted { get; private set; }
/// <summary>
/// Account username. Case insensitive validation.
/// </summary>
public string Username { get; set; }
/// <summary>
/// Account email address.
/// </summary>
public string Email { get; set; }
/// <summary>
/// Initial AccessLevel for new characters created on this account.
/// </summary>
public AccessLevel AccessLevel
{
get => m_AccessLevel;
set => m_AccessLevel = value;
}
public void SetPassword(string plainPassword)
{
Password = AccountSecurity.CurrentPasswordProtection.EncryptPassword(plainPassword);
m_PasswordAlgorithm = AccountSecurity.CurrentAlgorithm;
PasswordAlgorithm = AccountSecurity.CurrentAlgorithm;
}
public bool CheckPassword(string plainPassword)
{
var phrase = m_PasswordAlgorithm == PasswordProtectionAlgorithm.SHA1
? $"{Username}{plainPassword}"
var phrase = _passwordAlgorithm == PasswordProtectionAlgorithm.SHA1
? $"{_username}{plainPassword}"
: plainPassword;
var ok = AccountSecurity.GetPasswordProtection(m_PasswordAlgorithm).ValidatePassword(Password, phrase);
var ok = AccountSecurity.GetPasswordProtection(_passwordAlgorithm).ValidatePassword(Password, phrase);
if (!ok)
{
return false;
}
// Upgrade the password protection in case we change the algorithm
if (m_PasswordAlgorithm != AccountSecurity.CurrentAlgorithm)
if (_passwordAlgorithm != AccountSecurity.CurrentAlgorithm)
{
SetPassword(plainPassword);
}
@ -518,7 +482,7 @@ namespace Server.Accounting
/// <summary>
/// Gets the maximum amount of characters that this account can hold.
/// </summary>
public int Length => m_Mobiles.Length;
public int Length => _mobiles.Length;
/// <summary>
/// Gets or sets the character at a specified index for this account. Out of bound index values are handled; null returned
@ -528,9 +492,9 @@ namespace Server.Accounting
{
get
{
if (index >= 0 && index < m_Mobiles.Length)
if (index >= 0 && index < _mobiles.Length)
{
var m = m_Mobiles[index];
var m = _mobiles[index];
if (m?.Deleted != true)
{
@ -540,25 +504,27 @@ namespace Server.Accounting
// This is the only place that clears a mobile for garbage collection
// outside of an entire account deletion.
m.Account = null;
m_Mobiles[index] = null;
_mobiles[index] = null;
((ISerializable)this).MarkDirty();
}
return null;
}
set
{
if (index >= 0 && index < m_Mobiles.Length)
if (index >= 0 && index < _mobiles.Length)
{
if (m_Mobiles[index] != null)
if (_mobiles[index] != null)
{
m_Mobiles[index].Account = null;
_mobiles[index].Account = null;
}
m_Mobiles[index] = value;
_mobiles[index] = value;
((ISerializable)this).MarkDirty();
if (m_Mobiles[index] != null)
if (_mobiles[index] != null)
{
m_Mobiles[index].Account = this;
_mobiles[index].Account = this;
}
}
}
@ -566,23 +532,6 @@ namespace Server.Accounting
public int CompareTo(IAccount other) => string.CompareOrdinal(Username, other?.Username);
/// <summary>
/// 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.
/// </summary>
[CommandProperty(AccessLevel.Administrator)]
public int TotalGold { get; private set; }
/// <summary>
/// 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.
/// </summary>
[CommandProperty(AccessLevel.Administrator)]
public int TotalPlat { get; private set; }
/// <summary>
/// Attempts to deposit the given amount of Gold into this account.
/// If the given amount is greater than the CurrencyThreshold,
@ -634,7 +583,7 @@ namespace Server.Accounting
return true;
}
if (amount > TotalGold)
if (amount > _totalGold)
{
return false;
}
@ -656,7 +605,7 @@ namespace Server.Accounting
return true;
}
if (amount > TotalPlat)
if (amount > _totalPlat)
{
return false;
}
@ -671,15 +620,15 @@ namespace Server.Accounting
/// This is strictly for backwards compatibility
/// </summary>
/// <returns>Total gold, capped at Int32.MaxValue</returns>
public long GetTotalGold() => TotalGold + TotalPlat * AccountGold.CurrencyThreshold;
public long GetTotalGold() => _totalGold + _totalPlat * AccountGold.CurrencyThreshold;
public int CompareTo(Account other) => string.CompareOrdinal(Username, other?.Username);
public int CompareTo(Account other) => string.CompareOrdinal(_username, other?._username);
/// <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) => (Flags & (1 << index)) != 0;
public bool GetFlag(int index) => (_flags & (1 << index)) != 0;
/// <summary>
/// Sets the value of a specific flag in the Flags bitfield.
@ -705,7 +654,8 @@ namespace Server.Accounting
/// <param name="value">New tag value.</param>
public void AddTag(string name, string value)
{
Tags.Add(new AccountTag(name, value));
_tags.Add(new AccountTag(name, value));
((ISerializable)this).MarkDirty();
}
/// <summary>
@ -714,18 +664,19 @@ namespace Server.Accounting
/// <param name="name">Tag name to remove.</param>
public void RemoveTag(string name)
{
for (var i = Tags.Count - 1; i >= 0; --i)
for (var i = _tags.Count - 1; i >= 0; --i)
{
if (i >= Tags.Count)
if (i >= _tags.Count)
{
continue;
}
var tag = Tags[i];
var tag = _tags[i];
if (tag.Name == name)
{
Tags.RemoveAt(i);
_tags.RemoveAt(i);
((ISerializable)this).MarkDirty();
}
}
}
@ -737,13 +688,14 @@ namespace Server.Accounting
/// <param name="value">Tag value.</param>
public void SetTag(string name, string value)
{
for (var i = 0; i < Tags.Count; ++i)
for (var i = 0; i < _tags.Count; ++i)
{
var tag = Tags[i];
var tag = _tags[i];
if (tag.Name == name)
{
tag.Value = value;
((ISerializable)this).MarkDirty();
return;
}
}
@ -757,9 +709,9 @@ namespace Server.Accounting
/// <param name="name">Name of the desired tag value.</param>
public string GetTag(string name)
{
for (var i = 0; i < Tags.Count; ++i)
for (var i = 0; i < _tags.Count; ++i)
{
var tag = Tags[i];
var tag = _tags[i];
if (tag.Name == name)
{
@ -850,7 +802,7 @@ namespace Server.Accounting
private static void EventSink_Disconnected(Mobile m)
{
if (!(m.Account is Account acc))
if (m.Account is not Account acc)
{
return;
}
@ -861,22 +813,22 @@ namespace Server.Accounting
acc.m_YoungTimer = null;
}
if (!(m is PlayerMobile pm))
if (m is not PlayerMobile pm)
{
return;
}
acc.m_TotalGameTime += Core.Now - pm.SessionStart;
acc.TotalGameTime += Core.Now - pm.SessionStart;
}
private static void EventSink_Login(Mobile m)
{
if (!(m is PlayerMobile pm))
if (m is not PlayerMobile pm)
{
return;
}
if (!(m.Account is Account acc))
if (m.Account is not Account acc)
{
return;
}
@ -898,9 +850,9 @@ namespace Server.Accounting
{
Young = false;
for (var i = 0; i < m_Mobiles.Length; i++)
for (var i = 0; i < _mobiles.Length; i++)
{
if (m_Mobiles[i] is PlayerMobile m && m.Young)
if (_mobiles[i] is PlayerMobile { Young: true } m)
{
m.Young = false;
@ -930,12 +882,12 @@ namespace Server.Accounting
private bool UpgradePassword(string password, PasswordProtectionAlgorithm algorithm)
{
if (password == null || algorithm < m_PasswordAlgorithm)
if (password == null || algorithm < _passwordAlgorithm)
{
return false;
}
m_PasswordAlgorithm = algorithm;
PasswordAlgorithm = algorithm;
Password = password.ReplaceOrdinal("-", string.Empty);
return true;
}
@ -1136,7 +1088,7 @@ namespace Server.Accounting
{
var hasAccess = false;
if (m_AccessLevel >= level)
if (_accessLevel >= level)
{
hasAccess = true;
}
@ -1153,7 +1105,7 @@ namespace Server.Accounting
}
}
Console.WriteLine("{0} {1}", hasAccess ? "yes" : "no", m_AccessLevel);
Console.WriteLine("{0} {1}", hasAccess ? "yes" : "no", _accessLevel);
if (!hasAccess)
{
@ -1161,11 +1113,11 @@ namespace Server.Accounting
}
}
var accessAllowed = IPRestrictions.Length == 0 || IPLimiter.IsExempt(ipAddress);
var accessAllowed = _ipRestrictions.Length == 0 || IPLimiter.IsExempt(ipAddress);
for (var i = 0; !accessAllowed && i < IPRestrictions.Length; ++i)
for (var i = 0; !accessAllowed && i < _ipRestrictions.Length; ++i)
{
accessAllowed = IPAddress.Parse(IPRestrictions[i]).Equals(ipAddress);
accessAllowed = IPAddress.Parse(_ipRestrictions[i]).Equals(ipAddress);
}
return accessAllowed;
@ -1190,7 +1142,7 @@ namespace Server.Accounting
return;
}
if (LoginIPs.Length == 0)
if (_loginIPs.Length == 0)
{
AccountHandler.IPTable.TryGetValue(ipAddress, out var result);
AccountHandler.IPTable[ipAddress] = result + 1;
@ -1198,9 +1150,9 @@ namespace Server.Accounting
var contains = false;
for (var i = 0; !contains && i < LoginIPs.Length; ++i)
for (var i = 0; !contains && i < _loginIPs.Length; ++i)
{
contains = LoginIPs[i].Equals(ipAddress);
contains = _loginIPs[i].Equals(ipAddress);
}
if (contains)
@ -1208,7 +1160,7 @@ namespace Server.Accounting
return;
}
var old = LoginIPs;
var old = _loginIPs;
LoginIPs = new IPAddress[old.Length + 1];
for (var i = 0; i < old.Length; ++i)
@ -1239,135 +1191,7 @@ namespace Server.Accounting
return hasAccess;
}
/// <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(Username);
xml.WriteEndElement();
xml.WriteStartElement("passwordAlgorithm");
xml.WriteString(m_PasswordAlgorithm.ToString());
xml.WriteEndElement();
xml.WriteStartElement("password");
xml.WriteString(Password);
xml.WriteEndElement();
if (m_AccessLevel != AccessLevel.Player)
{
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 (var i = 0; i < m_Mobiles.Length; ++i)
{
var m = m_Mobiles[i];
if (m?.Deleted == false)
{
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 (var i = 0; i < m_Comments.Count; ++i)
{
m_Comments[i].Save(xml);
}
xml.WriteEndElement();
}
if (m_Tags?.Count > 0)
{
xml.WriteStartElement("tags");
for (var i = 0; i < m_Tags.Count; ++i)
{
m_Tags[i].Save(xml);
}
xml.WriteEndElement();
}
if (LoginIPs.Length > 0)
{
xml.WriteStartElement("addressList");
xml.WriteAttributeString("count", LoginIPs.Length.ToString());
for (var i = 0; i < LoginIPs.Length; ++i)
{
xml.WriteStartElement("ip");
xml.WriteString(LoginIPs[i].ToString());
xml.WriteEndElement();
}
xml.WriteEndElement();
}
if (IPRestrictions.Length > 0)
{
xml.WriteStartElement("accessCheck");
for (var i = 0; i < IPRestrictions.Length; ++i)
{
xml.WriteStartElement("ip");
xml.WriteString(IPRestrictions[i]);
xml.WriteEndElement();
}
xml.WriteEndElement();
}
xml.WriteStartElement("totalGold");
xml.WriteString(XmlConvert.ToString(TotalGold));
xml.WriteEndElement();
xml.WriteStartElement("totalPlat");
xml.WriteString(XmlConvert.ToString(TotalPlat));
xml.WriteEndElement();
xml.WriteEndElement();
}
public override string ToString() => Username;
public override string ToString() => _username;
private class YoungTimer : Timer
{

View file

@ -64,23 +64,6 @@ namespace Server.Accounting
/// </summary>
public DateTime LastModified { get; private set; }
/// <summary>
/// Serializes this AccountComment instance to an XmlTextWriter.
/// </summary>
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
public void Save(XmlTextWriter xml)
{
xml.WriteStartElement("comment");
xml.WriteAttributeString("addedBy", AddedBy);
xml.WriteAttributeString("lastModified", XmlConvert.ToString(LastModified, XmlDateTimeSerializationMode.Utc));
xml.WriteString(m_Content);
xml.WriteEndElement();
}
/// <summary>
/// Serializes this AccountComment instance.
/// </summary>

View file

@ -45,18 +45,6 @@ namespace Server.Accounting
/// </summary>
public string Value { get; set; }
/// <summary>
/// Serializes this AccountTag instance to an XmlTextWriter.
/// </summary>
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
public void Save(XmlTextWriter xml)
{
xml.WriteStartElement("tag");
xml.WriteAttributeString("name", Name);
xml.WriteString(Value);
xml.WriteEndElement();
}
/// <summary>
/// Serializes this AccountTag instance to an XmlTextWriter.
/// </summary>

View file

@ -10,8 +10,8 @@ namespace Server.Accounting
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Accounts));
private static readonly Dictionary<string, IAccount> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
private static Dictionary<Serial, IAccount> _accountsById = new(32);
private static readonly Dictionary<string, Account> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
private static Dictionary<Serial, Account> _accountsById = new(32);
private static Serial _lastAccount;
internal static List<Type> Types { get; } = new();
@ -44,7 +44,7 @@ namespace Server.Accounting
Persistence.Register("Accounts", Serialize, WriteSnapshot, Deserialize);
internal static void Serialize() =>
EntityPersistence.SaveEntities(_accountsById.Values, account => account.Serialize());
EntityPersistence.SaveEntities(_accountsById.Values, account => ((ISerializable)account).Serialize());
internal static void WriteSnapshot(string basePath)
{
@ -54,19 +54,19 @@ namespace Server.Accounting
public static IEnumerable<IAccount> GetAccounts() => _accountsByName.Values;
public static IAccount GetAccount(string username)
public static Account GetAccount(string username)
{
_accountsByName.TryGetValue(username, out var a);
return a;
}
public static void Add(IAccount a)
public static void Add(Account a)
{
_accountsByName[a.Username] = a;
_accountsById[a.Serial] = a;
}
public static void Remove(IAccount a)
public static void Remove(Account a)
{
_accountsByName.Remove(a.Username);
_accountsById.Remove(a.Serial);
@ -85,7 +85,7 @@ namespace Server.Accounting
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
_accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List<EntityIndex<IAccount>> accounts);
_accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List<EntityIndex<Account>> accounts);
EntityPersistence.LoadData(path, indexInfo, accounts);
foreach (var a in _accountsById.Values)

View file

@ -1056,7 +1056,7 @@ namespace Server.Gumps
if (m_List == null)
{
ipRestrictions = a.IPRestrictions.ToList();
ipRestrictions = a.IpRestrictions.ToList();
m_List = ipRestrictions.ToList<object>();
}
else
@ -3000,7 +3000,7 @@ namespace Server.Gumps
}
else
{
var list = a.IPRestrictions;
var list = a.IpRestrictions;
var contains = false;
for (var i = 0; !contains && i < list.Length; ++i)
@ -3023,7 +3023,7 @@ namespace Server.Gumps
newList[list.Length] = ip;
a.IPRestrictions = newList;
a.IpRestrictions = newList;
notice = $"{ip} : Added to restriction list.";
}
@ -3888,9 +3888,9 @@ namespace Server.Gumps
}
else if (m_PageType == AdminGumpPage.AccountDetails_Access_Restrictions)
{
var list = a.IPRestrictions.ToList();
var list = a.IpRestrictions.ToList();
list.Remove(m_List[index] as string);
a.IPRestrictions = list.ToArray();
a.IpRestrictions = list.ToArray();
from.SendGump(
new AdminGump(

View file

@ -0,0 +1,108 @@
{
"version": 2,
"type": "Server.Accounting.Account",
"properties": [
{
"name": "Username",
"type": "string",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "PasswordAlgorithm",
"type": "Server.Accounting.Security.PasswordProtectionAlgorithm",
"rule": "EnumMigrationRule",
"ruleArguments": []
},
{
"name": "Password",
"type": "string",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "AccessLevel",
"type": "Server.AccessLevel",
"rule": "EnumMigrationRule",
"ruleArguments": []
},
{
"name": "Flags",
"type": "int",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "Created",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "LastLogin",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "TotalGold",
"type": "int",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "TotalPlat",
"type": "int",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "Mobiles",
"type": "Server.Mobile[]",
"rule": "ArrayMigrationRule",
"ruleArguments": [
"Server.Mobile",
"SerializableInterfaceMigrationRule"
]
},
{
"name": "Comments",
"type": "System.Collections.Generic.List\u003CServer.Accounting.AccountComment\u003E",
"rule": "ListMigrationRule",
"ruleArguments": [
"Server.Accounting.AccountComment",
"SerializationMethodSignatureMigrationRule"
]
},
{
"name": "LoginIPs",
"type": "System.Net.IPAddress[]",
"rule": "ArrayMigrationRule",
"ruleArguments": [
"System.Net.IPAddress",
"PrimitiveTypeMigrationRule"
]
},
{
"name": "IpRestrictions",
"type": "string[]",
"rule": "ArrayMigrationRule",
"ruleArguments": [
"string",
"PrimitiveTypeMigrationRule"
]
},
{
"name": "TotalGameTime",
"type": "System.TimeSpan",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
},
{
"name": "Email",
"type": "string",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": []
}
]
}