From fa5eafdaffa3248d813d6f9723e72eb737269d0e Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 26 Sep 2021 01:09:45 -0700 Subject: [PATCH] fix: Adds Created, LastSerialized, and BeforeSerialize for all entities. (#775) * Adds `BeforeSerialized` for entities to handle cleanup. * Adds `Created` and `LastSerialized` fields to all entities. While this is a big bloat, this will be necessary for identifying dangling references to other invalid entities. * Changes formula for determining a valid reference to be _not null, not deleted, and reference's created date must be at or before the entities last serialized date_. * Adds versioning to idx file and serializes `Created` and `LastSerialized`. * Fixes Save Stats and also disables it by default. --- Projects/Server/Guild.cs | 8 ++ Projects/Server/IEntity.cs | 8 ++ Projects/Server/Items/Item.cs | 10 ++ Projects/Server/Mobiles/Mobile.cs | 28 +++- .../Server/Serialization/BinaryFileReader.cs | 2 + .../Server/Serialization/BinaryFileWriter.cs | 1 - Projects/Server/Serialization/BufferReader.cs | 4 + .../Server/Serialization/IGenericReader.cs | 3 + .../Server/Serialization/IGenericWriter.cs | 22 ++- .../Server/Serialization/ISerializable.cs | 15 ++- Projects/Server/World/EntityPersistence.cs | 31 ++++- Projects/Server/World/World.cs | 27 +++- Projects/UOContent/Accounting/Account.cs | 63 ++++++--- .../UOContent/Accounting/AccountHandler.cs | 2 +- Projects/UOContent/Compression/TarArchive.cs | 3 +- .../Character Statue Maker/CharacterStatue.cs | 3 +- .../Engines/Veteran Rewards/RewardSystem.cs | 32 +---- .../Items/Skill Items/Camping/Campfire.cs | 4 - .../Server.Accounting.Account.v4.json | 127 ++++++++++++++++++ Projects/UOContent/Misc/ClientVerification.cs | 2 +- Projects/UOContent/Misc/Guild.cs | 4 + Projects/UOContent/Misc/Profile.cs | 10 +- .../NPC/Guildmasters/BaseGuildmaster.cs | 4 +- 23 files changed, 325 insertions(+), 88 deletions(-) create mode 100644 Projects/UOContent/Migrations/Server.Accounting.Account.v4.json diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs index e9e6cc84a..5ff38abd9 100644 --- a/Projects/Server/Guild.cs +++ b/Projects/Server/Guild.cs @@ -63,13 +63,21 @@ namespace Server.Guilds [CommandProperty(AccessLevel.Counselor)] public Serial Serial { get; } + [CommandProperty(AccessLevel.GameMaster, readOnly: true)] + public DateTime Created { get; set; } = Core.Now; + + [CommandProperty(AccessLevel.GameMaster)] + DateTime ISerializable.LastSerialized { get; set; } = Core.Now; + long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } public int TypeRef { get; private set; } + public abstract void BeforeSerialize(); public abstract void Serialize(IGenericWriter writer); + public abstract void Deserialize(IGenericReader reader); public abstract void OnDelete(Mobile mob); diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs index 1d2dbcf62..f5b36b0d9 100644 --- a/Projects/Server/IEntity.cs +++ b/Projects/Server/IEntity.cs @@ -47,6 +47,10 @@ namespace Server { } + DateTime ISerializable.Created { get; set; } = Core.Now; + + DateTime ISerializable.LastSerialized { get; set; } = DateTime.MaxValue; + long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } @@ -103,6 +107,10 @@ namespace Server && p.Y >= Location.m_Y - range && p.Y <= Location.m_Y + range; + public void BeforeSerialize() + { + } + public void Deserialize(IGenericReader reader) { // Should not actually be saved diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index d6d023cf7..bff61cba3 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -771,6 +771,12 @@ namespace Server AddNameProperties(list); } + [CommandProperty(AccessLevel.GameMaster, readOnly: true)] + public DateTime Created { get; set; } = Core.Now; + + [CommandProperty(AccessLevel.GameMaster)] + DateTime ISerializable.LastSerialized { get; set; } = Core.Now; + long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } @@ -2571,6 +2577,10 @@ namespace Server } } + public virtual void BeforeSerialize() + { + } + public virtual void Deserialize(IGenericReader reader) { var version = reader.ReadInt(); diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 451a173ad..02c2a43f2 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -1147,9 +1147,6 @@ namespace Server public virtual bool ShouldCheckStatTimers => true; - [CommandProperty(AccessLevel.GameMaster)] - public DateTime CreationTime { get; private set; } - [CommandProperty(AccessLevel.GameMaster)] public int LightLevel { @@ -2472,6 +2469,12 @@ namespace Server AddNameProperties(list); } + [CommandProperty(AccessLevel.GameMaster, readOnly: true)] + public DateTime Created { get; set; } = Core.Now; + + [CommandProperty(AccessLevel.GameMaster)] + DateTime ISerializable.LastSerialized { get; set; } = Core.Now; + long ISerializable.SavePosition { get; set; } = -1; BufferWriter ISerializable.SaveBuffer { get; set; } @@ -2483,7 +2486,7 @@ namespace Server public virtual void Serialize(IGenericWriter writer) { - writer.Write(32); // version + writer.Write(33); // version writer.WriteDeltaTime(LastStrGain); writer.WriteDeltaTime(LastIntGain); @@ -2519,7 +2522,7 @@ namespace Server writer.Write(Corpse); - writer.Write(CreationTime); + // writer.Write(CreationTime); Stabled.Tidy(); writer.Write(Stabled); @@ -6167,12 +6170,21 @@ namespace Server { } + public virtual void BeforeSerialize() + { + } + public virtual void Deserialize(IGenericReader reader) { var version = reader.ReadInt(); switch (version) { + case 33: + { + // Removed created + goto case 32; + } case 32: { // Removed StuckMenu @@ -6232,7 +6244,10 @@ namespace Server } case 23: { - CreationTime = reader.ReadDateTime(); + if (version < 33) + { + Created = reader.ReadDateTime(); + } goto case 22; } @@ -7818,7 +7833,6 @@ namespace Server DamageEntries = new List(); NextSkillTime = Core.TickCount; - CreationTime = Core.Now; } public virtual void Delta(MobileDelta flag) diff --git a/Projects/Server/Serialization/BinaryFileReader.cs b/Projects/Server/Serialization/BinaryFileReader.cs index 890fc0c49..81064faa3 100644 --- a/Projects/Server/Serialization/BinaryFileReader.cs +++ b/Projects/Server/Serialization/BinaryFileReader.cs @@ -30,6 +30,8 @@ namespace Server [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Close() => _reader.Close(); + public DateTime LastSerialized { get; init; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadString(bool intern = false) { diff --git a/Projects/Server/Serialization/BinaryFileWriter.cs b/Projects/Server/Serialization/BinaryFileWriter.cs index 340e9c66b..9980eb000 100644 --- a/Projects/Server/Serialization/BinaryFileWriter.cs +++ b/Projects/Server/Serialization/BinaryFileWriter.cs @@ -35,7 +35,6 @@ namespace Server public override long Position => _position + Index; - protected override int BufferSize => 81920; public override void Flush() diff --git a/Projects/Server/Serialization/BufferReader.cs b/Projects/Server/Serialization/BufferReader.cs index 5b512215b..e04135db5 100644 --- a/Projects/Server/Serialization/BufferReader.cs +++ b/Projects/Server/Serialization/BufferReader.cs @@ -37,6 +37,8 @@ namespace Server _encoding = encoding ?? TextEncoding.UTF8; } + public BufferReader(byte[] buffer, DateTime LastSerialized) : this(buffer) => LastSerialized = LastSerialized; + public void Reset(byte[] newBuffer, out byte[] oldBuffer) { oldBuffer = _buffer; @@ -45,6 +47,8 @@ namespace Server } // Compatible with BinaryReader.ReadString() + public DateTime LastSerialized { get; init; } = DateTime.MinValue; + public string ReadString(bool intern = false) { if (!ReadBool()) diff --git a/Projects/Server/Serialization/IGenericReader.cs b/Projects/Server/Serialization/IGenericReader.cs index 27194c4eb..67e27764e 100644 --- a/Projects/Server/Serialization/IGenericReader.cs +++ b/Projects/Server/Serialization/IGenericReader.cs @@ -21,6 +21,9 @@ namespace Server { public interface IGenericReader { + // Used to determine valid Entity deserialization + DateTime LastSerialized { get; init; } + string ReadString(bool intern = false); long ReadLong(); ulong ReadULong(); diff --git a/Projects/Server/Serialization/IGenericWriter.cs b/Projects/Server/Serialization/IGenericWriter.cs index c7f8a0281..95aa9d92f 100644 --- a/Projects/Server/Serialization/IGenericWriter.cs +++ b/Projects/Server/Serialization/IGenericWriter.cs @@ -39,14 +39,13 @@ namespace Server void Write(DateTime value) { - var ticks = (value.Kind switch + // If DateTimeKind is Unspecified, we can't assume it needs to be converted. + if (value.Kind == DateTimeKind.Local) { - DateTimeKind.Local => value.ToUniversalTime(), - DateTimeKind.Unspecified => value.ToLocalTime().ToUniversalTime(), - _ => value - }).Ticks; + value = value.ToUniversalTime(); + } - Write(ticks); + Write(value.Ticks); } void WriteDeltaTime(DateTime value) { @@ -59,17 +58,16 @@ namespace Server if (value == DateTime.MaxValue) { Write(long.MaxValue); + return; } - var ticks = (value.Kind switch + if (value.Kind == DateTimeKind.Local) { - DateTimeKind.Local => value.ToUniversalTime(), - DateTimeKind.Unspecified => value.ToLocalTime().ToUniversalTime(), - _ => value - }).Ticks; + value = value.ToUniversalTime(); + } // Technically supports negative deltas for times in the past - Write(ticks - DateTime.UtcNow.Ticks); + Write(value.Ticks - DateTime.UtcNow.Ticks); } void Write(IPAddress value) { diff --git a/Projects/Server/Serialization/ISerializable.cs b/Projects/Server/Serialization/ISerializable.cs index d91366c9b..3488c2c38 100644 --- a/Projects/Server/Serialization/ISerializable.cs +++ b/Projects/Server/Serialization/ISerializable.cs @@ -20,10 +20,20 @@ namespace Server { public interface ISerializable { + // Should be serialized/deserialized with the index so it can be referenced by IGenericReader + DateTime Created { get; set; } + + // Should be serialized/deserialized with the index so it can be referenced by IGenericReader + DateTime LastSerialized { get; protected internal set; } long SavePosition { get; protected internal set; } BufferWriter SaveBuffer { get; protected internal set; } + int TypeRef { get; } Serial Serial { get; } + + // Executed on every entity, before it's serialized. + // For example, this is used to clean up weak references and mark them dirty. + void BeforeSerialize(); void Deserialize(IGenericReader reader); void Serialize(IGenericWriter writer); void Delete(); @@ -48,6 +58,8 @@ namespace Server { SaveBuffer ??= new BufferWriter(true); + BeforeSerialize(); + // Clean, don't bother serializing if (SavePosition > -1) { @@ -55,6 +67,7 @@ namespace Server return; } + LastSerialized = Core.Now; SaveBuffer.Seek(0, SeekOrigin.Begin); Serialize(SaveBuffer); @@ -64,7 +77,7 @@ namespace Server } else { - SavePosition = -1; + this.MarkDirty(); } } } diff --git a/Projects/Server/World/EntityPersistence.cs b/Projects/Server/World/EntityPersistence.cs index 93c597bba..4ba5b68c2 100644 --- a/Projects/Server/World/EntityPersistence.cs +++ b/Projects/Server/World/EntityPersistence.cs @@ -24,6 +24,8 @@ namespace Server { public static class EntityPersistence { + private const int _idxVersion = 1; + public static void WriteEntities( IIndexInfo indexInfo, Dictionary entities, @@ -48,6 +50,7 @@ namespace Server using var tdb = new BinaryFileWriter(tdbPath, false); using var bin = new BinaryFileWriter(binPath, true); + idx.Write(1); // Version idx.Write(entities.Count); foreach (var e in entities.Values) { @@ -55,6 +58,8 @@ namespace Server idx.Write(e.TypeRef); idx.Write(e.Serial); + idx.Write(e.Created.Ticks); + idx.Write(e.LastSerialized.Ticks); idx.Write(start); e.SerializeTo(bin); @@ -110,12 +115,28 @@ namespace Server List> types = ReadTypes(tdbReader); - var count = idxReader.ReadInt32(); + int count; + var version = idxReader.ReadInt32(); + + // Handle non-versioned (version 0). + if (version > _idxVersion || idx.Length - 4 - version * 20 == 0) + { + count = version; + version = 0; + } + else + { + count = idxReader.ReadInt32(); + } + + var now = DateTime.UtcNow; for (int i = 0; i < count; ++i) { var typeID = idxReader.ReadInt32(); - var number = idxReader.ReadUInt32(); + var serial = idxReader.ReadUInt32(); + var created = version == 0 ? now : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc); + var lastSerialized = version == 0 ? DateTime.MinValue : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc); var pos = idxReader.ReadInt64(); var length = idxReader.ReadInt32(); @@ -127,12 +148,14 @@ namespace Server } ConstructorInfo ctor = objs.Item1; - I indexer = indexInfo.CreateIndex(number); + I indexer = indexInfo.CreateIndex(serial); ctorArgs[0] = indexer; if (ctor.Invoke(ctorArgs) is T t) { + t.Created = created; + t.LastSerialized = lastSerialized; entities.Add(new EntityIndex(t, typeID, pos, length)); map[indexer] = t; } @@ -180,7 +203,7 @@ namespace Server var buffer = GC.AllocateUninitializedArray(entry.Length); if (br == null) { - br = new BufferReader(buffer); + br = new BufferReader(buffer, t.LastSerialized); } else { diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs index cb18a5396..53968cce2 100644 --- a/Projects/Server/World/World.cs +++ b/Projects/Server/World/World.cs @@ -47,6 +47,7 @@ namespace Server private static string _tempSavePath; // Path to the temporary folder for the save private static string _savePath; // Path to "Saves" folder + private static bool _enableSaveStats; public const bool DirtyTrackingEnabled = false; public const uint ItemOffset = 0x40000000; @@ -144,6 +145,7 @@ namespace Server _tempSavePath = Path.Combine(Core.BaseDirectory, tempSavePath); var savePath = ServerConfiguration.GetOrUpdateSetting("world.savePath", "Saves"); _savePath = Path.Combine(Core.BaseDirectory, savePath); + _enableSaveStats = ServerConfiguration.GetOrUpdateSetting("world.enableSaveStats", false); // Mobiles & Items Persistence.Register("Mobiles & Items", SaveEntities, WriteEntities, LoadEntities, 1); @@ -341,7 +343,10 @@ namespace Server int count = 0; var timestamp = Utility.GetTimeStamp(); - using var op = new StreamWriter("Logs/Saves/Save-Stats-{0}.log", true); + var saveStatsPath = Path.Combine(Core.BaseDirectory, $"Logs/Saves/Save-Stats-{timestamp}.log"); + AssemblyHandler.EnsureDirectory(saveStatsPath); + + using var op = new StreamWriter(saveStatsPath, true); for (var i = 0; i < entityTypes.Length; i++) { @@ -373,7 +378,10 @@ namespace Server EntityPersistence.WriteEntities(itemIndexInfo, Items, ItemTypes, basePath, out var itemCounts); EntityPersistence.WriteEntities(guildIndexInfo, Guilds, GuildTypes, basePath, out var guildCounts); - TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList()); + if (_enableSaveStats) + { + TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList()); + } } public static void WriteFiles(object state) @@ -652,13 +660,24 @@ namespace Server Serial serial = reader.ReadSerial(); var typeT = typeof(T); + T entity; + // Add to this list when creating new serializable types if (typeof(BaseGuild).IsAssignableTo(typeT)) { - return FindGuild(serial) as T; + entity = FindGuild(serial) as T; + } + else + { + entity = FindEntity(serial) as T; } - return FindEntity(serial) as T; + if (entity?.Deleted == false) + { + return entity; + } + + return entity?.Created <= reader.LastSerialized ? entity : null; } public static List ReadEntityList(this IGenericReader reader) where T : class, ISerializable diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index 02a3cc2ff..5e2306f8d 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -11,7 +11,7 @@ using Server.Network; namespace Server.Accounting { - [Serializable(3)] + [Serializable(4)] public partial class Account : IAccount, IComparable, ISerializable { public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0); @@ -33,10 +33,7 @@ namespace Server.Accounting [SerializableField(4)] private int _flags; - [SerializableField(5, setter: "private")] - private DateTime _created; - - [SerializableField(6)] + [SerializableField(5)] private DateTime _lastLogin; /// @@ -44,7 +41,7 @@ namespace Server.Accounting /// The value does not include the value of Platinum and ranges from /// 0 to 999,999,999 by default. /// - [SerializableField(7, setter: "private")] + [SerializableField(6, setter: "private")] [SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")] public int _totalGold; @@ -54,16 +51,16 @@ namespace Server.Accounting /// 0 to 2,147,483,647 by default. /// One Platinum represents the value of CurrencyThreshold in Gold. /// - [SerializableField(8, setter: "private")] + [SerializableField(7, setter: "private")] [SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")] public int _totalPlat; - [SerializableField(9, "private", "private")] + [SerializableField(8, "private", "private")] private Mobile[] _mobiles; private List _comments; - [SerializableField(10)] + [SerializableField(9)] public List Comments { get => _comments ??= new List(); @@ -76,7 +73,7 @@ namespace Server.Accounting private List _tags; - [SerializableField(11)] + [SerializableField(10)] public List Tags { get => _tags ??= new List(); @@ -87,14 +84,14 @@ namespace Server.Accounting } } - [SerializableField(12)] + [SerializableField(11)] private IPAddress[] _loginIPs; /// /// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP addresses /// are allowed. /// - [SerializableField(13)] + [SerializableField(12)] private string[] _ipRestrictions; private TimeSpan _totalGameTime; @@ -103,7 +100,7 @@ namespace Server.Accounting /// Gets the total game time of this account, also considering the game time of characters /// that have been deleted. /// - [SerializableField(14)] + [SerializableField(13)] public TimeSpan TotalGameTime { get @@ -125,7 +122,7 @@ namespace Server.Accounting } } - [SerializableField(15)] + [SerializableField(14)] [SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")] private string _email; @@ -139,7 +136,7 @@ namespace Server.Accounting _accessLevel = AccessLevel.Player; - _created = _lastLogin = Core.Now; + _lastLogin = Core.Now; _totalGameTime = TimeSpan.Zero; _mobiles = new Mobile[7]; @@ -184,7 +181,7 @@ namespace Server.Accounting 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); + 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); @@ -306,10 +303,22 @@ namespace Server.Accounting } } + public TimeSpan AccountAge => Core.Now - Created; + + [CommandProperty(AccessLevel.GameMaster, readOnly: true)] + public DateTime Created { get; set; } = Core.Now; + + [CommandProperty(AccessLevel.GameMaster)] + DateTime ISerializable.LastSerialized { get; set; } = Core.Now; + public int TypeRef { get; private set; } public Serial Serial { get; set; } + public void BeforeSerialize() + { + } + [AfterDeserialization] private void AfterDeserialization() { @@ -348,6 +357,26 @@ namespace Server.Accounting } } + private void MigrateFrom(V3Content content) + { + _username = content.Username; + _passwordAlgorithm = content.PasswordAlgorithm; + _password = content.Password; + _accessLevel = content.AccessLevel; + _flags = content.Flags; + Created = content.Created; + _lastLogin = content.LastLogin; + _totalGold = content.TotalGold; + _totalPlat = content.TotalPlat; + _mobiles = content.Mobiles; + _comments = content.Comments; + _tags = content.Tags; + _loginIPs = content.LoginIPs; + _ipRestrictions = content.IpRestrictions; + _totalGameTime = content.TotalGameTime; + _email = content.Email; + } + private void Deserialize(IGenericReader reader, int version) { if (version != 2) @@ -361,7 +390,7 @@ namespace Server.Accounting _password = reader.ReadString(); _accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum(); _flags = reader.ReadInt(); - _created = reader.ReadDateTime(); + Created = reader.ReadDateTime(); _lastLogin = reader.ReadDateTime(); _totalGold = reader.ReadInt(); diff --git a/Projects/UOContent/Accounting/AccountHandler.cs b/Projects/UOContent/Accounting/AccountHandler.cs index 91a5a4096..a115d993a 100644 --- a/Projects/UOContent/Accounting/AccountHandler.cs +++ b/Projects/UOContent/Accounting/AccountHandler.cs @@ -241,7 +241,7 @@ namespace Server.Misc { res = DeleteResultType.CharBeingPlayed; } - else if (RestrictDeletion && Core.Now < m.CreationTime + DeleteDelay) + else if (RestrictDeletion && Core.Now < m.Created + DeleteDelay) { res = DeleteResultType.CharTooYoung; } diff --git a/Projects/UOContent/Compression/TarArchive.cs b/Projects/UOContent/Compression/TarArchive.cs index 05d2d222b..3bc024a52 100755 --- a/Projects/UOContent/Compression/TarArchive.cs +++ b/Projects/UOContent/Compression/TarArchive.cs @@ -75,9 +75,8 @@ namespace Server.Compression { _pathToTar ??= GetPathToTar(); - var tarFlags = compressCommand == null ? "-axf" : "-xf"; var useExternalCompression = compressCommand != null ? $"--use-compress-program \"{compressCommand}\" " : ""; - var arguments = $"{useExternalCompression}{tarFlags} \"{fileNamePath}\" -C \"{outputDirectory}\""; + var arguments = $"{useExternalCompression} -xf \"{fileNamePath}\" -C \"{outputDirectory}\""; return RunTar(arguments, compressionProgramPath) == 0; } diff --git a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs index 014f38688..eeb92baa8 100644 --- a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs +++ b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatue.cs @@ -515,8 +515,7 @@ namespace Server.Mobiles { if (from.Account is Account acct && from.AccessLevel == AccessLevel.Player) { - var time = TimeSpan.FromDays(RewardSystem.RewardInterval.TotalDays * 6) - - (Core.Now - acct.Created); + var time = TimeSpan.FromDays(RewardSystem.RewardInterval.TotalDays * 6) - acct.AccountAge; if (time > TimeSpan.Zero) { diff --git a/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs b/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs index 869b1b3fc..504de1247 100644 --- a/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs +++ b/Projects/UOContent/Engines/Veteran Rewards/RewardSystem.cs @@ -59,15 +59,8 @@ namespace Server.Engines.VeteranRewards return false; } - public static bool HasAccess(Mobile mob, RewardEntry entry) - { - if (Core.Expansion < entry.RequiredExpansion) - { - return false; - } - - return HasAccess(mob, entry.List, out var _); - } + public static bool HasAccess(Mobile mob, RewardEntry entry) => + Core.Expansion >= entry.RequiredExpansion && HasAccess(mob, entry.List, out var _); public static bool HasAccess(Mobile mob, RewardList list, out TimeSpan ts) { @@ -77,13 +70,13 @@ namespace Server.Engines.VeteranRewards return false; } - if (!(mob.Account is Account acct)) + if (mob.Account is not Account acct) { ts = TimeSpan.Zero; return false; } - ts = list.Age - (Core.Now - acct.Created); + ts = list.Age - acct.AccountAge; return ts <= TimeSpan.Zero; } @@ -98,12 +91,8 @@ namespace Server.Engines.VeteranRewards return GetRewardLevel(acct); } - public static int GetRewardLevel(Account acct) - { - var totalTime = Core.Now - acct.Created; - - return Math.Max((int)(totalTime.TotalDays / RewardInterval.TotalDays), 0); - } + public static int GetRewardLevel(Account acct) => + Math.Max((int)(acct.AccountAge.TotalDays / RewardInterval.TotalDays), 0); public static bool HasHalfLevel(Mobile mob) { @@ -115,14 +104,7 @@ namespace Server.Engines.VeteranRewards return HasHalfLevel(acct); } - public static bool HasHalfLevel(Account acct) - { - var totalTime = Core.Now - acct.Created; - - var level = totalTime.TotalDays / RewardInterval.TotalDays; - - return level >= 0.5; - } + public static bool HasHalfLevel(Account acct) => acct.AccountAge.TotalDays / RewardInterval.TotalDays >= 0.5; public static bool ConsumeRewardPoint(Mobile mob) { diff --git a/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs b/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs index 9b67bffe1..424199d15 100644 --- a/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs +++ b/Projects/UOContent/Items/Skill Items/Camping/Campfire.cs @@ -29,7 +29,6 @@ namespace Server.Items m_Entries = new List(); - Created = Core.Now; Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), OnTick, out _timerToken); } @@ -37,9 +36,6 @@ namespace Server.Items { } - [CommandProperty(AccessLevel.GameMaster)] - public DateTime Created { get; } - [CommandProperty(AccessLevel.GameMaster)] public CampfireStatus Status { diff --git a/Projects/UOContent/Migrations/Server.Accounting.Account.v4.json b/Projects/UOContent/Migrations/Server.Accounting.Account.v4.json new file mode 100644 index 000000000..07d1336c7 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Accounting.Account.v4.json @@ -0,0 +1,127 @@ +{ + "version": 4, + "type": "Server.Accounting.Account", + "properties": [ + { + "name": "Username", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "PasswordAlgorithm", + "type": "Server.Accounting.Security.PasswordProtectionAlgorithm", + "rule": "EnumMigrationRule" + }, + { + "name": "Password", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "AccessLevel", + "type": "Server.AccessLevel", + "rule": "EnumMigrationRule" + }, + { + "name": "Flags", + "type": "int", + "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": "Tags", + "type": "System.Collections.Generic.List\u003CServer.Accounting.AccountTag\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "", + "Server.Accounting.AccountTag", + "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" + }, + { + "name": "Email", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Misc/ClientVerification.cs b/Projects/UOContent/Misc/ClientVerification.cs index cf3c92e72..23764174a 100644 --- a/Projects/UOContent/Misc/ClientVerification.cs +++ b/Projects/UOContent/Misc/ClientVerification.cs @@ -87,7 +87,7 @@ namespace Server.Misc if (Required != null && version < Required && (m_OldClientResponse == OldClientResponse.Kick || m_OldClientResponse == OldClientResponse.LenientKick && - Core.Now - state.Mobile.CreationTime > m_AgeLeniency && + Core.Now - state.Mobile.Created > m_AgeLeniency && state.Mobile is PlayerMobile mobile && mobile.GameTime > m_GameTimeLeniency)) { diff --git a/Projects/UOContent/Misc/Guild.cs b/Projects/UOContent/Misc/Guild.cs index 854b7680e..dd891a8b1 100644 --- a/Projects/UOContent/Misc/Guild.cs +++ b/Projects/UOContent/Misc/Guild.cs @@ -1156,6 +1156,10 @@ namespace Server.Guilds list.TrimExcess(); } + public override void BeforeSerialize() + { + } + public override void Serialize(IGenericWriter writer) { if (LastFealty + TimeSpan.FromDays(1.0) < Core.Now) diff --git a/Projects/UOContent/Misc/Profile.cs b/Projects/UOContent/Misc/Profile.cs index 085396208..04ceddea5 100644 --- a/Projects/UOContent/Misc/Profile.cs +++ b/Projects/UOContent/Misc/Profile.cs @@ -69,24 +69,24 @@ namespace Server.Misc return ""; } - var ts = Core.Now - a.Created; + var age = a.AccountAge; - if (Format(ts.TotalDays, "This account is {0} day{1} old.", out var v)) + if (Format(age.TotalDays, "This account is {0} day{1} old.", out var v)) { return v; } - if (Format(ts.TotalHours, "This account is {0} hour{1} old.", out v)) + if (Format(age.TotalHours, "This account is {0} hour{1} old.", out v)) { return v; } - if (Format(ts.TotalMinutes, "This account is {0} minute{1} old.", out v)) + if (Format(age.TotalMinutes, "This account is {0} minute{1} old.", out v)) { return v; } - if (Format(ts.TotalSeconds, "This account is {0} second{1} old.", out v)) + if (Format(age.TotalSeconds, "This account is {0} second{1} old.", out v)) { return v; } diff --git a/Projects/UOContent/Mobiles/Vendors/NPC/Guildmasters/BaseGuildmaster.cs b/Projects/UOContent/Mobiles/Vendors/NPC/Guildmasters/BaseGuildmaster.cs index 73056c170..4a8666ec0 100644 --- a/Projects/UOContent/Mobiles/Vendors/NPC/Guildmasters/BaseGuildmaster.cs +++ b/Projects/UOContent/Mobiles/Vendors/NPC/Guildmasters/BaseGuildmaster.cs @@ -94,7 +94,7 @@ namespace Server.Mobiles { SayTo(pm, 501046); // Thou must resign from thy other guild first. } - else if (pm.GameTime < JoinGameAge || pm.CreationTime + JoinAge > Core.Now) + else if (pm.GameTime < JoinGameAge || pm.Created + JoinAge > Core.Now) { SayTo(pm, 501048); // You are too young to join my guild... } @@ -141,7 +141,7 @@ namespace Server.Mobiles { SayTo(pm, 501046); // Thou must resign from thy other guild first. } - else if (pm.GameTime < JoinGameAge || pm.CreationTime + JoinAge > Core.Now) + else if (pm.GameTime < JoinGameAge || pm.Created + JoinAge > Core.Now) { SayTo(pm, 501048); // You are too young to join my guild... }