feat: convert all delta-time serialization to anchored time

Every actively-written delta-time value now stores an absolute anchored
timestamp, shifted by downtime at load: remaining time is preserved across
restarts and idle saves become byte-stable.

- Item v11: LastMoved (was whole-minute delta, rewritten every save) and
  DecayResetTime; Mobile v38: the three stat-gain stamps; BaseCreature v21:
  SummonEnd; plus VendorInventory, StealableArtifacts, and ML quest objective
  timers in their own version gates.
- 17 code-generated classes swap [DeltaDateTime] for [AnchoredDateTime] with a
  version bump and MigrateFrom each (generated from their current migration
  schemas, so old saves replay the delta reads).
- GenericPersistence payloads carry no anchor of their own, so they inherit
  the save-wide shift: entity indexes always load first, and every file in a
  save shares one SaveStartTime - World.LoadTimeShift is stamped from the idx
  v5 header and applied to generic persistence readers. This is what makes
  VirtueContext and StealableArtifacts convertible at all.
- Legacy read sites (old-version fallbacks and migration replays) keep
  ReadDeltaTime: they decode old bytes and must never change.

Acceptance tests: item serialization is byte-identical across saves hours
apart, and LastMoved/DecayResetTime round-trip exactly at sub-minute
precision (impossible under the old minutes encoding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kamron Batman 2026-08-22 19:19:42 -07:00
parent b992c7b955
commit 8d1283ba27
No known key found for this signature in database
GPG key ID: 7D81DF26D9A5D94A
46 changed files with 1407 additions and 223 deletions

View file

@ -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];
}
/// <summary>
/// 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.
/// </summary>
[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;
}
}
/// <summary>
/// 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.
/// </summary>
[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();
}
}

View file

@ -863,7 +863,7 @@ public partial class Item : IHued, IComparable<Item>, 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<Item>, 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<Item>, ISpawnable, IObjectPropert
switch (version)
{
case 11:
case 10:
case 9:
case 8:
@ -2780,7 +2775,11 @@ public partial class Item : IHued, IComparable<Item>, 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<Item>, 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;

View file

@ -2324,11 +2324,11 @@ public partial class Mobile : IHued, IComparable<Mobile>, 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<Mobile>, 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<Mobile>, 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;
}

View file

@ -504,6 +504,10 @@ public class GenericEntityPersistence<T> : 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)

View file

@ -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

View file

@ -99,6 +99,15 @@ public static class World
/// anchored timestamps can be re-based by the downtime at load.
/// </summary>
public static DateTime SaveStartTime { get; internal set; }
/// <summary>
/// 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 <see cref="SaveStartTime" />) and applied
/// to every reader of that save's files — including <see cref="GenericPersistence" />
/// payloads, which carry no anchor of their own. Zero for saves that predate the anchor.
/// </summary>
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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)
{

View file

@ -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<Item>();
var nextRespawn = reader.ReadDeltaTime();
var nextRespawn = version >= 2 ? reader.ReadAnchoredTime() : reader.ReadDeltaTime();
if (i < _artifacts.Length)
{

View file

@ -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)]

View file

@ -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;
}
}

View file

@ -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<Mobile> _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;

View file

@ -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"
]
}
]
}

View file

@ -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",
""
]
}
]
}

View file

@ -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"
}
]
}

View file

@ -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": [
""
]
}
]
}

View file

@ -0,0 +1,14 @@
{
"version": 1,
"type": "Server.Items.PuzzleChestSolutionAndTime",
"properties": [
{
"name": "When",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"AnchoredTime"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -0,0 +1,14 @@
{
"version": 2,
"type": "Server.Items.TransientItem",
"properties": [
{
"name": "Expiration",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"AnchoredTime"
]
}
]
}

View file

@ -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",
""
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -0,0 +1,14 @@
{
"version": 1,
"type": "Server.Mobiles.Sheep",
"properties": [
{
"name": "NextWoolTime",
"type": "System.DateTime",
"rule": "PrimitiveTypeMigrationRule",
"ruleArguments": [
"AnchoredTime"
]
}
]
}

View file

@ -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": [
""
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

@ -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"
]
}
]
}

View file

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

View file

@ -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();
}

View file

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

View file

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

View file

@ -37,7 +37,7 @@ namespace Server.Mobiles
Items = reader.ReadEntityList<Item>();
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

View file

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

View file

@ -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<Item> _items;
@ -17,7 +24,7 @@ public abstract partial class BaseCamp : BaseMulti
[SerializableField(1, setter: "private")]
private List<Mobile> _mobiles;
[DeltaDateTime]
[AnchoredDateTime]
[SerializableField(2, setter: "private")]
private DateTime _decayTime;

View file

@ -64,13 +64,19 @@ public class PoisonFieldSpell : MagerySpell, ITargetingSpell<IPoint3D>
}
[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;

View file

@ -67,16 +67,23 @@ public class FireFieldSpell : MagerySpell, ITargetingSpell<IPoint3D>
}
[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;

View file

@ -77,13 +77,19 @@ public class EnergyFieldSpell : MagerySpell, ITargetingSpell<IPoint3D>
}
[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;

View file

@ -77,13 +77,19 @@ public class ParalyzeFieldSpell : MagerySpell, ITargetingSpell<IPoint3D>
}
[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;

View file

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

View file

@ -63,13 +63,19 @@ public class WallOfStoneSpell : MagerySpell, ITargetingSpell<IPoint3D>
}
[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;