From 1a7e7c7c700ed9094f749719a268aa490bc89acf Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 28 Mar 2024 13:34:12 -0700 Subject: [PATCH] fix: Fixes spawner timer deserialization, decimal deserialization, and adds potion keg reverse lookup (#1711) ### Summary * Fixes spawner timer deserialization * Adds a check for a null timer and allows the timer to get recreated * Adds PotionKeg reverse lookup * Heavily optimizes decimal serialize/deserialize --- .../DecimalSerializationTests.cs | 46 +++++++++++++++++++ .../Serialization/EnumConversionTests.cs | 25 +++++----- Projects/Server/AssemblyHandler.cs | 1 - Projects/Server/Serialization/BufferWriter.cs | 10 ++++ .../Server/Serialization/IGenericWriter.cs | 11 +---- .../UOContent/Engines/Spawners/BaseSpawner.cs | 36 ++++++++------- .../Skill Items/Magical/Misc/PotionKeg.cs | 46 +++++++++++++++---- 7 files changed, 126 insertions(+), 49 deletions(-) create mode 100644 Projects/Server.Tests/Tests/Serialization/DecimalSerializationTests.cs diff --git a/Projects/Server.Tests/Tests/Serialization/DecimalSerializationTests.cs b/Projects/Server.Tests/Tests/Serialization/DecimalSerializationTests.cs new file mode 100644 index 000000000..246307e74 --- /dev/null +++ b/Projects/Server.Tests/Tests/Serialization/DecimalSerializationTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Buffers.Binary; +using System.Runtime.InteropServices; +using Xunit; + +namespace Server.Tests; + +public class DecimalSerializationTests +{ + private static void Write(byte[] buffer, decimal value) + { + Span bytes = stackalloc int[sizeof(decimal) / 4]; + decimal.GetBits(value, bytes); + + MemoryMarshal.Cast(bytes).CopyTo(buffer.AsSpan()); + } + + private static int ReadInt(ReadOnlySpan buffer) => BinaryPrimitives.ReadInt32LittleEndian(buffer); + + private static decimal ReadDecimal(ReadOnlySpan buffer) => new(stackalloc int[4] { ReadInt(buffer), ReadInt(buffer[4..]), ReadInt(buffer[8..]), ReadInt(buffer[12..]) }); + + public static TheoryData Data => + new() + { + 123.46m, + 0.0256m, + 10000m, + 2m, + 0.0001m, + 0.0000000000000000000000000001m + }; + + [Theory] + [MemberData(nameof(Data))] + public void TestSerializeDecimal(decimal value) + { + // Arrange + byte[] buffer = new byte[sizeof(decimal)]; + + // Act + Write(buffer, value); + + // Assert + Assert.Equal(value, ReadDecimal(buffer)); + } +} diff --git a/Projects/Server.Tests/Tests/Serialization/EnumConversionTests.cs b/Projects/Server.Tests/Tests/Serialization/EnumConversionTests.cs index 4e95890ad..26a6833b6 100644 --- a/Projects/Server.Tests/Tests/Serialization/EnumConversionTests.cs +++ b/Projects/Server.Tests/Tests/Serialization/EnumConversionTests.cs @@ -1,22 +1,21 @@ using System; using Xunit; -namespace Server.Tests +namespace Server.Tests; + +public class EnumConversionTests { - public class EnumConversionTests + [Fact] + public void TestToEnum() { - [Fact] - public void TestToEnum() - { - var e = ReadEnum(); + var e = ReadEnum(); - Assert.Equal(TileFlag.Container, e); - } + Assert.Equal(TileFlag.Container, e); + } - private unsafe T ReadEnum() where T : unmanaged, Enum - { - var num = (long)TileFlag.Container; - return *(T*)# - } + private static unsafe T ReadEnum() where T : unmanaged, Enum + { + var num = (long)TileFlag.Container; + return *(T*)# } } diff --git a/Projects/Server/AssemblyHandler.cs b/Projects/Server/AssemblyHandler.cs index d70aa81d9..2ff44bd4f 100644 --- a/Projects/Server/AssemblyHandler.cs +++ b/Projects/Server/AssemblyHandler.cs @@ -20,7 +20,6 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; -using Server.Logging; namespace Server; diff --git a/Projects/Server/Serialization/BufferWriter.cs b/Projects/Server/Serialization/BufferWriter.cs index 85a42c99a..f682e9dc1 100644 --- a/Projects/Server/Serialization/BufferWriter.cs +++ b/Projects/Server/Serialization/BufferWriter.cs @@ -19,6 +19,7 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Server.Text; @@ -290,6 +291,15 @@ public class BufferWriter : IGenericWriter } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(decimal value) + { + Span buffer = stackalloc int[sizeof(decimal) / 4]; + decimal.GetBits(value, buffer); + + Write(MemoryMarshal.Cast(buffer)); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void InternalWriteString(string value) { diff --git a/Projects/Server/Serialization/IGenericWriter.cs b/Projects/Server/Serialization/IGenericWriter.cs index 6595cb0fa..9c3469ccd 100644 --- a/Projects/Server/Serialization/IGenericWriter.cs +++ b/Projects/Server/Serialization/IGenericWriter.cs @@ -39,6 +39,7 @@ public interface IGenericWriter void Write(bool value); void Write(Serial serial); void Write(Type type); + void Write(decimal value); void Write(DateTime value) { @@ -79,20 +80,12 @@ public interface IGenericWriter Write((byte)bytesWritten); Write(stack[..bytesWritten]); } + void Write(TimeSpan value) { Write(value.Ticks); } - public void Write(decimal value) - { - var bits = decimal.GetBits(value); - - for (var i = 0; i < 4; ++i) - { - Write(bits[i]); - } - } void WriteEncodedInt(int value) { var v = (uint)value; diff --git a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs index 446d08156..2cc5bfe72 100644 --- a/Projects/UOContent/Engines/Spawners/BaseSpawner.cs +++ b/Projects/UOContent/Engines/Spawners/BaseSpawner.cs @@ -194,8 +194,11 @@ public abstract partial class BaseSpawner : Item, ISpawner get => _running && _timer?.Running == true ? End - Core.Now : TimeSpan.Zero; set { - Start(); - DoTimer(value); + if (!_running && Entries.Count > 0) + { + _running = true; + DoTimer(value); + } } } @@ -219,7 +222,7 @@ public abstract partial class BaseSpawner : Item, ISpawner entry?.RemoveFromSpawned(spawn); } - if (_running && !IsFull && _timer?.Running == false) + if (_running && !IsFull && _timer?.Running != true) { DoTimer(); } @@ -377,13 +380,10 @@ public abstract partial class BaseSpawner : Item, ISpawner public void Start() { - if (!_running) + if (!_running && Entries.Count > 0) { - if (Entries.Count > 0) - { - _running = true; - DoTimer(); - } + _running = true; + DoTimer(); } } @@ -757,7 +757,14 @@ public abstract partial class BaseSpawner : Item, ISpawner return; } - End = Core.Now + delay; + if (delay <= TimeSpan.Zero) + { + End = Core.Now; + } + else + { + End = Core.Now + delay; + } if (_timer == null) { @@ -795,7 +802,7 @@ public abstract partial class BaseSpawner : Item, ISpawner Entries.Remove(entry); - if (_running && !IsFull && _timer?.Running == false) + if (_running && !IsFull && _timer?.Running != true) { DoTimer(); } @@ -847,7 +854,7 @@ public abstract partial class BaseSpawner : Item, ISpawner } } - if (_running && !IsFull && _timer?.Running == false) + if (_running && !IsFull && _timer?.Running != true) { DoTimer(); } @@ -912,10 +919,7 @@ public abstract partial class BaseSpawner : Item, ISpawner } } - if (_running && _end > Core.Now) - { - DoTimer(_end - Core.Now); - } + DoTimer(_end - Core.Now); } private class InternalTimer : Timer diff --git a/Projects/UOContent/Items/Skill Items/Magical/Misc/PotionKeg.cs b/Projects/UOContent/Items/Skill Items/Magical/Misc/PotionKeg.cs index f9f15116f..1a6239322 100644 --- a/Projects/UOContent/Items/Skill Items/Magical/Misc/PotionKeg.cs +++ b/Projects/UOContent/Items/Skill Items/Magical/Misc/PotionKeg.cs @@ -12,9 +12,7 @@ public partial class PotionKeg : Item TileData.ItemTable[0x1940].Height = 4; } - [InvalidateProperties] - [SerializableField(0)] - [SerializedCommandProperty(AccessLevel.GameMaster)] + [InvalidateProperties] [SerializableField(0)] [SerializedCommandProperty(AccessLevel.GameMaster)] private PotionEffect _type; [Constructible] @@ -115,7 +113,7 @@ public partial class PotionKeg : Item { from.SendLocalizedMessage(502242); // You pour some of the keg's contents into an empty bottle... - var pot = FillBottle(); + var pot = FillBottle(_type); if (pack.TryDropItem(from, pot, false)) { @@ -184,9 +182,8 @@ public partial class PotionKeg : Item if (pot.PotionEffect != _type) { - from.SendLocalizedMessage( - 502236 - ); // You decide that it would be a bad idea to mix different types of potions. + // You decide that it would be a bad idea to mix different types of potions. + from.SendLocalizedMessage(502236); return false; } @@ -227,10 +224,9 @@ public partial class PotionKeg : Item return true; } - public BasePotion FillBottle() => - _type switch + public static BasePotion FillBottle(PotionEffect effect) => + effect switch { - PotionEffect.Nightsight => new NightSightPotion(), PotionEffect.CureLesser => new LesserCurePotion(), PotionEffect.Cure => new CurePotion(), PotionEffect.CureGreater => new GreaterCurePotion(), @@ -256,4 +252,34 @@ public partial class PotionKeg : Item PotionEffect.ConfusionBlastGreater => new GreaterConfusionBlastPotion(), _ => new NightSightPotion() }; + + // Function to convert potion type to potion effect enum + public static PotionEffect GetPotionEffect(Type type) => + type switch + { + _ when type == typeof(LesserCurePotion) => PotionEffect.CureLesser, + _ when type == typeof(CurePotion) => PotionEffect.Cure, + _ when type == typeof(GreaterCurePotion) => PotionEffect.CureGreater, + _ when type == typeof(AgilityPotion) => PotionEffect.Agility, + _ when type == typeof(GreaterAgilityPotion) => PotionEffect.AgilityGreater, + _ when type == typeof(StrengthPotion) => PotionEffect.Strength, + _ when type == typeof(GreaterStrengthPotion) => PotionEffect.StrengthGreater, + _ when type == typeof(LesserPoisonPotion) => PotionEffect.PoisonLesser, + _ when type == typeof(PoisonPotion) => PotionEffect.Poison, + _ when type == typeof(GreaterPoisonPotion) => PotionEffect.PoisonGreater, + _ when type == typeof(DeadlyPoisonPotion) => PotionEffect.PoisonDeadly, + _ when type == typeof(RefreshPotion) => PotionEffect.Refresh, + _ when type == typeof(TotalRefreshPotion) => PotionEffect.RefreshTotal, + _ when type == typeof(LesserHealPotion) => PotionEffect.HealLesser, + _ when type == typeof(HealPotion) => PotionEffect.Heal, + _ when type == typeof(GreaterHealPotion) => PotionEffect.HealGreater, + _ when type == typeof(LesserExplosionPotion) => PotionEffect.ExplosionLesser, + _ when type == typeof(ExplosionPotion) => PotionEffect.Explosion, + _ when type == typeof(GreaterExplosionPotion) => PotionEffect.ExplosionGreater, + _ when type == typeof(ConflagrationPotion) => PotionEffect.Conflagration, + _ when type == typeof(GreaterConflagrationPotion) => PotionEffect.ConflagrationGreater, + _ when type == typeof(ConfusionBlastPotion) => PotionEffect.ConfusionBlast, + _ when type == typeof(GreaterConfusionBlastPotion) => PotionEffect.ConfusionBlastGreater, + _ /* when type == typeof(NightSightPotion) */ => PotionEffect.Nightsight + }; }