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
This commit is contained in:
parent
70575e1517
commit
1a7e7c7c70
7 changed files with 126 additions and 49 deletions
|
|
@ -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<int> bytes = stackalloc int[sizeof(decimal) / 4];
|
||||
decimal.GetBits(value, bytes);
|
||||
|
||||
MemoryMarshal.Cast<int, byte>(bytes).CopyTo(buffer.AsSpan());
|
||||
}
|
||||
|
||||
private static int ReadInt(ReadOnlySpan<byte> buffer) => BinaryPrimitives.ReadInt32LittleEndian(buffer);
|
||||
|
||||
private static decimal ReadDecimal(ReadOnlySpan<byte> buffer) => new(stackalloc int[4] { ReadInt(buffer), ReadInt(buffer[4..]), ReadInt(buffer[8..]), ReadInt(buffer[12..]) });
|
||||
|
||||
public static TheoryData<decimal> 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TileFlag>();
|
||||
var e = ReadEnum<TileFlag>();
|
||||
|
||||
Assert.Equal(TileFlag.Container, e);
|
||||
}
|
||||
Assert.Equal(TileFlag.Container, e);
|
||||
}
|
||||
|
||||
private unsafe T ReadEnum<T>() where T : unmanaged, Enum
|
||||
{
|
||||
var num = (long)TileFlag.Container;
|
||||
return *(T*)#
|
||||
}
|
||||
private static unsafe T ReadEnum<T>() where T : unmanaged, Enum
|
||||
{
|
||||
var num = (long)TileFlag.Container;
|
||||
return *(T*)#
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Loader;
|
||||
using Server.Logging;
|
||||
|
||||
namespace Server;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<int> buffer = stackalloc int[sizeof(decimal) / 4];
|
||||
decimal.GetBits(value, buffer);
|
||||
|
||||
Write(MemoryMarshal.Cast<int, byte>(buffer));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void InternalWriteString(string value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue