From 2935eafe2476d427865b2e9f0b7eb8be5a9907d5 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 22 Aug 2026 19:34:43 -0700 Subject: [PATCH] feat: convert all delta-time serialization to anchored time (#2589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Phase 3 of the anchored-time work: **every actively-written delta-time value in the engine now stores an anchored timestamp** — absolute on the wire, shifted forward by the downtime at load. Remaining time survives restarts (as delta did), and unlike delta, the bytes do not change on every save, so an idle world serializes identically save after save. The answer to "is it possible everywhere": **yes** — including the one case that looked impossible. ## The GenericPersistence problem, solved `GenericPersistence` bins (`Virtues.bin`, `StealableArtifacts.bin`, …) are raw payloads with no idx header, so they have no anchor of their own — anchored reads there would silently apply zero shift. But the anchor is a property of the **save**, not the file: every file in one save shares one `World.SaveStartTime`, and `Persistence.Load` reads **all** entity indexes (phase 1) before **any** persistence payload (phase 2). So the idx v5 header stamps a save-wide `World.LoadTimeShift`, and generic persistence readers inherit it. No file-format change, no per-bin header, old bins unaffected. ## Converted - **Item v10 → v11**: `LastMoved` — previously whole-minute delta, rewritten every save for every item, the single largest source of idle-save churn — and `DecayResetTime` (retiring the TODO from #2583). **Mobile v37 → v38**: the three stat-gain stamps. **BaseCreature v20 → v21**: `SummonEnd`. - **17 code-generated classes** (`[DeltaDateTime]` → `[AnchoredDateTime]`, version bump + `MigrateFrom` each): the five field spells, TransientItem, VirtueContext (×7 fields), PuzzleChestSolutionAndTime, BaseCamp, BaseBoat, RentedVendor, PlayerVendor, Ethics Player, Sheep, StarRoomGate, ChampionSpawn (×3), Corpse (`TimeOfDeath`, v19). The `MigrateFrom` bodies were generated from each class's current migration schema and are compiler-verified; VirtueContext's save-flagged nullables fall back to the same defaults the old deserialize left in place. Corpse's six migrations moved to a new `Corpse.Migrations.cs`. - **Hand-written sites**: StealableArtifacts (v2), VendorInventory (v1), ML quest objectives (persistence v3) — each gated on its own version. **Not converted, deliberately**: the ~25 read-only `ReadDeltaTime` sites in legacy version fallbacks and migration replays — they decode existing old bytes and must never change. `[DeltaDateTime]`/`WriteDeltaTime` remain available for them. ## Verification - Build 0 errors / 0 warnings; **837 + 708 tests green**. - Schema regeneration produced exactly the 17 expected new `vN.json` files (all `AnchoredTime` rule args), nothing else touched. - **New acceptance tests** pin the point of the whole effort: serializing the same item at two save times **5 hours apart produces byte-identical output**, and `LastMoved`/`DecayResetTime` round-trip **exactly** at sub-minute precision (the old minutes encoding destroyed both properties). ## Notes for review - `LastMoved` grows from a 1–3 byte encoded minutes value to 8-byte ticks per item — the price of byte-stability; it repays itself in incremental-save behavior since unchanged items now produce unchanged bytes. - BaseEscortable-style semantics are unchanged: anchored shift preserves *remaining* time exactly, the same contract delta provided, so no gameplay-visible behavior changes — deadlines simply stop being consumed by downtime that delta already protected against, now with stable bytes. ## Enforcement `WriteDeltaTime` is now `[Obsolete]` (interface + implementation). With the repo's warnings-as-errors, any new delta-time write — hand-written or emitted by a still-unconverted `[DeltaDateTime]` field — fails the build, with the migration instructions in the message. That the full solution still builds with **zero warnings** is itself the proof no active delta writer survived the conversion. `ReadDeltaTime` deliberately stays un-attributed: its remaining callers decode existing old bytes and are correct forever; its XML docs now state the legacy-decode-only contract. --- .../AnchoredItemSerializationTests.cs | 76 +++++++ Projects/Server/Items/Item.cs | 29 ++- Projects/Server/Mobiles/Mobile.cs | 24 ++- Projects/Server/Serialization/BufferWriter.cs | 1 + .../Serialization/GenericEntityPersistence.cs | 4 + .../Serialization/GenericPersistence.cs | 8 +- .../Server/Serialization/IGenericReader.cs | 6 + .../Server/Serialization/IGenericWriter.cs | 3 + Projects/Server/World/World.cs | 9 + .../Engines/CannedEvil/ChampionSpawn.cs | 36 +++- .../Engines/CannedEvil/StarRoomGate.cs | 10 +- .../UOContent/Engines/Ethics/Core/Player.cs | 15 +- .../UOContent/Engines/Khaldun/PuzzleChest.cs | 9 +- .../Engines/ML Quests/MLQuestPersistence.cs | 2 +- .../ML Quests/Objectives/BaseObjective.cs | 4 +- .../Engines/Stealables/StealableArtifacts.cs | 6 +- .../Engines/Virtues/VirtueContext.cs | 35 +++- .../Items/Misc/Corpses/Corpse.Migrations.cs | 179 +++++++++++++++++ .../UOContent/Items/Misc/Corpses/Corpse.cs | 150 +------------- ....Engines.CannedEvil.ChampionSpawn.v11.json | 189 ++++++++++++++++++ ...rver.Engines.Virtues.VirtueContext.v1.json | 119 +++++++++++ .../Migrations/Server.Ethics.Player.v2.json | 50 +++++ .../Migrations/Server.Items.Corpse.v19.json | 137 +++++++++++++ ...r.Items.PuzzleChestSolutionAndTime.v1.json | 14 ++ .../Server.Items.StarRoomGate.v2.json | 22 ++ .../Server.Items.TransientItem.v2.json | 14 ++ .../Server.Mobiles.PlayerVendor.v4.json | 62 ++++++ .../Server.Mobiles.RentedVendor.v1.json | 62 ++++++ .../Migrations/Server.Mobiles.Sheep.v1.json | 14 ++ .../Migrations/Server.Multis.BaseBoat.v5.json | 73 +++++++ .../Migrations/Server.Multis.BaseCamp.v2.json | 34 ++++ .../Server.Spells.Fifth.PoisonField.v1.json | 19 ++ ...Server.Spells.Fourth.FireFieldItem.v1.json | 27 +++ .../Server.Spells.Seventh.EnergyField.v2.json | 19 ++ .../Server.Spells.Sixth.ParalyzeField.v1.json | 19 ++ .../Server.Spells.Third.WallOfStone.v1.json | 19 ++ .../UOContent/Mobiles/Animals/Misc/Sheep.cs | 9 +- Projects/UOContent/Mobiles/BaseCreature.cs | 6 +- .../UOContent/Mobiles/Vendors/PlayerVendor.cs | 15 +- .../UOContent/Mobiles/Vendors/RentedVendor.cs | 15 +- .../Mobiles/Vendors/VendorInventory.cs | 6 +- Projects/UOContent/Multis/Boats/BaseBoat.cs | 19 +- Projects/UOContent/Multis/Camps/BaseCamp.cs | 11 +- .../UOContent/Spells/Fifth/PoisonField.cs | 10 +- Projects/UOContent/Spells/Fourth/FireField.cs | 11 +- .../UOContent/Spells/Seventh/EnergyField.cs | 10 +- .../UOContent/Spells/Sixth/ParalyzeField.cs | 10 +- .../Spellweaving/Items/TransientItem.cs | 9 +- .../UOContent/Spells/Third/WallOfStone.cs | 10 +- 49 files changed, 1417 insertions(+), 223 deletions(-) create mode 100644 Projects/Server.Tests/Tests/Serialization/AnchoredItemSerializationTests.cs create mode 100644 Projects/UOContent/Items/Misc/Corpses/Corpse.Migrations.cs create mode 100644 Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v11.json create mode 100644 Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Ethics.Player.v2.json create mode 100644 Projects/UOContent/Migrations/Server.Items.Corpse.v19.json create mode 100644 Projects/UOContent/Migrations/Server.Items.PuzzleChestSolutionAndTime.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Items.StarRoomGate.v2.json create mode 100644 Projects/UOContent/Migrations/Server.Items.TransientItem.v2.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.PlayerVendor.v4.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.RentedVendor.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Mobiles.Sheep.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Multis.BaseBoat.v5.json create mode 100644 Projects/UOContent/Migrations/Server.Multis.BaseCamp.v2.json create mode 100644 Projects/UOContent/Migrations/Server.Spells.Fifth.PoisonField.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Spells.Fourth.FireFieldItem.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Spells.Seventh.EnergyField.v2.json create mode 100644 Projects/UOContent/Migrations/Server.Spells.Sixth.ParalyzeField.v1.json create mode 100644 Projects/UOContent/Migrations/Server.Spells.Third.WallOfStone.v1.json diff --git a/Projects/Server.Tests/Tests/Serialization/AnchoredItemSerializationTests.cs b/Projects/Server.Tests/Tests/Serialization/AnchoredItemSerializationTests.cs new file mode 100644 index 000000000..7889c57c8 --- /dev/null +++ b/Projects/Server.Tests/Tests/Serialization/AnchoredItemSerializationTests.cs @@ -0,0 +1,76 @@ +using System; +using Xunit; + +namespace Server.Tests; + +[Collection("Sequential Server Tests")] +public class AnchoredItemSerializationTests +{ + private static byte[] SerializeItem(Item item) + { + var writer = new BufferWriter(new byte[256], true); + item.Serialize(writer); + return writer.Buffer[..(int)writer.Position]; + } + + /// + /// Item v11 stores LastMoved and DecayResetTime as anchored time: the serialized bytes + /// are a function of item state only, not of when the save runs. Pre-v11 stored + /// minutes-since-moved and delta time, which rewrote the bytes on every save. + /// + [Fact] + public void ItemBytes_AreStable_AcrossSavesAtDifferentTimes() + { + var start = Core._now; + + try + { + var item = new Item(0x1F13); + item.MoveToWorld(new Point3D(120, 100, 0), Map.Felucca); + item.RestartDecay(); + + var first = SerializeItem(item); + + // A save hours later, with no state change, must produce identical bytes. + Core._now = start + TimeSpan.FromHours(5); + var second = SerializeItem(item); + + Assert.Equal(first, second); + + item.Delete(); + } + finally + { + Core._now = start; + } + } + + /// + /// Pre-v11 LastMoved was stored at whole-minute precision relative to the save time and + /// could never round-trip exactly. Anchored storage is absolute and exact. + /// + [Fact] + public void LastMovedAndDecayReset_RoundTripExactly() + { + var item = new Item(0x1F13); + item.MoveToWorld(new Point3D(121, 100, 0), Map.Felucca); + + // Sub-minute precision that the old minutes encoding would have destroyed. + var moved = Core.Now - TimeSpan.FromSeconds(90.5) - TimeSpan.FromMilliseconds(123); + item.LastMoved = moved; + + item.RestartDecay(); + var decayReset = item.DecayResetTime; + Assert.NotEqual(default(DateTime), decayReset); + + var bytes = SerializeItem(item); + + var restored = new Item((Serial)0x7ffff123u); + restored.Deserialize(new BufferReader(bytes)); + + Assert.Equal(moved, restored.LastMoved); + Assert.Equal(decayReset, restored.DecayResetTime); + + item.Delete(); + } +} diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index a2b63a821..13f249f2f 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -863,7 +863,7 @@ public partial class Item : IHued, IComparable, ISpawnable, IObjectPropert public virtual void Serialize(IGenericWriter writer) { - writer.Write(10); // version + writer.Write(11); // version var flags = SaveFlag.None; @@ -1015,19 +1015,13 @@ public partial class Item : IHued, IComparable, ISpawnable, IObjectPropert writer.Write((int)flags); - /* begin last moved time optimization */ - var ticks = LastMoved.Ticks; - var now = Core.Now.Ticks; - - var minutes = new TimeSpan(now - ticks).TotalMinutes; - - writer.WriteEncodedInt((int)Math.Clamp(minutes, int.MinValue, int.MaxValue)); - /* end */ + // Anchored: shifted by downtime at load, so time-since-moved is preserved and the + // bytes are stable across saves while the item does not move. + writer.WriteAnchoredTime(LastMoved); if (GetSaveFlag(flags, SaveFlag.DecayReset)) { - //TODO Use WriteAnchoredTime once the save-time anchor is ported - writer.WriteDeltaTime(info.m_DecayReset); + writer.WriteAnchoredTime(info.m_DecayReset); } if (GetSaveFlag(flags, SaveFlag.Direction)) @@ -2772,6 +2766,7 @@ public partial class Item : IHued, IComparable, ISpawnable, IObjectPropert switch (version) { + case 11: case 10: case 9: case 8: @@ -2780,7 +2775,11 @@ public partial class Item : IHued, IComparable, ISpawnable, IObjectPropert { var flags = (SaveFlag)reader.ReadInt(); - if (version < 7) + if (version >= 11) + { + LastMoved = reader.ReadAnchoredTime(); + } + else if (version < 7) { LastMoved = reader.ReadDeltaTime(); } @@ -2800,10 +2799,10 @@ public partial class Item : IHued, IComparable, ISpawnable, IObjectPropert if (version >= 10 && GetSaveFlag(flags, SaveFlag.DecayReset)) { - var reset = reader.ReadDeltaTime(); + var reset = version >= 11 ? reader.ReadAnchoredTime() : reader.ReadDeltaTime(); - // LastMoved is stored at whole-minute precision; keep the stamp only - // while it still extends the deadline. + // Pre-v11 LastMoved was stored at whole-minute precision; keep the + // stamp only while it still extends the deadline. if (reset > LastMoved) { DecayResetTime = reset; diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 0e88449e3..fb028b9d7 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -2324,11 +2324,11 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro public virtual void Serialize(IGenericWriter writer) { - writer.Write(37); // version + writer.Write(38); // version - writer.WriteDeltaTime(LastStrGain); - writer.WriteDeltaTime(LastIntGain); - writer.WriteDeltaTime(LastDexGain); + writer.WriteAnchoredTime(LastStrGain); + writer.WriteAnchoredTime(LastIntGain); + writer.WriteAnchoredTime(LastDexGain); byte hairflag = 0x00; @@ -6150,6 +6150,7 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro switch (version) { + case 38: // Stat-gain stamps moved from delta time to anchored time case 37: // Decomposed hair into inline item id/hue (dropped the VirtualHairInfo object) case 36: // Moved virtues to VirtueSystem case 35: // Moved short term murders to PlayerMurderSystem @@ -6158,9 +6159,18 @@ public partial class Mobile : IHued, IComparable, ISpawnable, IObjectPro case 32: // Removed StuckMenu case 31: { - LastStrGain = reader.ReadDeltaTime(); - LastIntGain = reader.ReadDeltaTime(); - LastDexGain = reader.ReadDeltaTime(); + if (version >= 38) + { + LastStrGain = reader.ReadAnchoredTime(); + LastIntGain = reader.ReadAnchoredTime(); + LastDexGain = reader.ReadAnchoredTime(); + } + else + { + LastStrGain = reader.ReadDeltaTime(); + LastIntGain = reader.ReadDeltaTime(); + LastDexGain = reader.ReadDeltaTime(); + } goto case 30; } diff --git a/Projects/Server/Serialization/BufferWriter.cs b/Projects/Server/Serialization/BufferWriter.cs index 68fd971ba..ab53e2f2f 100644 --- a/Projects/Server/Serialization/BufferWriter.cs +++ b/Projects/Server/Serialization/BufferWriter.cs @@ -384,6 +384,7 @@ public class BufferWriter : IGenericWriter } [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("Delta time rewrites its bytes on every save. Write anchored time instead (WriteAnchoredTime, or [AnchoredDateTime] on generated fields); bump the containing type's version, as the wire format changes. Existing delta payloads remain readable through ReadDeltaTime in old-version fallbacks.")] public void WriteDeltaTime(DateTime value) { if (value == DateTime.MinValue) diff --git a/Projects/Server/Serialization/GenericEntityPersistence.cs b/Projects/Server/Serialization/GenericEntityPersistence.cs index f27b22740..c7323329f 100644 --- a/Projects/Server/Serialization/GenericEntityPersistence.cs +++ b/Projects/Server/Serialization/GenericEntityPersistence.cs @@ -504,6 +504,10 @@ public class GenericEntityPersistence : GenericPersistence, IGenericEntityPer var anchor = new DateTime(dataReader.ReadLong(), DateTimeKind.Utc); var shift = Core.Now - anchor; _anchoredTimeShift = anchor.Ticks > 0 && shift > TimeSpan.Zero ? shift : TimeSpan.Zero; + + // The whole save shares one anchor. Publish it so payloads without their own + // (GenericPersistence bins) can shift too; indexes load before any of them. + World.LoadTimeShift = _anchoredTimeShift; } if (version >= 4) diff --git a/Projects/Server/Serialization/GenericPersistence.cs b/Projects/Server/Serialization/GenericPersistence.cs index 5b8e79c29..2268a86c6 100644 --- a/Projects/Server/Serialization/GenericPersistence.cs +++ b/Projects/Server/Serialization/GenericPersistence.cs @@ -98,7 +98,13 @@ public abstract class GenericPersistence : Persistence, IGenericSerializable byte* ptr = null; accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); - var dataReader = new UnmanagedDataReader(ptr, accessor.Length, typesDb); + var dataReader = new UnmanagedDataReader(ptr, accessor.Length, typesDb) + { + // These payloads carry no anchor of their own; they inherit the save-wide + // shift stamped while the entity indexes were read (indexes always load + // before persistence payloads — see Persistence.Load). + AnchoredTimeShift = World.LoadTimeShift + }; Deserialize(dataReader); error = dataReader.Position != fileLength diff --git a/Projects/Server/Serialization/IGenericReader.cs b/Projects/Server/Serialization/IGenericReader.cs index 4821a708b..bfa302b39 100644 --- a/Projects/Server/Serialization/IGenericReader.cs +++ b/Projects/Server/Serialization/IGenericReader.cs @@ -43,6 +43,12 @@ public interface IGenericReader DateTime ReadDateTime() => new(ReadLong(), DateTimeKind.Utc); TimeSpan ReadTimeSpan() => new(ReadLong()); + /// + /// Decodes a legacy delta-time value. Only for reading old-version payloads (version + /// fallbacks and migration replays) — current formats store anchored time and read it + /// with . is + /// obsolete: no current-version format may write delta time. + /// DateTime ReadDeltaTime() { return ReadLong() switch diff --git a/Projects/Server/Serialization/IGenericWriter.cs b/Projects/Server/Serialization/IGenericWriter.cs index 22579ab1a..4162655de 100644 --- a/Projects/Server/Serialization/IGenericWriter.cs +++ b/Projects/Server/Serialization/IGenericWriter.cs @@ -40,7 +40,10 @@ public interface IGenericWriter void Write(decimal value); void WriteEncodedInt(int value); void Write(DateTime value); + + [Obsolete("Delta time rewrites its bytes on every save. Write anchored time instead (WriteAnchoredTime, or [AnchoredDateTime] on generated fields); bump the containing type's version, as the wire format changes. Existing delta payloads remain readable through ReadDeltaTime in old-version fallbacks.")] void WriteDeltaTime(DateTime value); + void WriteAnchoredTime(DateTime value); void Write(IPAddress value); void Write(TimeSpan value); diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs index 4111ef0d1..c00fc85f6 100644 --- a/Projects/Server/World/World.cs +++ b/Projects/Server/World/World.cs @@ -99,6 +99,15 @@ public static class World /// anchored timestamps can be re-based by the downtime at load. /// public static DateTime SaveStartTime { get; internal set; } + + /// + /// The anchored-time shift for the save currently being loaded: the downtime between the + /// save's start and this load. Stamped while entity indexes are read (they all carry the + /// same anchor, since the whole save shares one ) and applied + /// to every reader of that save's files — including + /// payloads, which carry no anchor of their own. Zero for saves that predate the anchor. + /// + public static TimeSpan LoadTimeShift { get; internal set; } public static bool Running => WorldState is not WorldState.Loading and not WorldState.Initial; public static bool Loading => WorldState == WorldState.Loading; diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs index d1e14c834..9daea0ba3 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs @@ -27,16 +27,44 @@ using Server.Logging; namespace Server.Engines.CannedEvil; -[SerializationGenerator(10, false)] +[SerializationGenerator(11, false)] public partial class ChampionSpawn : Item { + private void MigrateFrom(V10Content content) + { + _level = content.Level; + _activatedByProximity = content.ActivatedByProximity; + _nextProximityTime = content.NextProximityTime; + _maxLevel = content.MaxLevel; + _activatedByValor = content.ActivatedByValor; + _damageEntries = content.DamageEntries; + _confinedRoaming = content.ConfinedRoaming; + _idol = content.Idol; + _hasBeenAdvanced = content.HasBeenAdvanced; + _spawnArea = content.SpawnArea; + _randomizeType = content.RandomizeType; + _kills = content.Kills; + _active = content.Active; + _type = content.Type; + _creatures = content.Creatures; + _redSkulls = content.RedSkulls; + _whiteSkulls = content.WhiteSkulls; + _platform = content.Platform; + _altar = content.Altar; + _expireDelay = content.ExpireDelay; + _expireTime = content.ExpireTime; + _champion = content.Champion; + _restartDelay = content.RestartDelay; + _restartTime = content.RestartTime; + } + private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionSpawn)); [SerializableField(1)] [SerializedCommandProperty(AccessLevel.GameMaster)] private bool _activatedByProximity; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _nextProximityTime; @@ -96,7 +124,7 @@ public partial class ChampionSpawn : Item [SerializedCommandProperty(AccessLevel.GameMaster)] private TimeSpan _expireDelay; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(20)] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _expireTime; @@ -109,7 +137,7 @@ public partial class ChampionSpawn : Item [SerializedCommandProperty(AccessLevel.GameMaster)] private TimeSpan _restartDelay; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(23, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _restartTime; diff --git a/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs b/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs index a5426e7a5..88acd587e 100644 --- a/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs +++ b/Projects/UOContent/Engines/CannedEvil/StarRoomGate.cs @@ -3,15 +3,21 @@ using ModernUO.Serialization; namespace Server.Items; -[SerializationGenerator(1, false)] +[SerializationGenerator(2, false)] public partial class StarRoomGate : Moongate { + private void MigrateFrom(V1Content content) + { + _decays = content.Decays; + _decayTime = content.DecayTime; + } + private static TimeSpan GateDuration = TimeSpan.FromMinutes(2.0); [SerializableField(0)] private bool _decays; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(1)] private DateTime _decayTime; diff --git a/Projects/UOContent/Engines/Ethics/Core/Player.cs b/Projects/UOContent/Engines/Ethics/Core/Player.cs index f21fe9eb9..d8aad9eb3 100644 --- a/Projects/UOContent/Engines/Ethics/Core/Player.cs +++ b/Projects/UOContent/Engines/Ethics/Core/Player.cs @@ -5,9 +5,20 @@ using Server.Mobiles; namespace Server.Ethics; [PropertyObject] -[SerializationGenerator(1)] +[SerializationGenerator(2)] public partial class Player : EthicsEntity { + private void MigrateFrom(V1Content content) + { + _mobile = content.Mobile; + _power = content.Power; + _history = content.History; + _steed = content.Steed; + _familiar = content.Familiar; + _shield = content.Shield; + _ethic = content.Ethic; + } + [SerializableField(0, setter: "private")] private Mobile _mobile; @@ -27,7 +38,7 @@ public partial class Player : EthicsEntity [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] private Mobile _familiar; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(5, setter: "private")] private DateTime _shield; diff --git a/Projects/UOContent/Engines/Khaldun/PuzzleChest.cs b/Projects/UOContent/Engines/Khaldun/PuzzleChest.cs index fbb3af7b6..53932c8f0 100644 --- a/Projects/UOContent/Engines/Khaldun/PuzzleChest.cs +++ b/Projects/UOContent/Engines/Khaldun/PuzzleChest.cs @@ -162,10 +162,15 @@ namespace Server.Items } } - [SerializationGenerator(0)] + [SerializationGenerator(1)] public partial class PuzzleChestSolutionAndTime : PuzzleChestSolution { - [DeltaDateTime] + private void MigrateFrom(V0Content content) + { + _when = content.When; + } + + [AnchoredDateTime] [SerializableField(0)] private DateTime _when; diff --git a/Projects/UOContent/Engines/ML Quests/MLQuestPersistence.cs b/Projects/UOContent/Engines/ML Quests/MLQuestPersistence.cs index 5d846d595..d3e39c09f 100644 --- a/Projects/UOContent/Engines/ML Quests/MLQuestPersistence.cs +++ b/Projects/UOContent/Engines/ML Quests/MLQuestPersistence.cs @@ -19,7 +19,7 @@ namespace Server.Engines.MLQuests { base.Serialize(writer); - writer.Write(2); // version + writer.Write(3); // version writer.Write(MLQuestSystem.Contexts.Count); foreach (var context in MLQuestSystem.Contexts.Values) diff --git a/Projects/UOContent/Engines/ML Quests/Objectives/BaseObjective.cs b/Projects/UOContent/Engines/ML Quests/Objectives/BaseObjective.cs index 2838c7736..77c3350f5 100644 --- a/Projects/UOContent/Engines/ML Quests/Objectives/BaseObjective.cs +++ b/Projects/UOContent/Engines/ML Quests/Objectives/BaseObjective.cs @@ -119,7 +119,7 @@ namespace Server.Engines.MLQuests.Objectives if (IsTimed) { writer.Write(true); - writer.WriteDeltaTime(EndTime); + writer.WriteAnchoredTime(EndTime); } else { @@ -135,7 +135,7 @@ namespace Server.Engines.MLQuests.Objectives { if (reader.ReadBool()) { - var endTime = reader.ReadDeltaTime(); + var endTime = version >= 3 ? reader.ReadAnchoredTime() : reader.ReadDeltaTime(); if (objInstance != null) { diff --git a/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs b/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs index 396b87dcd..4cd8d8c63 100644 --- a/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs +++ b/Projects/UOContent/Engines/Stealables/StealableArtifacts.cs @@ -248,7 +248,7 @@ public class StealableArtifacts : GenericPersistence public override void Serialize(IGenericWriter writer) { - writer.WriteEncodedInt(1); // version + writer.WriteEncodedInt(2); // version writer.Write(_enabled); @@ -261,7 +261,7 @@ public class StealableArtifacts : GenericPersistence var si = _artifacts[i]; writer.Write(si.Item); - writer.WriteDeltaTime(si.NextRespawn); + writer.WriteAnchoredTime(si.NextRespawn); } } } @@ -282,7 +282,7 @@ public class StealableArtifacts : GenericPersistence for (var i = 0; i < length; i++) { var item = reader.ReadEntity(); - var nextRespawn = reader.ReadDeltaTime(); + var nextRespawn = version >= 2 ? reader.ReadAnchoredTime() : reader.ReadDeltaTime(); if (i < _artifacts.Length) { diff --git a/Projects/UOContent/Engines/Virtues/VirtueContext.cs b/Projects/UOContent/Engines/Virtues/VirtueContext.cs index 490002f43..7524f77ed 100644 --- a/Projects/UOContent/Engines/Virtues/VirtueContext.cs +++ b/Projects/UOContent/Engines/Virtues/VirtueContext.cs @@ -5,10 +5,29 @@ using Server.Mobiles; namespace Server.Engines.Virtues; [PropertyObject] -[SerializationGenerator(0)] +[SerializationGenerator(1)] public partial class VirtueContext { - [DeltaDateTime] + private void MigrateFrom(V0Content content) + { + // Save-flagged values arrive as nullables; unset flags fall back to the same + // defaults the old deserialize left in place. + _lastSacrificeGain = content.LastSacrificeGain ?? default; + _lastSacrificeLoss = content.LastSacrificeLoss ?? default; + _availableResurrects = content.AvailableResurrects ?? 0; + _lastJusticeLoss = content.LastJusticeLoss ?? default; + _lastCompassionLoss = content.LastCompassionLoss ?? default; + _nextCompassionDay = content.NextCompassionDay ?? default; + _compassionGains = content.CompassionGains ?? 0; + _lastValorLoss = content.LastValorLoss ?? default; + _lastHonorUse = content.LastHonorUse ?? default; + _honorActive = content.HonorActive; + _justiceProtection = content.JusticeProtection; + _justiceStatus = content.JusticeStatus ?? JusticeProtectorStatus.None; + _values = content.Values; + } + + [AnchoredDateTime] [SerializableField(0)] [SaveFlag(nameof(ShouldSerializeLastSacrificeGain))] [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] @@ -16,7 +35,7 @@ public partial class VirtueContext private bool ShouldSerializeLastSacrificeGain() => !SacrificeVirtue.CanGain(this); - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(1)] [SaveFlag(nameof(ShouldSerializeLastSacrificeLoss))] [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] @@ -31,7 +50,7 @@ public partial class VirtueContext private bool ShouldSerializeAvailableResurrects() => _availableResurrects > 0; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(3)] [SaveFlag(nameof(ShouldSerializeLastJusticeLoss))] [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] @@ -39,7 +58,7 @@ public partial class VirtueContext private bool ShouldSerializeLastJusticeLoss() => !JusticeVirtue.CanAtrophy(this); - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(4)] [SaveFlag(nameof(ShouldSerializeLastCompassionLoss))] [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] @@ -47,7 +66,7 @@ public partial class VirtueContext private bool ShouldSerializeLastCompassionLoss() => !CompassionVirtue.CanAtrophy(this); - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(5)] [SaveFlag(nameof(ShouldSerializeNextCompassionDay))] [SerializedCommandProperty(AccessLevel.GameMaster)] @@ -62,7 +81,7 @@ public partial class VirtueContext private bool ShouldSerializeCompassionGains() => _compassionGains > 0; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(7)] [SaveFlag(nameof(ShouldSerializeValorLoss))] [SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] @@ -70,7 +89,7 @@ public partial class VirtueContext private bool ShouldSerializeValorLoss() => !ValorVirtue.CanAtrophy(this); - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(8)] [SaveFlag(nameof(ShouldSerializeLastHonorUse))] [SerializedCommandProperty(AccessLevel.GameMaster)] diff --git a/Projects/UOContent/Items/Misc/Corpses/Corpse.Migrations.cs b/Projects/UOContent/Items/Misc/Corpses/Corpse.Migrations.cs new file mode 100644 index 000000000..bc9ce047e --- /dev/null +++ b/Projects/UOContent/Items/Misc/Corpses/Corpse.Migrations.cs @@ -0,0 +1,179 @@ +using System; + +namespace Server.Items; + +public partial class Corpse +{ + // Decay timer and TimeOfDeath moved from delta time to anchored time + private void MigrateFrom(V18Content content) + { + _restoreEquip = content.RestoreEquip; + _flags = content.Flags; + _timeOfDeath = content.TimeOfDeath; + _restoreTable = content.RestoreTable; + _looters = content.Looters; + _killer = content.Killer; + _aggressors = content.Aggressors; + _owner = content.Owner; + _corpseName = content.CorpseName; + _accessLevel = content.AccessLevel; + _guild = content.Guild; + _equipItems = content.EquipItems; + _hairItemId = content.HairItemId; + _hairHue = content.HairHue; + _facialHairItemId = content.FacialHairItemId; + _facialHairHue = content.FacialHairHue; + + if (content.DecayTimerDelay != TimeSpan.MinValue) + { + DeserializeDecayTimer(content.DecayTimerDelay); + } + } + + // Decay timer moved from [TimerDrift]/[DeserializeTimerField] to [DeserializeTimer] + private void MigrateFrom(V17Content content) + { + _restoreEquip = content.RestoreEquip; + _flags = content.Flags; + _timeOfDeath = content.TimeOfDeath; + _restoreTable = content.RestoreTable; + _looters = content.Looters; + _killer = content.Killer; + _aggressors = content.Aggressors; + _owner = content.Owner; + _corpseName = content.CorpseName; + _accessLevel = content.AccessLevel; + _guild = content.Guild; + _equipItems = content.EquipItems; + _hairItemId = content.HairItemId; + _hairHue = content.HairHue; + _facialHairItemId = content.FacialHairItemId; + _facialHairHue = content.FacialHairHue; + + if (content.DecayTimerDelay != TimeSpan.MinValue) + { + DeserializeDecayTimer(content.DecayTimerDelay); + } + } + + // Decomposed VirtualHairInfo into discrete int fields (hair/facial hair item id + hue) + private void MigrateFrom(V16Content content) + { + _restoreEquip = content.RestoreEquip; + _flags = content.Flags; + _timeOfDeath = content.TimeOfDeath; + _restoreTable = content.RestoreTable; + _decayTimer = new InternalTimer(this, content.DecayTimerDelay); + _decayTimer.Start(); + _looters = content.Looters; + _killer = content.Killer; + _aggressors = content.Aggressors; + _owner = content.Owner; + _corpseName = content.CorpseName; + _accessLevel = content.AccessLevel; + _guild = content.Guild; + _equipItems = content.EquipItems; + if (content.Hair != null) + { + _hairItemId = content.Hair.ItemId; + _hairHue = content.Hair.Hue; + } + + if (content.FacialHair != null) + { + _facialHairItemId = content.FacialHair.ItemId; + _facialHairHue = content.FacialHair.Hue; + } + } + + // Folded Murderer bool field into CorpseFlag.Murderer + private void MigrateFrom(V15Content content) + { + _restoreEquip = content.RestoreEquip; + _flags = content.Flags; + if (content.Murderer) + { + _flags |= CorpseFlag.Murderer; + } + _timeOfDeath = content.TimeOfDeath; + _restoreTable = content.RestoreTable; + _decayTimer = new InternalTimer(this, content.DecayTimerDelay); + _decayTimer.Start(); + _looters = content.Looters; + _killer = content.Killer; + _aggressors = content.Aggressors; + _owner = content.Owner; + _corpseName = content.CorpseName; + _accessLevel = content.AccessLevel; + _guild = content.Guild; + _equipItems = content.EquipItems; + if (content.Hair != null) + { + _hairItemId = content.Hair.ItemId; + _hairHue = content.Hair.Hue; + } + + if (content.FacialHair != null) + { + _facialHairItemId = content.FacialHair.ItemId; + _facialHairHue = content.FacialHair.Hue; + } + } + + // Replaced int Kills snapshot with bool Murderer snapshot + private void MigrateFrom(V14Content content) + { + _restoreEquip = content.RestoreEquip; + _flags = content.Flags; + if (content.Kills >= 5) + { + _flags |= CorpseFlag.Murderer; + } + _timeOfDeath = content.TimeOfDeath; + _restoreTable = content.RestoreTable; + _decayTimer = new InternalTimer(this, content.DecayTimerDelay); + _decayTimer.Start(); + _looters = content.Looters; + _killer = content.Killer; + _aggressors = content.Aggressors; + _owner = content.Owner; + _corpseName = content.CorpseName; + _accessLevel = content.AccessLevel; + _guild = content.Guild; + _equipItems = content.EquipItems; + if (content.Hair != null) + { + _hairItemId = content.Hair.ItemId; + _hairHue = content.Hair.Hue; + } + + if (content.FacialHair != null) + { + _facialHairItemId = content.FacialHair.ItemId; + _facialHairHue = content.FacialHair.Hue; + } + } + + // Added corpse hair and corpse facial hair + private void MigrateFrom(V13Content content) + { + _restoreEquip = content.RestoreEquip; + _flags = content.Flags; + if (content.Kills >= 5) + { + _flags |= CorpseFlag.Murderer; + } + _timeOfDeath = content.TimeOfDeath; + _restoreTable = content.RestoreTable; + _decayTimer = new InternalTimer(this, content.DecayTimerDelay); + _decayTimer.Start(); + _looters = content.Looters; + _killer = content.Killer; + _aggressors = content.Aggressors; + _owner = content.Owner; + _corpseName = content.CorpseName; + _accessLevel = content.AccessLevel; + _guild = content.Guild; + _equipItems = content.EquipItems; + } +} diff --git a/Projects/UOContent/Items/Misc/Corpses/Corpse.cs b/Projects/UOContent/Items/Misc/Corpses/Corpse.cs index 59db3dc98..04e9806c9 100644 --- a/Projects/UOContent/Items/Misc/Corpses/Corpse.cs +++ b/Projects/UOContent/Items/Misc/Corpses/Corpse.cs @@ -86,7 +86,7 @@ public enum CorpseFlag OwnerWasAnimatedDead = 0x00000800 } -[SerializationGenerator(18, false)] +[SerializationGenerator(19, false)] public partial class Corpse : Container, ICarvable { public static readonly TimeSpan MonsterLootRightSacrifice = TimeSpan.FromMinutes(2.0); @@ -106,7 +106,7 @@ public partial class Corpse : Container, ICarvable [SerializableField(1)] private CorpseFlag _flags; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(2)] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _timeOfDeath; @@ -120,31 +120,6 @@ public partial class Corpse : Container, ICarvable private void DeserializeDecayTimer(TimeSpan delay) => BeginDecay(delay); - private void MigrateFrom(V17Content content) - { - _restoreEquip = content.RestoreEquip; - _flags = content.Flags; - _timeOfDeath = content.TimeOfDeath; - _restoreTable = content.RestoreTable; - _looters = content.Looters; - _killer = content.Killer; - _aggressors = content.Aggressors; - _owner = content.Owner; - _corpseName = content.CorpseName; - _accessLevel = content.AccessLevel; - _guild = content.Guild; - _equipItems = content.EquipItems; - _hairItemId = content.HairItemId; - _hairHue = content.HairHue; - _facialHairItemId = content.FacialHairItemId; - _facialHairHue = content.FacialHairHue; - - if (content.DecayTimerDelay != TimeSpan.MinValue) - { - DeserializeDecayTimer(content.DecayTimerDelay); - } - } - [SerializableField(5, setter: "private")] private HashSet _looters; @@ -342,127 +317,6 @@ public partial class Corpse : Container, ICarvable DevourCorpse(); } - // Decomposed VirtualHairInfo into discrete int fields (hair/facial hair item id + hue) - private void MigrateFrom(V16Content content) - { - _restoreEquip = content.RestoreEquip; - _flags = content.Flags; - _timeOfDeath = content.TimeOfDeath; - _restoreTable = content.RestoreTable; - _decayTimer = new InternalTimer(this, content.DecayTimerDelay); - _decayTimer.Start(); - _looters = content.Looters; - _killer = content.Killer; - _aggressors = content.Aggressors; - _owner = content.Owner; - _corpseName = content.CorpseName; - _accessLevel = content.AccessLevel; - _guild = content.Guild; - _equipItems = content.EquipItems; - if (content.Hair != null) - { - _hairItemId = content.Hair.ItemId; - _hairHue = content.Hair.Hue; - } - - if (content.FacialHair != null) - { - _facialHairItemId = content.FacialHair.ItemId; - _facialHairHue = content.FacialHair.Hue; - } - } - - // Folded Murderer bool field into CorpseFlag.Murderer - private void MigrateFrom(V15Content content) - { - _restoreEquip = content.RestoreEquip; - _flags = content.Flags; - if (content.Murderer) - { - _flags |= CorpseFlag.Murderer; - } - _timeOfDeath = content.TimeOfDeath; - _restoreTable = content.RestoreTable; - _decayTimer = new InternalTimer(this, content.DecayTimerDelay); - _decayTimer.Start(); - _looters = content.Looters; - _killer = content.Killer; - _aggressors = content.Aggressors; - _owner = content.Owner; - _corpseName = content.CorpseName; - _accessLevel = content.AccessLevel; - _guild = content.Guild; - _equipItems = content.EquipItems; - if (content.Hair != null) - { - _hairItemId = content.Hair.ItemId; - _hairHue = content.Hair.Hue; - } - - if (content.FacialHair != null) - { - _facialHairItemId = content.FacialHair.ItemId; - _facialHairHue = content.FacialHair.Hue; - } - } - - // Replaced int Kills snapshot with bool Murderer snapshot - private void MigrateFrom(V14Content content) - { - _restoreEquip = content.RestoreEquip; - _flags = content.Flags; - if (content.Kills >= 5) - { - _flags |= CorpseFlag.Murderer; - } - _timeOfDeath = content.TimeOfDeath; - _restoreTable = content.RestoreTable; - _decayTimer = new InternalTimer(this, content.DecayTimerDelay); - _decayTimer.Start(); - _looters = content.Looters; - _killer = content.Killer; - _aggressors = content.Aggressors; - _owner = content.Owner; - _corpseName = content.CorpseName; - _accessLevel = content.AccessLevel; - _guild = content.Guild; - _equipItems = content.EquipItems; - if (content.Hair != null) - { - _hairItemId = content.Hair.ItemId; - _hairHue = content.Hair.Hue; - } - - if (content.FacialHair != null) - { - _facialHairItemId = content.FacialHair.ItemId; - _facialHairHue = content.FacialHair.Hue; - } - } - - // Added corpse hair and corpse facial hair - private void MigrateFrom(V13Content content) - { - _restoreEquip = content.RestoreEquip; - _flags = content.Flags; - if (content.Kills >= 5) - { - _flags |= CorpseFlag.Murderer; - } - _timeOfDeath = content.TimeOfDeath; - _restoreTable = content.RestoreTable; - _decayTimer = new InternalTimer(this, content.DecayTimerDelay); - _decayTimer.Start(); - _looters = content.Looters; - _killer = content.Killer; - _aggressors = content.Aggressors; - _owner = content.Owner; - _corpseName = content.CorpseName; - _accessLevel = content.AccessLevel; - _guild = content.Guild; - _equipItems = content.EquipItems; - } - [CommandProperty(AccessLevel.GameMaster)] public virtual bool InstancedCorpse => Core.SE && Core.Now < TimeOfDeath + InstancedCorpseTime; diff --git a/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v11.json b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v11.json new file mode 100644 index 000000000..defcbe100 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.CannedEvil.ChampionSpawn.v11.json @@ -0,0 +1,189 @@ +{ + "version": 11, + "type": "Server.Engines.CannedEvil.ChampionSpawn", + "properties": [ + { + "name": "Level", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "ActivatedByProximity", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "NextProximityTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "MaxLevel", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "ActivatedByValor", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "DamageEntries", + "type": "System.Collections.Generic.Dictionary\u003CServer.Mobile, int\u003E", + "rule": "DictionaryMigrationRule", + "ruleArguments": [ + "Server.Mobile", + "SerializableInterfaceMigrationRule", + "0", + "int", + "PrimitiveTypeMigrationRule", + "1", + "" + ] + }, + { + "name": "ConfinedRoaming", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Idol", + "type": "Server.Engines.CannedEvil.IdolOfTheChampion", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "HasBeenAdvanced", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SpawnArea", + "type": "Server.Rectangle2D", + "rule": "PrimitiveUOTypeMigrationRule", + "ruleArguments": [ + "Rect2D" + ] + }, + { + "name": "RandomizeType", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Kills", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Active", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Type", + "type": "Server.Engines.CannedEvil.ChampionSpawnType", + "rule": "EnumMigrationRule" + }, + { + "name": "Creatures", + "type": "System.Collections.Generic.List\u003CServer.Mobile\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "@Tidy", + "Server.Mobile", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "RedSkulls", + "type": "System.Collections.Generic.List\u003CServer.Item\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "@Tidy", + "Server.Item", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "WhiteSkulls", + "type": "System.Collections.Generic.List\u003CServer.Item\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "@Tidy", + "Server.Item", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "Platform", + "type": "Server.Engines.CannedEvil.ChampionPlatform", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Altar", + "type": "Server.Engines.CannedEvil.ChampionAltar", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "ExpireDelay", + "type": "System.TimeSpan", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "ExpireTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "Champion", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "RestartDelay", + "type": "System.TimeSpan", + "rule": "PrimitiveTypeMigrationRule" + }, + { + "name": "RestartTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v1.json b/Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v1.json new file mode 100644 index 000000000..956de91db --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Engines.Virtues.VirtueContext.v1.json @@ -0,0 +1,119 @@ +{ + "version": 1, + "type": "Server.Engines.Virtues.VirtueContext", + "properties": [ + { + "name": "LastSacrificeGain", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "LastSacrificeLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "AvailableResurrects", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "LastJusticeLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "LastCompassionLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "NextCompassionDay", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "CompassionGains", + "type": "int", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "LastValorLoss", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "LastHonorUse", + "type": "System.DateTime", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "HonorActive", + "type": "bool", + "usesSaveFlag": true, + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "JusticeProtection", + "type": "Server.Mobiles.PlayerMobile", + "usesSaveFlag": true, + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "JusticeStatus", + "type": "Server.Engines.Virtues.JusticeProtectorStatus", + "usesSaveFlag": true, + "rule": "EnumMigrationRule" + }, + { + "name": "Values", + "type": "int[]", + "usesSaveFlag": true, + "rule": "ArrayMigrationRule", + "ruleArguments": [ + "int", + "PrimitiveTypeMigrationRule", + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Ethics.Player.v2.json b/Projects/UOContent/Migrations/Server.Ethics.Player.v2.json new file mode 100644 index 000000000..04f4a0199 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Ethics.Player.v2.json @@ -0,0 +1,50 @@ +{ + "version": 2, + "type": "Server.Ethics.Player", + "properties": [ + { + "name": "Mobile", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Power", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "History", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Steed", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Familiar", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Shield", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "Ethic", + "type": "Server.Ethics.Ethic", + "rule": "SerializableInterfaceMigrationRule" + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.Corpse.v19.json b/Projects/UOContent/Migrations/Server.Items.Corpse.v19.json new file mode 100644 index 000000000..2cccb267a --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.Corpse.v19.json @@ -0,0 +1,137 @@ +{ + "version": 19, + "type": "Server.Items.Corpse", + "properties": [ + { + "name": "RestoreEquip", + "type": "System.Collections.Generic.List\u003CServer.Item\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "Server.Item", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "Flags", + "type": "Server.Items.CorpseFlag", + "rule": "EnumMigrationRule" + }, + { + "name": "TimeOfDeath", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "RestoreTable", + "type": "System.Collections.Generic.Dictionary\u003CServer.Item, Server.Point3D\u003E", + "rule": "DictionaryMigrationRule", + "ruleArguments": [ + "Server.Item", + "SerializableInterfaceMigrationRule", + "0", + "Server.Point3D", + "PrimitiveUOTypeMigrationRule", + "1", + "Point3D" + ] + }, + { + "name": "DecayTimer", + "type": "Server.Timer", + "rule": "TimerMigrationRule", + "ruleArguments": [ + "@AnchoredTimer" + ] + }, + { + "name": "Looters", + "type": "System.Collections.Generic.HashSet\u003CServer.Mobile\u003E", + "rule": "HashSetMigrationRule", + "ruleArguments": [ + "Server.Mobile", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "Killer", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Aggressors", + "type": "System.Collections.Generic.List\u003CServer.Mobile\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "Server.Mobile", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "Owner", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "CorpseName", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "AccessLevel", + "type": "Server.AccessLevel", + "rule": "EnumMigrationRule" + }, + { + "name": "Guild", + "type": "Server.Guilds.Guild", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "EquipItems", + "type": "System.Collections.Generic.List\u003CServer.Item\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "Server.Item", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "HairItemId", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "HairHue", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "FacialHairItemId", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "FacialHairHue", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.PuzzleChestSolutionAndTime.v1.json b/Projects/UOContent/Migrations/Server.Items.PuzzleChestSolutionAndTime.v1.json new file mode 100644 index 000000000..8424276f6 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.PuzzleChestSolutionAndTime.v1.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "type": "Server.Items.PuzzleChestSolutionAndTime", + "properties": [ + { + "name": "When", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.StarRoomGate.v2.json b/Projects/UOContent/Migrations/Server.Items.StarRoomGate.v2.json new file mode 100644 index 000000000..0a61f7bfe --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.StarRoomGate.v2.json @@ -0,0 +1,22 @@ +{ + "version": 2, + "type": "Server.Items.StarRoomGate", + "properties": [ + { + "name": "Decays", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "DecayTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Items.TransientItem.v2.json b/Projects/UOContent/Migrations/Server.Items.TransientItem.v2.json new file mode 100644 index 000000000..3745c4428 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Items.TransientItem.v2.json @@ -0,0 +1,14 @@ +{ + "version": 2, + "type": "Server.Items.TransientItem", + "properties": [ + { + "name": "Expiration", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendor.v4.json b/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendor.v4.json new file mode 100644 index 000000000..6b9c9eb1c --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.PlayerVendor.v4.json @@ -0,0 +1,62 @@ +{ + "version": 4, + "type": "Server.Mobiles.PlayerVendor", + "properties": [ + { + "name": "ShopName", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "NextPayTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "House", + "type": "Server.Multis.BaseHouse", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Owner", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "BankAccount", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "HoldGold", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "SellItems", + "type": "System.Collections.Generic.Dictionary\u003CServer.Item, Server.Mobiles.VendorItem\u003E", + "rule": "DictionaryMigrationRule", + "ruleArguments": [ + "Server.Item", + "SerializableInterfaceMigrationRule", + "0", + "Server.Mobiles.VendorItem", + "RawSerializableMigrationRule", + "1", + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.RentedVendor.v1.json b/Projects/UOContent/Migrations/Server.Mobiles.RentedVendor.v1.json new file mode 100644 index 000000000..b42fa33eb --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.RentedVendor.v1.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "type": "Server.Mobiles.RentedVendor", + "properties": [ + { + "name": "RentalDurationId", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "RentalPrice", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "LandlordRenew", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "RenterRenew", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "RenewalPrice", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "RentalGold", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "RentalExpireTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Mobiles.Sheep.v1.json b/Projects/UOContent/Migrations/Server.Mobiles.Sheep.v1.json new file mode 100644 index 000000000..bd224f77f --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Mobiles.Sheep.v1.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "type": "Server.Mobiles.Sheep", + "properties": [ + { + "name": "NextWoolTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Multis.BaseBoat.v5.json b/Projects/UOContent/Migrations/Server.Multis.BaseBoat.v5.json new file mode 100644 index 000000000..140d3dd64 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Multis.BaseBoat.v5.json @@ -0,0 +1,73 @@ +{ + "version": 5, + "type": "Server.Multis.BaseBoat", + "properties": [ + { + "name": "MapItem", + "type": "Server.Items.MapItem", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "NextNavPoint", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Facing", + "type": "Server.Direction", + "rule": "EnumMigrationRule" + }, + { + "name": "TimeOfDecay", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + }, + { + "name": "Owner", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "PPlank", + "type": "Server.Items.Plank", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "SPlank", + "type": "Server.Items.Plank", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "TillerMan", + "type": "Server.Items.TillerMan", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Hold", + "type": "Server.Items.Hold", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "Anchored", + "type": "bool", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "ShipName", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Multis.BaseCamp.v2.json b/Projects/UOContent/Migrations/Server.Multis.BaseCamp.v2.json new file mode 100644 index 000000000..471c645f1 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Multis.BaseCamp.v2.json @@ -0,0 +1,34 @@ +{ + "version": 2, + "type": "Server.Multis.BaseCamp", + "properties": [ + { + "name": "Items", + "type": "System.Collections.Generic.List\u003CServer.Item\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "@Tidy", + "Server.Item", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "Mobiles", + "type": "System.Collections.Generic.List\u003CServer.Mobile\u003E", + "rule": "ListMigrationRule", + "ruleArguments": [ + "@Tidy", + "Server.Mobile", + "SerializableInterfaceMigrationRule" + ] + }, + { + "name": "DecayTime", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Fifth.PoisonField.v1.json b/Projects/UOContent/Migrations/Server.Spells.Fifth.PoisonField.v1.json new file mode 100644 index 000000000..1f16a4d7f --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Fifth.PoisonField.v1.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "type": "Server.Spells.Fifth.PoisonField", + "properties": [ + { + "name": "Caster", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "End", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Fourth.FireFieldItem.v1.json b/Projects/UOContent/Migrations/Server.Spells.Fourth.FireFieldItem.v1.json new file mode 100644 index 000000000..3e91c7a4d --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Fourth.FireFieldItem.v1.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "type": "Server.Spells.Fourth.FireFieldItem", + "properties": [ + { + "name": "Damage", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "" + ] + }, + { + "name": "Caster", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "End", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Seventh.EnergyField.v2.json b/Projects/UOContent/Migrations/Server.Spells.Seventh.EnergyField.v2.json new file mode 100644 index 000000000..dd202da03 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Seventh.EnergyField.v2.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "type": "Server.Spells.Seventh.EnergyField", + "properties": [ + { + "name": "Caster", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "End", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Sixth.ParalyzeField.v1.json b/Projects/UOContent/Migrations/Server.Spells.Sixth.ParalyzeField.v1.json new file mode 100644 index 000000000..3e834a6b1 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Sixth.ParalyzeField.v1.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "type": "Server.Spells.Sixth.ParalyzeField", + "properties": [ + { + "name": "Caster", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "End", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Migrations/Server.Spells.Third.WallOfStone.v1.json b/Projects/UOContent/Migrations/Server.Spells.Third.WallOfStone.v1.json new file mode 100644 index 000000000..eafcc5945 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Spells.Third.WallOfStone.v1.json @@ -0,0 +1,19 @@ +{ + "version": 1, + "type": "Server.Spells.Third.WallOfStone", + "properties": [ + { + "name": "Caster", + "type": "Server.Mobile", + "rule": "SerializableInterfaceMigrationRule" + }, + { + "name": "End", + "type": "System.DateTime", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [ + "AnchoredTime" + ] + } + ] +} \ No newline at end of file diff --git a/Projects/UOContent/Mobiles/Animals/Misc/Sheep.cs b/Projects/UOContent/Mobiles/Animals/Misc/Sheep.cs index 32d0883cc..741093110 100644 --- a/Projects/UOContent/Mobiles/Animals/Misc/Sheep.cs +++ b/Projects/UOContent/Mobiles/Animals/Misc/Sheep.cs @@ -5,9 +5,14 @@ using System.Runtime.CompilerServices; namespace Server.Mobiles { - [SerializationGenerator(0, false)] + [SerializationGenerator(1, false)] public partial class Sheep : BaseCreature, ICarvable { + private void MigrateFrom(V0Content content) + { + _nextWoolTime = content.NextWoolTime; + } + [Constructible] public Sheep() : base(AIType.AI_Animal, FightMode.Aggressor) { @@ -44,7 +49,7 @@ namespace Server.Mobiles public override string CorpseName => "a sheep corpse"; [SerializableField(0, fieldChanged: nameof(OnNextWoolTimeChanged))] - [DeltaDateTime] + [AnchoredDateTime] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _nextWoolTime; diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index af85a195a..6c480ce18 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -1843,7 +1843,7 @@ namespace Server.Mobiles { base.Serialize(writer); - writer.Write(20); // version + writer.Write(21); // version writer.Write((int)m_CurrentAI); writer.Write((int)m_DefaultAI); @@ -1880,7 +1880,7 @@ namespace Server.Mobiles if (_summoned) { - writer.WriteDeltaTime(SummonEnd); + writer.WriteAnchoredTime(SummonEnd); } writer.Write(ControlSlots); @@ -2035,7 +2035,7 @@ namespace Server.Mobiles if (_summoned) { - SummonEnd = reader.ReadDeltaTime(); + SummonEnd = version >= 21 ? reader.ReadAnchoredTime() : reader.ReadDeltaTime(); new UnsummonTimer(this, SummonEnd - Core.Now).Start(); } diff --git a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs index 5b878def2..9b8f5a2f3 100644 --- a/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs +++ b/Projects/UOContent/Mobiles/Vendors/PlayerVendor.cs @@ -22,9 +22,20 @@ public class PlayerVendorTargetAttribute : Attribute; * Next, uncomment the MigrateFrom function and change the `V3Content` type to match the serialization version * before it was bumped. Then run publish.cmd to generate the migration file. */ -[SerializationGenerator(3, false)] +[SerializationGenerator(4, false)] public partial class PlayerVendor : Mobile { + private void MigrateFrom(V3Content content) + { + _shopName = content.ShopName; + _nextPayTime = content.NextPayTime; + _house = content.House; + _owner = content.Owner; + _bankAccount = content.BankAccount; + _holdGold = content.HoldGold; + _sellItems = content.SellItems; + } + private Timer _payTimer; [InvalidateProperties] @@ -32,7 +43,7 @@ public partial class PlayerVendor : Mobile [SerializedCommandProperty(AccessLevel.GameMaster)] private string _shopName; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(1, setter: "private")] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _nextPayTime; diff --git a/Projects/UOContent/Mobiles/Vendors/RentedVendor.cs b/Projects/UOContent/Mobiles/Vendors/RentedVendor.cs index 99d818826..d7d0a3ab6 100644 --- a/Projects/UOContent/Mobiles/Vendors/RentedVendor.cs +++ b/Projects/UOContent/Mobiles/Vendors/RentedVendor.cs @@ -46,9 +46,20 @@ public class VendorRentalDuration } } -[SerializationGenerator(0)] +[SerializationGenerator(1)] public partial class RentedVendor : PlayerVendor { + private void MigrateFrom(V0Content content) + { + _rentalDurationId = content.RentalDurationId; + _rentalPrice = content.RentalPrice; + _landlordRenew = content.LandlordRenew; + _renterRenew = content.RenterRenew; + _renewalPrice = content.RenewalPrice; + _rentalGold = content.RentalGold; + _rentalExpireTime = content.RentalExpireTime; + } + private Timer _rentalExpireTimer; public RentedVendor( @@ -93,7 +104,7 @@ public partial class RentedVendor : PlayerVendor [SerializedCommandProperty(AccessLevel.GameMaster)] private int _rentalGold; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(6)] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _rentalExpireTime; diff --git a/Projects/UOContent/Mobiles/Vendors/VendorInventory.cs b/Projects/UOContent/Mobiles/Vendors/VendorInventory.cs index d4d5ce4bd..8f0a1fba8 100644 --- a/Projects/UOContent/Mobiles/Vendors/VendorInventory.cs +++ b/Projects/UOContent/Mobiles/Vendors/VendorInventory.cs @@ -37,7 +37,7 @@ namespace Server.Mobiles Items = reader.ReadEntityList(); Gold = reader.ReadInt(); - ExpireTime = reader.ReadDeltaTime(); + ExpireTime = version >= 1 ? reader.ReadAnchoredTime() : reader.ReadDeltaTime(); if (Items.Count == 0 && Gold == 0) { @@ -88,7 +88,7 @@ namespace Server.Mobiles public void Serialize(IGenericWriter writer) { - writer.WriteEncodedInt(0); // version + writer.WriteEncodedInt(1); // version writer.Write(Owner); writer.Write(VendorName); @@ -98,7 +98,7 @@ namespace Server.Mobiles writer.Write(Items); writer.Write(Gold); - writer.WriteDeltaTime(ExpireTime); + writer.WriteAnchoredTime(ExpireTime); } private class ExpireTimer : Timer diff --git a/Projects/UOContent/Multis/Boats/BaseBoat.cs b/Projects/UOContent/Multis/Boats/BaseBoat.cs index 9b2d4cc37..ed618b91a 100644 --- a/Projects/UOContent/Multis/Boats/BaseBoat.cs +++ b/Projects/UOContent/Multis/Boats/BaseBoat.cs @@ -19,9 +19,24 @@ namespace Server.Multis Single } - [SerializationGenerator(4, false)] + [SerializationGenerator(5, false)] public abstract partial class BaseBoat : BaseMulti { + private void MigrateFrom(V4Content content) + { + _mapItem = content.MapItem; + _nextNavPoint = content.NextNavPoint; + _facing = content.Facing; + _timeOfDecay = content.TimeOfDecay; + _owner = content.Owner; + _pPlank = content.PPlank; + _sPlank = content.SPlank; + _tillerMan = content.TillerMan; + _hold = content.Hold; + _anchored = content.Anchored; + _shipName = content.ShipName; + } + public enum DryDockResult { Valid, @@ -137,7 +152,7 @@ namespace Server.Multis } [SerializableField(3, fieldChanged: nameof(OnTimeOfDecayChanged))] - [DeltaDateTime] + [AnchoredDateTime] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _timeOfDecay; diff --git a/Projects/UOContent/Multis/Camps/BaseCamp.cs b/Projects/UOContent/Multis/Camps/BaseCamp.cs index e3d10dd0b..e60e3e46e 100644 --- a/Projects/UOContent/Multis/Camps/BaseCamp.cs +++ b/Projects/UOContent/Multis/Camps/BaseCamp.cs @@ -6,9 +6,16 @@ using Server.Mobiles; namespace Server.Multis; -[SerializationGenerator(1, false)] +[SerializationGenerator(2, false)] public abstract partial class BaseCamp : BaseMulti { + private void MigrateFrom(V1Content content) + { + _items = content.Items; + _mobiles = content.Mobiles; + _decayTime = content.DecayTime; + } + [Tidy] [SerializableField(0, setter: "private")] private List _items; @@ -17,7 +24,7 @@ public abstract partial class BaseCamp : BaseMulti [SerializableField(1, setter: "private")] private List _mobiles; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(2, setter: "private")] private DateTime _decayTime; diff --git a/Projects/UOContent/Spells/Fifth/PoisonField.cs b/Projects/UOContent/Spells/Fifth/PoisonField.cs index 3c5d2f609..92a797f19 100644 --- a/Projects/UOContent/Spells/Fifth/PoisonField.cs +++ b/Projects/UOContent/Spells/Fifth/PoisonField.cs @@ -64,13 +64,19 @@ public class PoisonFieldSpell : MagerySpell, ITargetingSpell } [DispellableField] -[SerializationGenerator(0, false)] +[SerializationGenerator(1, false)] public partial class PoisonField : Item { + private void MigrateFrom(V0Content content) + { + _caster = content.Caster; + _end = content.End; + } + [SerializableField(0)] private Mobile _caster; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(1)] private DateTime _end; diff --git a/Projects/UOContent/Spells/Fourth/FireField.cs b/Projects/UOContent/Spells/Fourth/FireField.cs index 0b0a22129..8f20f995b 100644 --- a/Projects/UOContent/Spells/Fourth/FireField.cs +++ b/Projects/UOContent/Spells/Fourth/FireField.cs @@ -67,16 +67,23 @@ public class FireFieldSpell : MagerySpell, ITargetingSpell } [DispellableField] -[SerializationGenerator(0, false)] +[SerializationGenerator(1, false)] public partial class FireFieldItem : Item { + private void MigrateFrom(V0Content content) + { + _damage = content.Damage; + _caster = content.Caster; + _end = content.End; + } + [SerializableField(0)] private int _damage; [SerializableField(1)] private Mobile _caster; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(2)] private DateTime _end; private Timer _timer; diff --git a/Projects/UOContent/Spells/Seventh/EnergyField.cs b/Projects/UOContent/Spells/Seventh/EnergyField.cs index 5e3ea1149..dbfece36d 100644 --- a/Projects/UOContent/Spells/Seventh/EnergyField.cs +++ b/Projects/UOContent/Spells/Seventh/EnergyField.cs @@ -77,13 +77,19 @@ public class EnergyFieldSpell : MagerySpell, ITargetingSpell } [DispellableField] -[SerializationGenerator(1, false)] +[SerializationGenerator(2, false)] public partial class EnergyField : Item { + private void MigrateFrom(V1Content content) + { + _caster = content.Caster; + _end = content.End; + } + [SerializableField(0)] private Mobile _caster; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(1)] private DateTime _end; diff --git a/Projects/UOContent/Spells/Sixth/ParalyzeField.cs b/Projects/UOContent/Spells/Sixth/ParalyzeField.cs index 830f16dfe..301cee507 100644 --- a/Projects/UOContent/Spells/Sixth/ParalyzeField.cs +++ b/Projects/UOContent/Spells/Sixth/ParalyzeField.cs @@ -77,13 +77,19 @@ public class ParalyzeFieldSpell : MagerySpell, ITargetingSpell } [DispellableField] -[SerializationGenerator(0, false)] +[SerializationGenerator(1, false)] public partial class ParalyzeField : Item { + private void MigrateFrom(V0Content content) + { + _caster = content.Caster; + _end = content.End; + } + [SerializableField(0)] private Mobile _caster; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(1)] private DateTime _end; diff --git a/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs b/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs index 54ac52030..49894cc95 100644 --- a/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs +++ b/Projects/UOContent/Spells/Spellweaving/Items/TransientItem.cs @@ -3,12 +3,17 @@ using ModernUO.Serialization; namespace Server.Items; -[SerializationGenerator(1, false)] +[SerializationGenerator(2, false)] public partial class TransientItem : Item { + private void MigrateFrom(V1Content content) + { + _expiration = content.Expiration; + } + private TimerExecutionToken _timerToken; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private DateTime _expiration; diff --git a/Projects/UOContent/Spells/Third/WallOfStone.cs b/Projects/UOContent/Spells/Third/WallOfStone.cs index 7cae3b848..1b1dd04d3 100644 --- a/Projects/UOContent/Spells/Third/WallOfStone.cs +++ b/Projects/UOContent/Spells/Third/WallOfStone.cs @@ -63,13 +63,19 @@ public class WallOfStoneSpell : MagerySpell, ITargetingSpell } [DispellableField] -[SerializationGenerator(0, false)] +[SerializationGenerator(1, false)] public partial class WallOfStone : Item { + private void MigrateFrom(V0Content content) + { + _caster = content.Caster; + _end = content.End; + } + [SerializableField(0)] private Mobile _caster; - [DeltaDateTime] + [AnchoredDateTime] [SerializableField(1)] private DateTime _end;