diff --git a/.editorconfig b/.editorconfig
index 6cf20e3167..5acefbd372 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -135,6 +135,19 @@ dotnet_style_qualification_for_field=false:suggestion
dotnet_style_qualification_for_method=false:suggestion
dotnet_style_qualification_for_property=false:suggestion
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
+# Added by Derek
+dotnet_diagnostic.IDE0022.severity = none # expression bodies
+dotnet_diagnostic.IDE0130.severity = none # namespace not matching file location
+dotnet_diagnostic.IDE0290.severity = none # primary constructors
+dotnet_diagnostic.IDE1006.severity = none # naming violations with "_"
+dotnet_diagnostic.IDE0038.severity = none # pattern matching
+dotnet_diagnostic.IDE0008.severity = none # using var
+# probably should be ruled var or not var across the board but it's just suggestions anyway.
+# Leave this here in case we want to turn it on in the future
+# csharp_style_var_for_built_in_types = true:suggestion # suggest using var (implied) variables
+# csharp_style_var_when_type_is_apparent = true:suggestion # suggest using var (implied) variables
+# csharp_style_var_elsewhere = true:suggestion # suggest using var (implied) variables
+dotnet_diagnostic.RCS1123.severity = none
# ReSharper properties
resharper_apply_auto_detected_rules=false
diff --git a/Directory.Build.props b/Directory.Build.props
index 7aac5ce87b..68c23b16b0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -62,11 +62,11 @@
latest
-
+
- 3.6.139
+ 3.6.143
all
diff --git a/Distribution/Data/Decoration/Felucca/ocllo.cfg b/Distribution/Data/Decoration/Felucca/ocllo.cfg
index b4f25611f7..a434e69f6f 100644
--- a/Distribution/Data/Decoration/Felucca/ocllo.cfg
+++ b/Distribution/Data/Decoration/Felucca/ocllo.cfg
@@ -1060,7 +1060,7 @@ TreatiseOnAlchemy 0x0FF4
3672 2475 4
# spinning wheel
-Static 0x1019
+SpinningWheelEastAddon 0x1019
3667 2597 0
# pile of wool
diff --git a/Projects/Application/Application.cs b/Projects/Application/Application.cs
index 28c3aeda16..6eb5d213bb 100644
--- a/Projects/Application/Application.cs
+++ b/Projects/Application/Application.cs
@@ -7,16 +7,6 @@ public class Application
{
public static void Main(string[] args)
{
- bool profiling = false;
-
- foreach (var a in args)
- {
- if (a.InsensitiveEquals("-profile"))
- {
- profiling = true;
- }
- }
-
- Core.Setup(profiling, Assembly.GetEntryAssembly(), Process.GetCurrentProcess());
+ Core.Setup(Assembly.GetEntryAssembly(), Process.GetCurrentProcess());
}
}
diff --git a/Projects/Server.Tests/Helpers/Packet.cs b/Projects/Server.Tests/Helpers/Packet.cs
index 9432fc44eb..e9d1d421c2 100644
--- a/Projects/Server.Tests/Helpers/Packet.cs
+++ b/Projects/Server.Tests/Helpers/Packet.cs
@@ -2,7 +2,6 @@
using System.Buffers;
using System.Diagnostics;
using System.IO;
-using Server.Diagnostics;
namespace Server.Network
{
@@ -19,12 +18,6 @@ public abstract class Packet
protected Packet(int packetID)
{
PacketID = packetID;
-
- if (Core.Profiling)
- {
- var prof = PacketSendProfile.Acquire(PacketID);
- prof.Increment();
- }
}
protected Packet(int packetID, int length)
@@ -34,12 +27,6 @@ protected Packet(int packetID, int length)
Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length );
Stream.Write((byte)packetID);
-
- if (Core.Profiling)
- {
- var prof = PacketSendProfile.Acquire(PacketID);
- prof.Increment();
- }
}
public int PacketID { get; }
diff --git a/Projects/Server.Tests/Server.Tests.csproj b/Projects/Server.Tests/Server.Tests.csproj
index 46715636ec..a798866ec6 100644
--- a/Projects/Server.Tests/Server.Tests.csproj
+++ b/Projects/Server.Tests/Server.Tests.csproj
@@ -5,8 +5,8 @@
Server.Tests
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs
index 0ad93a512f..ed3bfd4e35 100644
--- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs
+++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/AccountPacketTests.cs
@@ -51,6 +51,10 @@ public Mobile this[int index]
public Serial Serial { get; }
public void Deserialize(IGenericReader reader) => throw new NotImplementedException();
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
public void Serialize(IGenericWriter writer) => throw new NotImplementedException();
public bool Deleted { get; }
diff --git a/Projects/Server/Buffers/STArrayPool.cs b/Projects/Server/Buffers/STArrayPool.cs
index d53d650ee4..25dd6a7f47 100644
--- a/Projects/Server/Buffers/STArrayPool.cs
+++ b/Projects/Server/Buffers/STArrayPool.cs
@@ -15,6 +15,16 @@ namespace Server.Buffers;
*/
public class STArrayPool : ArrayPool
{
+#if DEBUG_ARRAYPOOL
+ private class RentReturnStatus
+ {
+ public string StackTrace { get; set; }
+ public bool IsRented { get; set; }
+ }
+
+ private static readonly ConditionalWeakTable _rentedArrays = new();
+#endif
+
private const int StackArraySize = 32;
private const int BucketCount = 27; // SelectBucketIndex(1024 * 1024 * 1024 + 1)
private static readonly STArrayPool _shared = new();
@@ -39,6 +49,12 @@ public override T[] Rent(int minimumLength)
if (buffer is not null)
{
cachedBuckets[bucketIndex].Array = null;
+#if DEBUG_ARRAYPOOL
+ _rentedArrays.AddOrUpdate(
+ buffer,
+ new RentReturnStatus { IsRented = true }
+ );
+#endif
return buffer;
}
}
@@ -52,6 +68,12 @@ public override T[] Rent(int minimumLength)
buffer = b.TryPop();
if (buffer is not null)
{
+#if DEBUG_ARRAYPOOL
+ _rentedArrays.AddOrUpdate(
+ buffer,
+ new RentReturnStatus { IsRented = true }
+ );
+#endif
return buffer;
}
}
@@ -70,8 +92,16 @@ public override T[] Rent(int minimumLength)
throw new ArgumentOutOfRangeException(nameof(minimumLength));
}
- buffer = GC.AllocateUninitializedArray(minimumLength);
- return buffer;
+ var array = GC.AllocateUninitializedArray(minimumLength);
+
+#if DEBUG_ARRAYPOOL
+ _rentedArrays.AddOrUpdate(
+ array,
+ new RentReturnStatus { IsRented = true, StackTrace = Environment.StackTrace }
+ );
+#endif
+
+ return array;
}
public override void Return(T[]? array, bool clearArray = false)
@@ -91,10 +121,26 @@ public override void Return(T[]? array, bool clearArray = false)
Array.Clear(array);
}
+#if DEBUG_ARRAYPOOL
+ if (array.Length != GetMaxSizeForBucket(bucketIndex) || !_rentedArrays.TryGetValue(array, out var status))
+ {
+ throw new ArgumentException("Buffer is not from the pool", nameof(array));
+ }
+
+ if (!status!.IsRented)
+ {
+ throw new InvalidOperationException($"Array has already been returned.\nOriginal StackTrace:{status.StackTrace}\n");
+ }
+
+ // Mark it as returned
+ status.IsRented = false;
+ status.StackTrace = Environment.StackTrace;
+#else
if (array.Length != GetMaxSizeForBucket(bucketIndex))
{
throw new ArgumentException("Buffer is not from the pool", nameof(array));
}
+#endif
ref var bucketArray = ref cacheBuckets[bucketIndex];
var prev = bucketArray.Array;
diff --git a/Projects/Server/Diagnostics/BaseProfile.cs b/Projects/Server/Diagnostics/BaseProfile.cs
deleted file mode 100644
index 32e20ba556..0000000000
--- a/Projects/Server/Diagnostics/BaseProfile.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-
-namespace Server.Diagnostics;
-
-public abstract class BaseProfile
-{
- private readonly Stopwatch _stopwatch;
-
- protected BaseProfile(string name)
- {
- Name = name;
-
- _stopwatch = new Stopwatch();
- }
-
- public string Name { get; }
-
- public long Count { get; private set; }
-
- public TimeSpan AverageTime => TimeSpan.FromTicks(TotalTime.Ticks / Math.Max(Count, 1));
-
- public TimeSpan PeakTime { get; private set; }
-
- public TimeSpan TotalTime { get; private set; }
-
- public static void WriteAll(TextWriter op, IEnumerable profiles) where T : BaseProfile
- {
- var list = new List(profiles);
-
- list.Sort((a, b) => -a.TotalTime.CompareTo(b.TotalTime));
-
- foreach (var prof in list)
- {
- prof.WriteTo(op);
- op.WriteLine();
- }
- }
-
- public virtual void Start()
- {
- if (_stopwatch.IsRunning)
- {
- _stopwatch.Reset();
- }
-
- _stopwatch.Start();
- }
-
- public virtual void Finish()
- {
- var elapsed = _stopwatch.Elapsed;
-
- TotalTime += elapsed;
-
- if (elapsed > PeakTime)
- {
- PeakTime = elapsed;
- }
-
- Count++;
-
- _stopwatch.Reset();
- }
-
- public virtual void WriteTo(TextWriter op)
- {
- op.Write(
- "{0,-100} {1,12:N0} {2,12:F5} {3,-12:F5} {4,12:F5}",
- Name,
- Count,
- AverageTime.TotalSeconds,
- PeakTime.TotalSeconds,
- TotalTime.TotalSeconds
- );
- }
-}
diff --git a/Projects/Server/Diagnostics/PacketProfile.cs b/Projects/Server/Diagnostics/PacketProfile.cs
deleted file mode 100644
index 2a731233c2..0000000000
--- a/Projects/Server/Diagnostics/PacketProfile.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-
-namespace Server.Diagnostics;
-
-public abstract class BasePacketProfile : BaseProfile
-{
- protected BasePacketProfile(string name) : base(name)
- {
- }
-
- public long TotalLength { get; private set; }
-
- public double AverageLength => (double)TotalLength / Math.Max(Count, 1);
-
- public void Finish(long length)
- {
- Finish();
-
- TotalLength += length;
- }
-
- public override void WriteTo(TextWriter op)
- {
- base.WriteTo(op);
-
- op.Write("\t{0,12:F2} {1,-12:N0}", AverageLength, TotalLength);
- }
-}
-
-public class PacketSendProfile : BasePacketProfile
-{
- private static readonly Dictionary _profiles = new();
-
- private long _created;
-
- public PacketSendProfile(int packetId) : base($"0x{packetId:X2}")
- {
- }
-
- public static IEnumerable Profiles => _profiles.Values;
-
- public static PacketSendProfile Acquire(int packetId)
- {
- if (!_profiles.TryGetValue(packetId, out var prof))
- {
- _profiles.Add(packetId, prof = new PacketSendProfile(packetId));
- }
-
- return prof;
- }
-
- public void Increment()
- {
- Interlocked.Increment(ref _created);
- }
-
- public override void WriteTo(TextWriter op)
- {
- base.WriteTo(op);
-
- op.Write("\t{0,12:N0}", _created);
- }
-}
-
-public class PacketReceiveProfile : BasePacketProfile
-{
- private static readonly Dictionary
- _profiles = new();
-
- public PacketReceiveProfile(int packetId) : base($"0x{packetId:X2}")
- {
- }
-
- public static IEnumerable Profiles => _profiles.Values;
-
- public static PacketReceiveProfile Acquire(int packetId)
- {
- if (!_profiles.TryGetValue(packetId, out var prof))
- {
- _profiles.Add(packetId, prof = new PacketReceiveProfile(packetId));
- }
-
- return prof;
- }
-}
diff --git a/Projects/Server/Diagnostics/TargetProfile.cs b/Projects/Server/Diagnostics/TargetProfile.cs
deleted file mode 100644
index 32971c341a..0000000000
--- a/Projects/Server/Diagnostics/TargetProfile.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Server.Diagnostics;
-
-public class TargetProfile : BaseProfile
-{
- private static readonly Dictionary _profiles = new();
-
- public TargetProfile(Type type)
- : base(type.FullName)
- {
- }
-
- public static IEnumerable Profiles => _profiles.Values;
-
- public static TargetProfile Acquire(Type type)
- {
- if (!Core.Profiling)
- {
- return null;
- }
-
- if (!_profiles.TryGetValue(type, out var prof))
- {
- _profiles.Add(type, prof = new TargetProfile(type));
- }
-
- return prof;
- }
-}
diff --git a/Projects/Server/Diagnostics/TimerProfile.cs b/Projects/Server/Diagnostics/TimerProfile.cs
deleted file mode 100644
index c1ba30d91a..0000000000
--- a/Projects/Server/Diagnostics/TimerProfile.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.Collections.Generic;
-using System.IO;
-
-namespace Server.Diagnostics;
-
-public class TimerProfile : BaseProfile
-{
- private static readonly Dictionary _profiles = new();
-
- public TimerProfile(string name)
- : base(name)
- {
- }
-
- public static IEnumerable Profiles => _profiles.Values;
-
- public long Created { get; set; }
-
- public long Started { get; set; }
-
- public long Stopped { get; set; }
-
- public static TimerProfile Acquire(string name)
- {
- if (!Core.Profiling)
- {
- return null;
- }
-
- if (!_profiles.TryGetValue(name, out var prof))
- {
- _profiles.Add(name, prof = new TimerProfile(name));
- }
-
- return prof;
- }
-
- public override void WriteTo(TextWriter op)
- {
- base.WriteTo(op);
-
- op.Write("\t{0,12:N0} {1,12:N0} {2,-12:N0}", Created, Started, Stopped);
- }
-}
diff --git a/Projects/Server/Events/CharacterCreatedEvent.cs b/Projects/Server/Events/CharacterCreatedEvent.cs
deleted file mode 100644
index 59dd626a6f..0000000000
--- a/Projects/Server/Events/CharacterCreatedEvent.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-/*************************************************************************
- * ModernUO *
- * Copyright 2019-2023 - ModernUO Development Team *
- * Email: hi@modernuo.com *
- * File: CharacterCreatedEvent.cs *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- *************************************************************************/
-
-using System;
-using System.Runtime.CompilerServices;
-using Server.Accounting;
-using Server.Network;
-
-namespace Server;
-
-public class CharacterCreatedEventArgs
-{
- public CharacterCreatedEventArgs(
- NetState state, IAccount a, string name, bool female, int hue, StatNameValue[] stats, CityInfo city,
- SkillNameValue[] skills, int shirtHue, int pantsHue, int hairID, int hairHue, int beardID, int beardHue,
- int profession, Race race
- )
- {
- State = state;
- Account = a;
- Name = name;
- Female = female;
- Hue = hue;
- Stats = stats;
- City = city;
- Skills = skills;
- ShirtHue = shirtHue;
- PantsHue = pantsHue;
- HairID = hairID;
- HairHue = hairHue;
- BeardID = beardID;
- BeardHue = beardHue;
- Profession = profession;
- Race = race;
- }
-
- public NetState State { get; }
-
- public IAccount Account { get; }
-
- public Mobile Mobile { get; set; }
-
- public string Name { get; }
-
- public bool Female { get; }
-
- public int Hue { get; }
-
- public StatNameValue[] Stats { get; }
-
- public CityInfo City { get; }
-
- public SkillNameValue[] Skills { get; }
-
- public int ShirtHue { get; }
-
- public int PantsHue { get; }
-
- public int HairID { get; }
-
- public int HairHue { get; }
-
- public int BeardID { get; }
-
- public int BeardHue { get; }
-
- public int Profession { get; set; }
-
- public Race Race { get; }
-}
-
-public readonly record struct SkillNameValue(SkillName Name, int Value);
-public readonly record struct StatNameValue(StatType Name, int Value);
-
-public static partial class EventSink
-{
- public static event Action CharacterCreated;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void InvokeCharacterCreated(CharacterCreatedEventArgs e) => CharacterCreated?.Invoke(e);
-}
diff --git a/Projects/Server/Events/EventSink.cs b/Projects/Server/Events/EventSink.cs
index 129e256e7a..6a72185f4c 100644
--- a/Projects/Server/Events/EventSink.cs
+++ b/Projects/Server/Events/EventSink.cs
@@ -34,14 +34,6 @@ public static partial class EventSink
public static event Action Disconnected;
public static void InvokeDisconnected(Mobile m) => Disconnected?.Invoke(m);
- public static event Action PlayerDeath;
- public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m);
-
- public static event Action VirtueMacroRequest;
-
- public static void InvokeVirtueMacroRequest(Mobile mobile, int virtueID) =>
- VirtueMacroRequest?.Invoke(mobile, virtueID);
-
public static event Action PaperdollRequest;
public static void InvokePaperdollRequest(Mobile beholder, Mobile beheld) =>
@@ -49,9 +41,4 @@ public static void InvokePaperdollRequest(Mobile beholder, Mobile beheld) =>
public static event Action ServerStarted;
public static void InvokeServerStarted() => ServerStarted?.Invoke();
-
- public static event Action TargetByResourceMacro;
-
- public static void InvokeTargetByResourceMacro(Mobile m, Item item, short resourceType) =>
- TargetByResourceMacro?.Invoke(m, item, resourceType);
}
diff --git a/Projects/Server/Events/ServerListEvent.cs b/Projects/Server/Events/ServerListEvent.cs
deleted file mode 100644
index ce1c88ed11..0000000000
--- a/Projects/Server/Events/ServerListEvent.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-/*************************************************************************
- * ModernUO *
- * Copyright 2019-2023 - ModernUO Development Team *
- * Email: hi@modernuo.com *
- * File: ServerListEvent.cs *
- * *
- * This program is free software: you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation, either version 3 of the License, or *
- * (at your option) any later version. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program. If not, see . *
- *************************************************************************/
-
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Runtime.CompilerServices;
-using Server.Accounting;
-using Server.Network;
-
-namespace Server;
-
-public class ServerListEventArgs
-{
- public ServerListEventArgs(NetState state, IAccount account)
- {
- State = state;
- Account = account;
- Servers = new List();
- }
-
- public NetState State { get; }
-
- public IAccount Account { get; }
-
- public bool Rejected { get; set; }
-
- public List Servers { get; }
-
- public void AddServer(string name, IPEndPoint address)
- {
- AddServer(name, 0, TimeZoneInfo.Local, address);
- }
-
- public void AddServer(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address)
- {
- Servers.Add(new ServerInfo(name, fullPercent, tz, address));
- }
-}
-
-public static partial class EventSink
-{
- public static event Action ServerList;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void InvokeServerList(ServerListEventArgs e) => ServerList?.Invoke(e);
-}
diff --git a/Projects/Server/Events/GameLoginEvent.cs b/Projects/Server/Events/SocketConnectionEvent.cs
similarity index 66%
rename from Projects/Server/Events/GameLoginEvent.cs
rename to Projects/Server/Events/SocketConnectionEvent.cs
index 5d05f180a6..9ac96dcd38 100644
--- a/Projects/Server/Events/GameLoginEvent.cs
+++ b/Projects/Server/Events/SocketConnectionEvent.cs
@@ -2,7 +2,7 @@
* ModernUO *
* Copyright 2019-2023 - ModernUO Development Team *
* Email: hi@modernuo.com *
- * File: GameLoginEvent.cs *
+ * File: SocketConnectionEvent.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
@@ -14,35 +14,28 @@
*************************************************************************/
using System;
+using System.Net.Sockets;
using System.Runtime.CompilerServices;
-using Server.Network;
namespace Server;
-public class GameLoginEventArgs
+public class SocketConnectEventArgs
{
- public GameLoginEventArgs(NetState state, string un, string pw)
+ public SocketConnectEventArgs(Socket c)
{
- State = state;
- Username = un;
- Password = pw;
+ Connection = c;
+ AllowConnection = true;
}
- public NetState State { get; }
+ public Socket Connection { get; }
- public string Username { get; }
-
- public string Password { get; }
-
- public bool Accepted { get; set; }
-
- public CityInfo[] CityInfo { get; set; }
+ public bool AllowConnection { get; set; }
}
public static partial class EventSink
{
- public static event Action GameLogin;
+ public static event Action SocketConnect;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void InvokeGameLogin(GameLoginEventArgs e) => GameLogin?.Invoke(e);
+ public static void InvokeSocketConnect(SocketConnectEventArgs e) => SocketConnect?.Invoke(e);
}
diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs
index ab11330c38..8998860cf1 100644
--- a/Projects/Server/Guild.cs
+++ b/Projects/Server/Guild.cs
@@ -52,6 +52,10 @@ protected BaseGuild()
[CommandProperty(AccessLevel.GameMaster, readOnly: true)]
public DateTime Created { get; set; } = Core.Now;
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
public abstract void Serialize(IGenericWriter writer);
public abstract void Deserialize(IGenericReader reader);
diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs
index 4ebc265626..95b11843b2 100644
--- a/Projects/Server/IEntity.cs
+++ b/Projects/Server/IEntity.cs
@@ -115,6 +115,10 @@ public void Deserialize(IGenericReader reader)
Timer.StartTimer(Delete);
}
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
public void Serialize(IGenericWriter writer)
{
}
diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs
index 368991f2a0..aeab60811f 100644
--- a/Projects/Server/Items/Item.cs
+++ b/Projects/Server/Items/Item.cs
@@ -802,6 +802,10 @@ public virtual void GetProperties(IPropertyList list)
[CommandProperty(AccessLevel.Counselor)]
public Serial Serial { get; }
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
public virtual void Serialize(IGenericWriter writer)
{
writer.Write(9); // version
diff --git a/Projects/Server/Items/VirtualHair.cs b/Projects/Server/Items/VirtualHair.cs
index 89310e21a5..ce67350738 100644
--- a/Projects/Server/Items/VirtualHair.cs
+++ b/Projects/Server/Items/VirtualHair.cs
@@ -1,6 +1,5 @@
using System;
using System.Buffers;
-using System.Runtime.CompilerServices;
using ModernUO.Serialization;
using Server.Network;
diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs
index cdc30fa3df..13577440d0 100644
--- a/Projects/Server/Main.cs
+++ b/Projects/Server/Main.cs
@@ -44,9 +44,6 @@ public static class Core
private static bool _crashed;
private static string _baseDirectory;
- private static bool _profiling;
- private static long _profileStart;
- private static long _profileTime;
private static bool? _isRunningFromXUnit;
private static int _itemCount;
@@ -90,30 +87,6 @@ public static bool IsRunningFromXUnit
}
#nullable restore
- public static bool Profiling
- {
- get => _profiling;
- set
- {
- if (_profiling == value)
- {
- return;
- }
-
- _profiling = value;
-
- if (_profileStart > 0)
- {
- _profileTime += Stopwatch.GetTimestamp() - _profileStart;
- }
-
- _profileStart = _profiling ? Stopwatch.GetTimestamp() : 0;
- }
- }
-
- public static TimeSpan ProfileTime =>
- TimeSpan.FromTicks(_profileStart > 0 ? _profileTime + (Stopwatch.GetTimestamp() - _profileStart) : _profileTime);
-
public static Assembly ApplicationAssembly { get; set; }
public static Assembly Assembly { get; set; }
@@ -126,50 +99,15 @@ public static bool Profiling
private static long _firstTick;
- [ThreadStatic]
private static long _tickCount;
- // Don't access this from other threads than the game thread.
private static DateTime _now;
- // For Unix Stopwatch.Frequency is normalized to 1ns
- // We don't anticipate needing this for Windows/OSX
- private const long _maxTickCountBeforePrecisionLoss = long.MaxValue / 1000L;
- private static readonly long _ticksPerMillisecond = Stopwatch.Frequency / 1000L;
+ public static long TickCount => _tickCount;
- public static long TickCount
- {
- get
- {
- if (_tickCount != 0)
- {
- return _tickCount;
- }
+ public static DateTime Now => _now;
- var timestamp = Stopwatch.GetTimestamp();
-
- return timestamp > _maxTickCountBeforePrecisionLoss
- ? timestamp / _ticksPerMillisecond
- // No precision loss
- : 1000L * timestamp / Stopwatch.Frequency;
- }
- // Setting this to a value lower than the previous is bad. Timers will become delayed
- // until time catches up.
- set => _tickCount = value;
- }
-
- public static DateTime Now
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get
- {
- // See notes above for _now and why this is a volatile variable.
- var now = _now;
- return now == DateTime.MinValue ? DateTime.UtcNow : now;
- }
- }
-
- public static long Uptime => Thread.CurrentThread != Thread ? 0 : TickCount - _firstTick;
+ public static long Uptime => TickCount - _firstTick;
private static long _cycleIndex;
private static readonly double[] _cyclesPerSecond = new double[128];
@@ -207,21 +145,6 @@ public static string BaseDirectory
public static bool Closing => ClosingTokenSource.IsCancellationRequested;
- public static string Arguments
- {
- get
- {
- var sb = new StringBuilder();
-
- if (_profiling)
- {
- Utility.Separate(sb, "-profile", " ");
- }
-
- return sb.ToString();
- }
- }
-
public static int GlobalUpdateRange { get; set; } = 18;
public static int GlobalMaxUpdateRange { get; set; } = 24;
@@ -380,7 +303,7 @@ internal static void DoKill(bool restart = false)
logger.Information("Restarting");
if (IsWindows)
{
- using var process = Process.Start("dotnet", $"{ApplicationAssembly.Location} {Arguments}");
+ using var process = Process.Start("dotnet", $"{ApplicationAssembly.Location}");
}
else
{
@@ -388,7 +311,7 @@ internal static void DoKill(bool restart = false)
process.StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
- Arguments = $"{ApplicationAssembly.Location} {Arguments}",
+ Arguments = $"{ApplicationAssembly.Location}",
UseShellExecute = true
};
@@ -413,6 +336,8 @@ private static void HandleClosed()
World.WaitForWriteCompletion();
World.ExitSerializationThreads();
+ PingServer.Shutdown();
+ TcpServer.Shutdown();
if (!_crashed)
{
@@ -420,7 +345,10 @@ private static void HandleClosed()
}
}
- public static void Setup(bool profiling, Assembly applicationAssembly, Process process)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static long GetTimestamp() => 1000L * Stopwatch.GetTimestamp() / Stopwatch.Frequency;
+
+ public static void Setup(Assembly applicationAssembly, Process process)
{
Process = process;
ApplicationAssembly = applicationAssembly;
@@ -428,7 +356,6 @@ public static void Setup(bool profiling, Assembly applicationAssembly, Process p
Thread = Thread.CurrentThread;
LoopContext = new EventLoopContext();
SynchronizationContext.SetSynchronizationContext(LoopContext);
- Profiling = profiling;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
@@ -472,13 +399,6 @@ You should have received a copy of the GNU General Public License
logger.Information("Running on {Framework}", RuntimeInformation.FrameworkDescription);
- var s = Arguments;
-
- if (s.Length > 0)
- {
- logger.Information("Running with arguments: {Args}", s);
- }
-
var assemblyPath = Path.Join(BaseDirectory, AssembliesConfiguration);
// Load UOContent.dll
@@ -497,7 +417,10 @@ You should have received a copy of the GNU General Public License
VerifySerialization();
- Timer.Init(TickCount);
+ _now = DateTime.UtcNow;
+ _firstTick = _tickCount = GetTimestamp();
+
+ Timer.Init(_tickCount);
AssemblyHandler.Invoke("Configure");
@@ -511,7 +434,6 @@ You should have received a copy of the GNU General Public License
TcpServer.Start();
PingServer.Start();
EventSink.InvokeServerStarted();
- _firstTick = TickCount;
RunEventLoop();
}
@@ -526,7 +448,7 @@ public static void RunEventLoop()
#endif
var cycleCount = _cyclesPerSecond.Length;
- long last = Stopwatch.GetTimestamp();
+ long last = _tickCount;
const int interval = 100;
double frequency = Stopwatch.Frequency * interval;
@@ -534,7 +456,7 @@ public static void RunEventLoop()
while (!Closing)
{
- _tickCount = TickCount;
+ _tickCount = GetTimestamp();
_now = DateTime.UtcNow;
Mobile.ProcessDeltaQueue();
@@ -543,7 +465,6 @@ public static void RunEventLoop()
// Handle networking
NetState.Slice();
- // PingServer.Slice();
// Execute captured post-await methods (like Timer.Pause)
LoopContext.ExecuteTasks();
@@ -563,12 +484,10 @@ public static void RunEventLoop()
break;
}
- _tickCount = 0;
- _now = DateTime.MinValue;
-
- if (++sample % interval == 0)
+ if (sample++ == interval)
{
- var now = Stopwatch.GetTimestamp();
+ sample = 0;
+ var now = GetTimestamp();
var cyclesPerSecond = frequency / (now - last);
_cyclesPerSecond[_cycleIndex++] = cyclesPerSecond;
diff --git a/Projects/Server/Maps/Map.ClientEnumerator.cs b/Projects/Server/Maps/Map.ClientEnumerator.cs
index 4321a0fc5d..ae53010a1b 100644
--- a/Projects/Server/Maps/Map.ClientEnumerator.cs
+++ b/Projects/Server/Maps/Map.ClientEnumerator.cs
@@ -195,11 +195,14 @@ public MobileEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
_bounds = bounds;
- map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
+ if (map != null)
+ {
+ map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
- // We start the X sector one short because it gets incremented immediately in MoveNext()
- _currentSectorX = _sectorStartX - 1;
- _currentSectorY = _sectorStartY;
+ // We start the X sector one short because it gets incremented immediately in MoveNext()
+ _currentSectorX = _sectorStartX - 1;
+ _currentSectorY = _sectorStartY;
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Projects/Server/Maps/Map.ItemEnumerator.cs b/Projects/Server/Maps/Map.ItemEnumerator.cs
index 9eab2effb9..4bf8f42029 100644
--- a/Projects/Server/Maps/Map.ItemEnumerator.cs
+++ b/Projects/Server/Maps/Map.ItemEnumerator.cs
@@ -226,11 +226,14 @@ public ItemEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
_bounds = bounds;
- map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
+ if (map != null)
+ {
+ map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
- // We start the X sector one short because it gets incremented immediately in MoveNext()
- _currentSectorX = _sectorStartX - 1;
- _currentSectorY = _sectorStartY;
+ // We start the X sector one short because it gets incremented immediately in MoveNext()
+ _currentSectorX = _sectorStartX - 1;
+ _currentSectorY = _sectorStartY;
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Projects/Server/Maps/Map.MobileEnumerator.cs b/Projects/Server/Maps/Map.MobileEnumerator.cs
index 9e7edee15e..cac64ce3a6 100644
--- a/Projects/Server/Maps/Map.MobileEnumerator.cs
+++ b/Projects/Server/Maps/Map.MobileEnumerator.cs
@@ -225,11 +225,14 @@ public MobileEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusive)
_bounds = bounds;
- map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
+ if (map != null)
+ {
+ map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
- // We start the X sector one short because it gets incremented immediately in MoveNext()
- _currentSectorX = _sectorStartX - 1;
- _currentSectorY = _sectorStartY;
+ // We start the X sector one short because it gets incremented immediately in MoveNext()
+ _currentSectorX = _sectorStartX - 1;
+ _currentSectorY = _sectorStartY;
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Projects/Server/Maps/Map.MultiEnumerator.cs b/Projects/Server/Maps/Map.MultiEnumerator.cs
index d7e9f9eaf4..bc1f7b018b 100644
--- a/Projects/Server/Maps/Map.MultiEnumerator.cs
+++ b/Projects/Server/Maps/Map.MultiEnumerator.cs
@@ -189,12 +189,15 @@ public MultiBoundsEnumerator(Map map, Rectangle2D bounds, bool makeBoundsInclusi
_bounds = bounds;
- map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
+ if (map != null)
+ {
+ map.CalculateSectors(bounds, out _sectorStartX, out var _sectorStartY, out _sectorEndX, out _sectorEndY);
- // We start the X sector one short because it gets incremented immediately in MoveNext()
- _currentSectorX = _sectorStartX - 1;
- _currentSectorY = _sectorStartY;
- _index = 0;
+ // We start the X sector one short because it gets incremented immediately in MoveNext()
+ _currentSectorX = _sectorStartX - 1;
+ _currentSectorY = _sectorStartY;
+ _index = 0;
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs
index adcdfbc606..d6b9eb9a24 100644
--- a/Projects/Server/Mobiles/Mobile.cs
+++ b/Projects/Server/Mobiles/Mobile.cs
@@ -59,7 +59,7 @@ public class DamageEntry
}
[Flags]
-public enum StatType
+public enum StatType : byte
{
Str = 1,
Dex = 2,
@@ -2277,6 +2277,10 @@ public virtual void GetProperties(IPropertyList list)
[CommandProperty(AccessLevel.Counselor)]
public Serial Serial { get; }
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
public virtual void Serialize(IGenericWriter writer)
{
writer.Write(36); // version
@@ -2847,6 +2851,7 @@ public virtual void ProcessDelta()
Layer.FacialHair
);
}
+
ourState.Send(facialHairPacket);
}
@@ -2984,6 +2989,7 @@ public virtual void ProcessDelta()
Layer.FacialHair
);
}
+
state.Send(facialHairPacket);
}
@@ -3429,7 +3435,6 @@ public virtual void ValidateSkillMods()
Skills[mod.Skill]?.Update();
}
}
-
}
public virtual void AddSkillMod(SkillMod mod)
@@ -4826,37 +4831,34 @@ public virtual void OnDeath(Container c)
if (!m_Player)
{
Delete();
+ return;
}
- else
- {
- m_NetState.SendDeathStatus();
- Warmode = false;
+ m_NetState.SendDeathStatus();
- BodyMod = 0;
- // Body = this.Female ? 0x193 : 0x192;
- Body = Race.GhostBody(this);
+ Warmode = false;
- var deathShroud = new Item(0x204E) { Movable = false, Layer = Layer.OuterTorso };
+ BodyMod = 0;
+ // Body = this.Female ? 0x193 : 0x192;
+ Body = Race.GhostBody(this);
- AddItem(deathShroud);
+ var deathShroud = new Item(0x204E) { Movable = false, Layer = Layer.OuterTorso };
- Items.Remove(deathShroud);
- Items.Insert(0, deathShroud);
+ AddItem(deathShroud);
- Poison = null;
- Combatant = null;
+ Items.Remove(deathShroud);
+ Items.Insert(0, deathShroud);
- Hits = 0;
- Stam = 0;
- Mana = 0;
+ Poison = null;
+ Combatant = null;
- EventSink.InvokePlayerDeath(this);
+ Hits = 0;
+ Stam = 0;
+ Mana = 0;
- ProcessDelta();
+ ProcessDelta();
- CheckStatTimers();
- }
+ CheckStatTimers();
}
public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => true;
@@ -5070,7 +5072,8 @@ public virtual void Lift(Item item, int amount, out bool rejected, out LRReason
{
var rootItem = root as Item;
- Span buffer = stackalloc byte[OutgoingPlayerPackets.DragEffectPacketLength].InitializePacket();
+ Span buffer = stackalloc byte[OutgoingPlayerPackets.DragEffectPacketLength]
+ .InitializePacket();
foreach (var ns in map.GetClientsInRange(from.Location))
{
@@ -5588,7 +5591,8 @@ public virtual void DoSpeech(string text, int[] keywords, MessageType type, int
ProcessDelta();
Span regBuffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket();
- Span mutBuffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(mutatedText)].InitializePacket();
+ Span mutBuffer =
+ stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(mutatedText)].InitializePacket();
// TODO: Should this be sorted like onSpeech is below?
for (var i = 0; i < hears.Count; ++i)
@@ -5598,7 +5602,16 @@ public virtual void DoSpeech(string text, int[] keywords, MessageType type, int
if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext))
{
var length = OutgoingMessagePackets.CreateMessage(
- regBuffer, Serial, Body, type, hue, 3, false, m_Language, Name, text
+ regBuffer,
+ Serial,
+ Body,
+ type,
+ hue,
+ 3,
+ false,
+ m_Language,
+ Name,
+ text
);
if (length != regBuffer.Length)
@@ -5612,7 +5625,16 @@ public virtual void DoSpeech(string text, int[] keywords, MessageType type, int
else
{
var length = OutgoingMessagePackets.CreateMessage(
- mutBuffer, Serial, Body, type, hue, 3, false, m_Language, Name, mutatedText
+ mutBuffer,
+ Serial,
+ Body,
+ type,
+ hue,
+ 3,
+ false,
+ m_Language,
+ Name,
+ mutatedText
);
if (length != mutBuffer.Length)
@@ -6899,10 +6921,12 @@ public void SendEverything()
}
}
- var range = new Rectangle2D(m_Location.X - Core.GlobalMaxUpdateRange,
+ var range = new Rectangle2D(
+ m_Location.X - Core.GlobalMaxUpdateRange,
m_Location.Y - Core.GlobalMaxUpdateRange,
Core.GlobalMaxUpdateRange * 2 + 1,
- Core.GlobalMaxUpdateRange * 2 + 1);
+ Core.GlobalMaxUpdateRange * 2 + 1
+ );
foreach (var multi in m_Map.GetMultisInBounds(range))
{
@@ -8072,7 +8096,8 @@ public Map.MobileBoundsEnumerable GetMobilesInRange(int range) where T : M
m_Map == null ? Map.MobileBoundsEnumerable.Empty : m_Map.GetMobilesInRange(m_Location, range);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Map.ClientAtEnumerable GetClientsAt() => m_Map == null ? Map.ClientAtEnumerable.Empty : Map.GetClientsAt(m_Location);
+ public Map.ClientAtEnumerable GetClientsAt() =>
+ m_Map == null ? Map.ClientAtEnumerable.Empty : Map.GetClientsAt(m_Location);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Map.ClientBoundsEnumerable GetClientsInRange(int range) =>
@@ -8852,7 +8877,16 @@ public void PublicOverheadMessage(
)
{
var length = OutgoingMessagePackets.CreateMessage(
- buffer, Serial, Body, type, hue, 3, ascii, Language, Name, text
+ buffer,
+ Serial,
+ Body,
+ type,
+ hue,
+ 3,
+ ascii,
+ Language,
+ Name,
+ text
);
if (length != buffer.Length)
@@ -8879,7 +8913,15 @@ public void PublicOverheadMessage(MessageType type, int hue, int number, string
if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this)))
{
var length = OutgoingMessagePackets.CreateMessageLocalized(
- buffer, Serial, Body, type, hue, 3, number, Name, args
+ buffer,
+ Serial,
+ Body,
+ type,
+ hue,
+ 3,
+ number,
+ Name,
+ args
);
if (length != buffer.Length)
@@ -8915,7 +8957,17 @@ public void PublicOverheadMessage(
)
{
var length = OutgoingMessagePackets.CreateMessageLocalizedAffix(
- buffer, Serial, Body, type, hue, 3, number, Name, affixType, affix, args
+ buffer,
+ Serial,
+ Body,
+ type,
+ hue,
+ 3,
+ number,
+ Name,
+ affixType,
+ affix,
+ args
);
if (length != buffer.Length)
@@ -8959,7 +9011,15 @@ public void NonlocalOverheadMessage(MessageType type, int hue, int number, strin
if (state != m_NetState && state.Mobile.CanSee(this))
{
var length = OutgoingMessagePackets.CreateMessageLocalized(
- buffer, Serial, Body, type, hue, 3, number, Name, args
+ buffer,
+ Serial,
+ Body,
+ type,
+ hue,
+ 3,
+ number,
+ Name,
+ args
);
if (length != buffer.Length)
@@ -8986,7 +9046,16 @@ public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, strin
if (state != m_NetState && state.Mobile.CanSee(this))
{
var length = OutgoingMessagePackets.CreateMessage(
- buffer, Serial, Body, type, hue, 3, ascii, Language, Name, text
+ buffer,
+ Serial,
+ Body,
+ type,
+ hue,
+ 3,
+ ascii,
+ Language,
+ Name,
+ text
);
if (length != buffer.Length)
diff --git a/Projects/Server/Network/Firewall/Firewall.cs b/Projects/Server/Network/Firewall/Firewall.cs
index f35046d40d..e800504165 100644
--- a/Projects/Server/Network/Firewall/Firewall.cs
+++ b/Projects/Server/Network/Firewall/Firewall.cs
@@ -14,83 +14,21 @@
*************************************************************************/
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using Server.Logging;
namespace Server.Network;
public static class Firewall
{
- private static readonly ILogger logger = LogFactory.GetLogger(typeof(Firewall));
-
private static InternalValidationEntry _validationEntry;
private static readonly Dictionary _isBlockedCache = new();
- private static readonly ConcurrentQueue<(IFirewallEntry FirewallyEntry, bool Remove)> _firewallQueue = new();
private static readonly SortedSet _firewallSet = new();
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static IFirewallEntry RequestAddSingleIPEntry(string entry)
- {
- try
- {
- var firewallEntry = new SingleIpFirewallEntry(entry);
- _firewallQueue.Enqueue((firewallEntry, false));
- return firewallEntry;
- }
- catch (Exception e)
- {
- logger.Warning(e, "Failed to add firewall entry: {Pattern}", entry);
- return null;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static IFirewallEntry RequestAddCIDREntry(string entry)
- {
- try
- {
- var firewallEntry = new CidrFirewallEntry(entry);
- _firewallQueue.Enqueue((firewallEntry, false));
- return firewallEntry;
- }
- catch (Exception e)
- {
- logger.Warning(e, "Failed to add firewall entry: {Pattern}", entry);
- return null;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void RequestAddEntry(IFirewallEntry entry)
- {
- _firewallQueue.Enqueue((entry, false));
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void RequestRemoveEntry(IFirewallEntry entry)
- {
- _firewallQueue.Enqueue((entry, true));
- }
-
- internal static void ProcessQueue()
- {
- while (_firewallQueue.TryDequeue(out var entry))
- {
- if (entry.Remove)
- {
- RemoveEntry(entry.FirewallyEntry);
- }
- else
- {
- AddEntry(entry.FirewallyEntry);
- }
- }
- }
+ public static SortedSet FirewallSet => _firewallSet;
internal static bool IsBlocked(IPAddress address)
{
@@ -128,22 +66,32 @@ internal static bool IsBlocked(IPAddress address)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void AddEntry(IFirewallEntry firewallEntry)
+ public static bool Add(IFirewallEntry firewallEntry)
{
- _firewallSet.Add(firewallEntry);
- _isBlockedCache.Clear();
+ if (_firewallSet.Add(firewallEntry))
+ {
+ _isBlockedCache.Clear();
+ return true;
+ }
+
+ return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void RemoveEntry(IFirewallEntry entry)
+ public static bool Remove(IFirewallEntry entry)
{
if (entry == null)
{
- return;
+ return false;
+ }
+
+ if (_firewallSet.Remove(entry))
+ {
+ _isBlockedCache.Clear();
+ return true;
}
- _firewallSet.Remove(entry);
- _isBlockedCache.Clear();
+ return false;
}
private class InternalValidationEntry : BaseFirewallEntry
diff --git a/Projects/Server/Network/IPLimiter.cs b/Projects/Server/Network/IPLimiter.cs
index 046ccd639e..dc4717645d 100644
--- a/Projects/Server/Network/IPLimiter.cs
+++ b/Projects/Server/Network/IPLimiter.cs
@@ -51,22 +51,32 @@ public static bool Verify(IPAddress ourAddress)
var now = Core.Now;
- CheckThrottledAddresses(now);
+ IPAccessLog accessLog;
+
+ while (_throttledAddresses.Count > 0)
+ {
+ accessLog = _throttledAddresses.Min;
+ if (now <= accessLog.Expiration)
+ {
+ break;
+ }
+
+ _throttledAddresses.Remove(accessLog);
+ }
_accessCheck.IPAddress = ourAddress;
- if (_connectionAttempts.TryGetValue(_accessCheck, out var accessLog))
+ if (_connectionAttempts.TryGetValue(_accessCheck, out accessLog))
{
_connectionAttempts.Remove(accessLog);
accessLog.Count++;
+ accessLog.Expiration = now + ConnectionAttemptsDuration;
if (now <= accessLog.Expiration && accessLog.Count >= MaxConnections)
{
- BlockConnection(now, accessLog);
+ _throttledAddresses.Add(accessLog);
return false;
}
-
- accessLog.Expiration = now + ConnectionAttemptsDuration;
}
else
{
@@ -79,26 +89,6 @@ public static bool Verify(IPAddress ourAddress)
return true;
}
- private static void BlockConnection(DateTime now, IPAccessLog accessLog)
- {
- accessLog.Expiration = now + ConnectionAttemptsDuration;
- _throttledAddresses.Add(accessLog);
- }
-
- private static void CheckThrottledAddresses(DateTime now)
- {
- while (_throttledAddresses.Count > 0)
- {
- var accessLog = _throttledAddresses.Min;
- if (now <= accessLog.Expiration)
- {
- break;
- }
-
- _throttledAddresses.Remove(accessLog);
- }
- }
-
private class IPAccessLog : IComparable
{
public IPAddress IPAddress;
diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs
index 51ef3eb0ef..172be42aaf 100755
--- a/Projects/Server/Network/NetState/NetState.cs
+++ b/Projects/Server/Network/NetState/NetState.cs
@@ -15,7 +15,6 @@
using Server.Accounting;
using Server.Collections;
-using Server.Diagnostics;
using Server.HuePickers;
using Server.Items;
using Server.Logging;
@@ -33,8 +32,6 @@
namespace Server.Network;
-public delegate void NetStateCreatedCallback(NetState ns);
-
public delegate void DecodePacket(Span buffer, ref int length);
public delegate int EncodePacket(ReadOnlySpan inputBuffer, Span outputBuffer);
@@ -57,8 +54,6 @@ public partial class NetState : IComparable, IValueLinkListNode _throttled = new(256);
private static readonly Queue _throttledPending = new(256);
- public static NetStateCreatedCallback CreatedCallback { get; set; }
-
private static readonly SortedSet _connecting = new(NetStateConnectingComparer.Instance);
private static readonly HashSet _instances = new(2048);
public static IReadOnlySet Instances => _instances;
@@ -69,8 +64,8 @@ public partial class NetState : IComparable, IValueLinkListNode= 0 and < 0x100)
{
+ _packetThrottles ??= new long[0x100];
_packetThrottles[packetID] = Core.TickCount;
}
}
- public long GetPacketTime(int packetID) => packetID is >= 0 and < 0x100 ? _packetThrottles[packetID] : 0;
+ public long GetPacketTime(int packetID) =>
+ packetID is >= 0 and < 0x100 && _packetThrottles != null ? _packetThrottles[packetID] : 0;
private void UpdatePacketCount(int packetID)
{
if (packetID is >= 0 and < 0x100)
{
+ _packetCounts ??= new long[0x100];
_packetCounts[packetID]++;
}
}
public int CheckPacketCounts()
{
+ if (_packetCounts == null)
+ {
+ return 0;
+ }
+
for (int i = 0; i < _packetCounts.Length; i++)
{
long count = _packetCounts[i];
@@ -455,14 +462,6 @@ public void Send(ReadOnlySpan span)
try
{
- PacketSendProfile prof = null;
-
- if (Core.Profiling)
- {
- prof = PacketSendProfile.Acquire(span[0]);
- prof.Start();
- }
-
if (_packetEncoder != null)
{
length = _packetEncoder(span, buffer);
@@ -484,8 +483,6 @@ public void Send(ReadOnlySpan span)
_flushPending.Enqueue(this);
_flushQueued = true;
}
-
- prof?.Finish();
}
catch (Exception ex)
{
@@ -594,7 +591,7 @@ public void HandleReceive(bool throttled = false)
if (newSeed == 0)
{
- HandleError(0, 0);
+ Disconnect(string.Empty);
return;
}
@@ -605,16 +602,16 @@ public void HandleReceive(bool throttled = false)
_parserState = ParserState.AwaitingNextPacket;
_protocolState = ProtocolState.GameServer_AwaitingGameServerLogin;
}
- else
+ else // Don't allow partial packets on initial connection, just disconnect them.
{
- _parserState = ParserState.AwaitingPartialPacket;
+ Disconnect(string.Empty);
}
break;
}
case ProtocolState.LoginServer_AwaitingLogin:
{
- if (packetId != 0xCF && packetId != 0x80)
+ if (packetId != 0x80)
{
LogInfo("Possible encrypted client detected, disconnecting...");
HandleError(packetId, packetLength);
@@ -662,7 +659,12 @@ public void HandleReceive(bool throttled = false)
case ProtocolState.GameServer_AwaitingGameServerLogin:
{
- if (packetId != 0x91 && packetId != 0x80)
+ if (packetId == 0x80)
+ {
+ goto case ProtocolState.LoginServer_AwaitingLogin;
+ }
+
+ if (packetId != 0x91)
{
HandleError(packetId, packetLength);
return;
@@ -804,14 +806,6 @@ private unsafe ParserState HandlePacket(SpanReader packetReader, byte packetId,
SetPacketTime(packetId);
}
- PacketReceiveProfile prof = null;
-
- if (Core.Profiling)
- {
- prof = PacketReceiveProfile.Acquire(packetId);
- prof?.Start();
- }
-
UpdatePacketCount(packetId);
if (PacketLogging)
@@ -826,8 +820,6 @@ private unsafe ParserState HandlePacket(SpanReader packetReader, byte packetId,
handler.OnReceive(this, new SpanReader(packetReader.Buffer.Slice(start, remainingLength)));
- prof?.Finish(packetLength);
-
return ParserState.AwaitingNextPacket;
}
@@ -938,7 +930,7 @@ private static void DisconnectUnattachedSockets()
var ns = _connecting.Min;
var socketTime = ns.ConnectedOn;
- // If the socket has been connected for less than 2 seconds, we can stop checking
+ // If the socket has been connected for less than the limit, we can stop checking
if (now - socketTime < ConnectingSocketIdleLimit)
{
break;
@@ -967,17 +959,6 @@ public static void Slice()
{
DisconnectUnattachedSockets();
- const int maxEntriesPerLoop = 32;
- var count = 0;
- while (++count <= maxEntriesPerLoop && TcpServer.ConnectedQueue.TryDequeue(out var ns))
- {
- CreatedCallback?.Invoke(ns);
-
- _instances.Add(ns);
- _connecting.Add(ns); // Add to the connecting set, and remove them when they authenticated.
- ns.LogInfo($"Connected. [{Instances.Count} Online]");
- }
-
while (_throttled.Count > 0)
{
var ns = _throttled.Dequeue();
@@ -993,7 +974,7 @@ public static void Slice()
_throttled.Enqueue(_throttledPending.Dequeue());
}
- count = _pollGroup.Poll(_polledStates);
+ var count = _pollGroup.Poll(_polledStates);
if (count > 0)
{
diff --git a/Projects/Server/Network/PingServer.cs b/Projects/Server/Network/PingServer.cs
index 31db1ad08e..41a59468b7 100644
--- a/Projects/Server/Network/PingServer.cs
+++ b/Projects/Server/Network/PingServer.cs
@@ -16,7 +16,6 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
-using System.Threading;
using Server.Logging;
namespace Server.Network;
@@ -47,8 +46,8 @@ public static void Start()
return;
}
- HashSet listeningAddresses = new HashSet();
- List listeners = new List();
+ HashSet listeningAddresses = [];
+ List listeners = [];
foreach (var serverIpep in ServerConfiguration.Listeners)
{
@@ -70,7 +69,7 @@ public static void Start()
}
listeners.Add(listener);
- new Thread(BeginAcceptingUdpRequest).Start(listener);
+ BeginAcceptingUdpRequest(listener);
}
foreach (var ipep in listeningAddresses)
@@ -140,4 +139,12 @@ private static async void BeginAcceptingUdpRequest(object state)
}
}
}
+
+ public static void Shutdown()
+ {
+ foreach (var listener in Listeners)
+ {
+ listener.Close();
+ }
+ }
}
diff --git a/Projects/Server/Network/TcpServer.cs b/Projects/Server/Network/TcpServer.cs
index 8ec399bd0f..be526f7038 100644
--- a/Projects/Server/Network/TcpServer.cs
+++ b/Projects/Server/Network/TcpServer.cs
@@ -13,8 +13,6 @@
* along with this program. If not, see . *
*************************************************************************/
-using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -22,7 +20,6 @@
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
-using System.Threading;
using Server.Logging;
using Server.Misc;
@@ -32,30 +29,52 @@ public static class TcpServer
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer));
- private const long ListenerErrorMessageDelay = 10000; // 10 seconds
-
- private static long _nextMaximumSocketsReachedMessage;
- private static readonly SemaphoreSlim _queueSemaphore = new(0);
- private static readonly ConcurrentQueue _connectingQueue = [];
- private static Thread _processConnectionsThread;
-
- // Sanity. 256 * 1024 * 4096 = ~1.3GB of ram
- public static int MaxConnections { get; set; }
+ // AccountLoginReject BadComm
+ private static readonly byte[] _socketRejected = [0x82, 0xFF];
public static IPEndPoint[] ListeningAddresses { get; private set; }
public static Socket[] Listeners { get; private set; }
- public static ConcurrentQueue ConnectedQueue { get; } = [];
-
- public static void Configure()
+ public static void Start()
{
- MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", 4096);
+ HashSet listeningAddresses = new HashSet();
+ List listeners = new List();
+ foreach (var ipep in ServerConfiguration.Listeners)
+ {
+ var listener = CreateListener(ipep);
+ if (listener == null)
+ {
+ continue;
+ }
+
+ if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
+ {
+ listeningAddresses.UnionWith(GetListeningAddresses(ipep));
+ }
+ else
+ {
+ listeningAddresses.Add(ipep);
+ }
+
+ listeners.Add(listener);
+ BeginAcceptingSockets(listener);
+ }
+
+ foreach (var ipep in listeningAddresses)
+ {
+ logger.Information("Listening: {Address}:{Port}", ipep.Address, ipep.Port);
+ }
+
+ ListeningAddresses = listeningAddresses.ToArray();
+ Listeners = listeners.ToArray();
}
- public static void Start()
+ public static void Shutdown()
{
- _processConnectionsThread = new Thread(ProcessConnections);
- _processConnectionsThread.Start();
+ foreach (var listener in Listeners)
+ {
+ listener.Close();
+ }
}
public static IEnumerable GetListeningAddresses(IPEndPoint ipep) =>
@@ -104,104 +123,48 @@ public static Socket CreateListener(IPEndPoint ipep)
return null;
}
- private static async void BeginAcceptingSockets(object state)
+ private static async void BeginAcceptingSockets(Socket listener)
{
- if (state is not Socket listener)
- {
- return;
- }
-
- var cancellationToken = Core.ClosingTokenSource.Token;
-
- while (!cancellationToken.IsCancellationRequested)
+ while (!Core.Closing)
{
+ Socket socket = null;
try
{
- var socket = await listener.AcceptAsync(cancellationToken);
- _connectingQueue.Enqueue(socket);
- _queueSemaphore.Release();
- }
- catch (OperationCanceledException)
- {
- return;
- }
- catch
- {
- // ignored
- }
- }
-
- listener.Close();
- }
-
- private static void ProcessConnections()
- {
- var cancellationToken = Core.ClosingTokenSource.Token;
- HashSet listeningAddresses = [];
- List listeners = [];
+ socket = await listener.AcceptAsync();
+ var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address;
- foreach (var ipep in ServerConfiguration.Listeners)
- {
- var listener = CreateListener(ipep);
- if (listener == null)
- {
- continue;
- }
-
- bool added;
-
- if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
- {
- var beforeCount = listeningAddresses.Count;
- listeningAddresses.UnionWith(GetListeningAddresses(ipep));
- added = listeningAddresses.Count > beforeCount;
- }
- else
- {
- added = listeningAddresses.Add(ipep);
- }
-
- if (added)
- {
- listeners.Add(listener);
-
- new Thread(BeginAcceptingSockets).Start(listener);
- }
- }
-
- foreach (var ipep in listeningAddresses)
- {
- logger.Information("Listening: {Address}:{Port}", ipep.Address, ipep.Port);
- }
-
- ListeningAddresses = listeningAddresses.ToArray();
- Listeners = listeners.ToArray();
-
- while (true)
- {
- try
- {
- while (!cancellationToken.IsCancellationRequested)
+ if (!IPLimiter.Verify(remoteIP))
{
- _queueSemaphore.Wait(cancellationToken);
-
- Firewall.ProcessQueue();
+ TraceDisconnect("Past IP limit threshold", remoteIP);
+ logger.Debug("{Address} Past IP limit threshold", remoteIP);
+ }
+ else if (Firewall.IsBlocked(remoteIP))
+ {
+ TraceDisconnect("Firewalled", remoteIP);
+ logger.Debug("{Address} Firewalled", remoteIP);
+ }
+ else
+ {
+ var args = new SocketConnectEventArgs(socket);
+ EventSink.InvokeSocketConnect(args);
- if (_connectingQueue.TryDequeue(out var socket))
+ if (args.AllowConnection)
{
- ProcessConnection(socket);
+ _ = new NetState(socket);
+ continue;
}
+
+ TraceDisconnect("Rejected by socket event handler", remoteIP);
+
+ // Reject the connection
+ socket.Send(_socketRejected, SocketFlags.None);
}
- return;
- }
- catch (OperationCanceledException)
- {
- return;
+ CloseSocket(socket);
}
- catch (Exception e)
+ catch
{
- logger.Error(e, "Error occurred in ProcessConnections");
+ CloseSocket(socket);
}
}
}
@@ -213,71 +176,9 @@ private static void CloseSocket(Socket socket)
{
socket.Shutdown(SocketShutdown.Both);
}
- catch
+ finally
{
- // ignored
- }
-
- socket.Close();
- }
-
- private static void ProcessConnection(Socket socket)
- {
- try
- {
- var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address;
-
- if (NetState.Instances.Count >= MaxConnections)
- {
- var ticks = Core.TickCount;
-
- if (ticks - _nextMaximumSocketsReachedMessage > 0)
- {
- if (socket.RemoteEndPoint is IPEndPoint ipep)
- {
- var ip = ipep.Address.ToString();
- logger.Warning("{Address} Failed (Maximum connections reached)", ip);
- }
-
- _nextMaximumSocketsReachedMessage = ticks + ListenerErrorMessageDelay;
- }
-
- CloseSocket(socket);
- return;
- }
-
- var firewalled = Firewall.IsBlocked(remoteIP);
- if (!firewalled)
- {
- var socketConnectedArgs = new SocketConnectedEventArgs(socket);
- EventSink.InvokeSocketConnected(socketConnectedArgs);
- firewalled = !socketConnectedArgs.ConnectionAllowed;
- }
-
- if (firewalled)
- {
- TraceDisconnect("Firewalled", remoteIP);
- logger.Debug("{Address} Firewalled", remoteIP);
-
- CloseSocket(socket);
- return;
- }
-
- if (!IPLimiter.Verify(remoteIP))
- {
- TraceDisconnect("Past IP limit threshold", remoteIP);
- logger.Debug("{Address} Past IP limit threshold", remoteIP);
-
- CloseSocket(socket);
- return;
- }
-
- var ns = new NetState(socket);
- ConnectedQueue.Enqueue(ns);
- }
- catch
- {
- // ignored
+ socket.Close();
}
}
@@ -299,22 +200,4 @@ private static void TraceDisconnect(string reason, IPAddress ip)
// ignored
}
}
-
- public static class EventSink
- {
- // IMPORTANT: This is executed asynchronously! Do not run any game thread code on these delegates!
- public static event Action SocketConnected;
-
- internal static void InvokeSocketConnected(SocketConnectedEventArgs context) =>
- SocketConnected?.Invoke(context);
- }
-
- public class SocketConnectedEventArgs
- {
- public Socket Socket { get; }
-
- public bool ConnectionAllowed { get; set; } = true;
-
- internal SocketConnectedEventArgs(Socket socket) => Socket = socket;
- }
}
diff --git a/Projects/Server/PropertyList/ObjectPropertyList.cs b/Projects/Server/PropertyList/ObjectPropertyList.cs
index ae3cf5ae92..5a8a94e975 100644
--- a/Projects/Server/PropertyList/ObjectPropertyList.cs
+++ b/Projects/Server/PropertyList/ObjectPropertyList.cs
@@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
- * Copyright 2019-2023 - ModernUO Development Team *
+ * Copyright 2019-2024 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ObjectPropertyList.cs *
* *
@@ -79,8 +79,9 @@ public void Reset()
_stringNumbersIndex = 0;
Header = 0;
HeaderArgs = null;
- STArrayPool.Shared.Return(_arrayToReturnToPool);
_pos = 0;
+
+ Dispose();
}
private void Flush()
@@ -499,13 +500,19 @@ private void GrowCore(uint requiredMinCapacity)
public void Dispose()
{
- STArrayPool.Shared.Return(_arrayToReturnToPool);
- _arrayToReturnToPool = null;
+ if (_arrayToReturnToPool != null)
+ {
+ STArrayPool.Shared.Return(_arrayToReturnToPool);
+ _arrayToReturnToPool = null;
+ }
}
~ObjectPropertyList()
{
- STArrayPool.Shared.Return(_arrayToReturnToPool);
- _arrayToReturnToPool = null;
+ if (_arrayToReturnToPool != null)
+ {
+ STArrayPool.Shared.Return(_arrayToReturnToPool);
+ _arrayToReturnToPool = null;
+ }
}
}
diff --git a/Projects/Server/Serialization/AdhocPersistence.cs b/Projects/Server/Serialization/AdhocPersistence.cs
index 332f3a83c1..b60e2482ab 100644
--- a/Projects/Server/Serialization/AdhocPersistence.cs
+++ b/Projects/Server/Serialization/AdhocPersistence.cs
@@ -55,8 +55,8 @@ public static void SerializeAndSnapshot(
{
var fullPath = PathUtility.GetFullPath(filePath, Core.BaseDirectory);
PathUtility.EnsureDirectory(Path.GetDirectoryName(fullPath));
- ConcurrentQueue types = [];
- var writer = new MemoryMapFileWriter(new FileStream(filePath, FileMode.Create), sizeHint, types);
+ HashSet typesSet = [];
+ var writer = new MemoryMapFileWriter(new FileStream(filePath, FileMode.Create), sizeHint, typesSet);
serializer(writer);
Task.Run(
@@ -67,14 +67,6 @@ public static void SerializeAndSnapshot(
writer.Dispose();
fs.Dispose();
- HashSet typesSet = [];
-
- // Dedupe the queue.
- foreach (var type in types)
- {
- typesSet.Add(type);
- }
-
Persistence.WriteSerializedTypesSnapshot(Path.GetDirectoryName(fullPath), typesSet);
},
Core.ClosingTokenSource.Token
diff --git a/Projects/Server/Serialization/BinaryFileReader.cs b/Projects/Server/Serialization/BinaryFileReader.cs
new file mode 100644
index 0000000000..f540f8e2e9
--- /dev/null
+++ b/Projects/Server/Serialization/BinaryFileReader.cs
@@ -0,0 +1,198 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2024 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: BinaryFileReader.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+using System.IO;
+using System.IO.MemoryMappedFiles;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Server;
+
+///
+/// Read bits of data from a serialized file in a managed environment.
+///
+/// Uses the following components collectively:
+///
+///
+///
+///
+///
+public sealed unsafe class BinaryFileReader : IDisposable, IGenericReader
+{
+ private readonly bool _usePrefixes;
+ private readonly MemoryMappedFile _mmf;
+ private readonly MemoryMappedViewStream _accessor;
+ private readonly UnmanagedDataReader _reader;
+
+ ///
+ /// Read bits of data from a serialized file in a managed environment.
+ ///
Encoding is UTF8 if left null.
+ ///
+ /// Uses the following components collectively:
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Full file path of the file to be deserialized.
+ /// Sets if strings should be read with itern.
+ /// Set an encoding. By default UTF8
+ public BinaryFileReader(string path, bool usePrefixes = true, Encoding encoding = null)
+ {
+ _usePrefixes = usePrefixes;
+ var fi = new FileInfo(path);
+
+ if (fi.Length > 0)
+ {
+ _mmf = MemoryMappedFile.CreateFromFile(path, FileMode.Open);
+ _accessor = _mmf.CreateViewStream();
+ byte* ptr = null;
+ _accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
+ _reader = new UnmanagedDataReader(ptr, _accessor.Length, encoding: encoding);
+ }
+ else
+ {
+ _reader = new UnmanagedDataReader(null, 0, encoding: encoding);
+ }
+ }
+
+ ///
+ /// How many bits deep into the file is the reader at currently.
+ ///
+ public long Position => _reader.Position;
+
+ public void Dispose()
+ {
+ _accessor?.SafeMemoryMappedViewHandle.ReleasePointer();
+ _accessor?.Dispose();
+ _mmf?.Dispose();
+ }
+
+ ///
+ /// If usePrefixes is true, return a ReadString(intern) else ReadStringRaw(intern).
+ ///
+ /// A string value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string ReadString(bool intern = false) => _usePrefixes ? _reader.ReadString(intern) : _reader.ReadStringRaw(intern);
+ ///
+ /// Returns the next set of bits that make up a string.
+ ///
+ /// Next string value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public string ReadStringRaw(bool intern = false) => _reader.ReadStringRaw(intern);
+ ///
+ /// Read the next 64 bits to make up a long (int64).
+ ///
+ /// Next long value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public long ReadLong() => _reader.ReadLong();
+ ///
+ /// Read the next 64 bits to make up an unsigned long (uint64).
+ ///
+ /// Next ulong value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ulong ReadULong() => _reader.ReadULong();
+ ///
+ /// Read the next 32 bits to make up an int (int32).
+ ///
+ /// Next int value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int ReadInt() => _reader.ReadInt();
+ ///
+ /// Read the next 32 bits to make up an unsigned int (uint32).
+ ///
+ /// Next uint value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public uint ReadUInt() => _reader.ReadUInt();
+ ///
+ /// Read the next 16 bits to make up a short (int16).
+ ///
+ /// Next short value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public short ReadShort() => _reader.ReadShort();
+ ///
+ /// Read the next 16 bits to make up an unsigned short (int16).
+ ///
+ /// Next ushort value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ushort ReadUShort() => _reader.ReadUShort();
+ ///
+ /// Read the next 8 bits to make up a float point double.
+ ///
+ /// Next double value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public double ReadDouble() => _reader.ReadDouble();
+ ///
+ /// Read the next 4 bits to make up a float point.
+ ///
+ /// Next float value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public float ReadFloat() => _reader.ReadFloat();
+ ///
+ /// Read the next 8 bits to make up a byte.
+ ///
+ /// Next byte value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public byte ReadByte() => _reader.ReadByte();
+ ///
+ /// Read the next 8 bits to make up a signed byte.
+ ///
+ /// Next sbyte value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public sbyte ReadSByte() => _reader.ReadSByte();
+ ///
+ /// Read the next 1 bit to make up a boolean.
+ ///
+ /// Next bool value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool ReadBool() => _reader.ReadBool();
+ ///
+ /// Read the next 32 bytes to make up a .
+ ///
+ /// Next uint value cast as a struct.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Serial ReadSerial() => _reader.ReadSerial();
+
+ ///
+ /// Reads the next Byte which helps determin how to read the following Type.
+ ///
If the byte returns 1 => and translate into a Type via the
+ ///
If the byte returns 2 =>
+ ///
else return null
+ ///
+ /// Next Type value
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Type ReadType() => _reader.ReadType();
+
+ ///
+ /// Reads the next set of bytes to fill the buffer.
+ ///
+ /// A reference span that will be filled with the next set of bytes.
+ /// The length of the buffer.
+ /// Thrown if the buffer is larger than the remaining data to read in the file.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int Read(Span buffer) => _reader.Read(buffer);
+
+ ///
+ /// Sets the current position of the stream to a specified value.
+ ///
+ /// The new position, relative to the parameter.
+ /// The reference point for the parameter. It can be one of the values of .
+ /// The new position in the stream, in bytes.
+ /// Thrown when the or the resulting position is out of the valid range.
+ /// Thrown if the stream does not support seeking.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public long Seek(long offset, SeekOrigin origin) => _reader.Seek(offset, origin);
+}
diff --git a/Projects/Server/Serialization/BufferWriter.cs b/Projects/Server/Serialization/BufferWriter.cs
index 2d425ba1be..d3be1f0508 100644
--- a/Projects/Server/Serialization/BufferWriter.cs
+++ b/Projects/Server/Serialization/BufferWriter.cs
@@ -105,13 +105,7 @@ public void Resize(int size)
_buffer = newBuffer;
}
- public virtual void Flush()
- {
- // Need to avoid buffer.Length = 2, buffer * 2 is 4, but we need 8 or 16bytes, causing an exception.
- // The least we need is 16bytes + Index, but we use BufferSize since it should always be big enough for a single
- // non-dynamic field.
- Resize(Math.Max(BufferSize, _buffer.Length * 2));
- }
+ public virtual void Flush() => Resize(Math.Clamp(_buffer.Length * 2, BufferSize, _buffer.Length + 1024 * 1024 * 64));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void FlushIfNeeded(int amount)
diff --git a/Projects/Server/Serialization/GenericEntityPersistence.cs b/Projects/Server/Serialization/GenericEntityPersistence.cs
index 28a9696da6..54617198f4 100644
--- a/Projects/Server/Serialization/GenericEntityPersistence.cs
+++ b/Projects/Server/Serialization/GenericEntityPersistence.cs
@@ -14,7 +14,6 @@
*************************************************************************/
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -32,141 +31,115 @@ public interface IGenericEntityPersistence
public void DeserializeIndexes(string savePath, Dictionary typesDb);
}
-public class GenericEntityPersistence : Persistence, IGenericEntityPersistence where T : class, ISerializable
+public class GenericEntityPersistence : GenericPersistence, IGenericEntityPersistence where T : class, ISerializable
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(GenericEntityPersistence));
- private static List>[] _entities;
- private long _initialIdxSize = 1024 * 256;
- private long _initialBinSize = 1024 * 1024;
- private readonly string _name;
- private readonly uint _minSerial;
- private readonly uint _maxSerial;
+ // Support legacy split file serialization
+ private static Dictionary>> _entities;
+
+ private readonly Serial _minSerial;
+ private readonly Serial _maxSerial;
private Serial _lastEntitySerial;
private readonly Dictionary _pendingAdd = new();
private readonly Dictionary _pendingDelete = new();
- private readonly uint[] _entitiesCount = new uint[World.GetThreadWorkerCount()];
-
- private readonly (MemoryMapFileWriter idxWriter, MemoryMapFileWriter binWriter)[] _writers =
- new (MemoryMapFileWriter, MemoryMapFileWriter)[World.GetThreadWorkerCount()];
-
public Dictionary EntitiesBySerial { get; } = new();
- public GenericEntityPersistence(string name, int priority, uint minSerial, uint maxSerial) : base(priority)
+ public GenericEntityPersistence(string name, int priority, uint minSerial, uint maxSerial) : this(
+ name,
+ priority,
+ (Serial)minSerial,
+ (Serial)maxSerial
+ )
{
- _name = name;
- _minSerial = minSerial;
- _maxSerial = maxSerial;
- _lastEntitySerial = (Serial)(minSerial - 1);
- typeof(T).RegisterFindEntity(Find);
}
- public override void Preserialize(string savePath, ConcurrentQueue types)
+ public GenericEntityPersistence(string name, int priority, Serial minSerial, Serial maxSerial) : base(name, priority)
{
- var path = Path.Combine(savePath, _name);
- PathUtility.EnsureDirectory(path);
-
- var threadCount = World.GetThreadWorkerCount();
- for (var i = 0; i < threadCount; i++)
- {
- var idxPath = Path.Combine(path, $"{_name}_{i}.idx");
- var binPath = Path.Combine(path, $"{_name}_{i}.bin");
-
- _writers[i] = (
- new MemoryMapFileWriter(new FileStream(idxPath, FileMode.Create), _initialIdxSize, types),
- new MemoryMapFileWriter(new FileStream(binPath, FileMode.Create), _initialBinSize, types)
- );
-
- _writers[i].idxWriter.Write(3); // version
- _writers[i].idxWriter.Seek(4, SeekOrigin.Current); // Entity count
-
- _entitiesCount[i] = 0;
- }
+ _minSerial = minSerial;
+ _maxSerial = maxSerial;
+ _lastEntitySerial = minSerial - 1;
+ typeof(T).RegisterFindEntity(Find);
}
- public override void Serialize(IGenericSerializable e, int threadIndex)
+ public override void WriteSnapshot(string savePath, HashSet typeSet)
{
- var (idx, bin) = _writers[threadIndex];
- var pos = bin.Position;
-
- var entity = (ISerializable)e;
+ var dir = Path.Combine(savePath, Name);
+ PathUtility.EnsureDirectory(dir);
- entity.Serialize(bin);
- var length = (uint)(bin.Position - pos);
+ var threads = World._threadWorkers;
- var t = entity.GetType();
- idx.Write(t);
- idx.Write(entity.Serial);
- idx.Write(entity.Created.Ticks);
- idx.Write(pos);
- idx.Write(length);
+ using var binFs = new FileStream(Path.Combine(dir, $"{Name}.bin"), FileMode.Create, FileAccess.Write, FileShare.None);
+ using var idxFs = new FileStream(Path.Combine(dir, $"{Name}.idx"), FileMode.Create);
+ using var idx = new MemoryMapFileWriter(idxFs, 1024 * 1024, typeSet); // 1MB
- _entitiesCount[threadIndex]++;
- }
+ var binPosition = 0L;
- public override void WriteSnapshot()
- {
- var wroteFile = false;
- string folderPath = null;
- for (int i = 0; i < _writers.Length; i++)
+ // Support for non-entity generic serialization.
+ if (SerializedLength > 0)
{
- var (idxWriter, binWriter) = _writers[i];
-
- var binBytesWritten = binWriter.Position;
-
- // Write the entity count
- var pos = idxWriter.Position;
- idxWriter.Seek(4, SeekOrigin.Begin);
- idxWriter.Write(_entitiesCount[i]);
- idxWriter.Seek(pos, SeekOrigin.Begin);
-
- var idxFs = idxWriter.FileStream;
- var idxFilePath = idxFs.Name;
- var binFs = binWriter.FileStream;
- var binFilePath = binFs.Name;
-
- if (_initialIdxSize < idxFs.Position)
+ try
{
- _initialIdxSize = idxFs.Position;
+ binFs.Write(threads[SerializedThread].GetHeap(SerializedPosition, SerializedLength));
}
-
- if (_initialBinSize < binFs.Position)
+ catch (Exception error)
{
- _initialBinSize = binFs.Position;
+ logger.Error(
+ error,
+ "Error writing entity: (Thread: {Thread} - {Start} {Length})",
+ SerializedThread,
+ SerializedPosition,
+ SerializedLength
+ );
}
- idxWriter.Dispose();
- binWriter.Dispose();
+ binPosition += SerializedLength;
+ }
+
+ idx.Write(3); // Version
+ idx.Write(EntitiesBySerial.Values.Count);
- idxFs.Dispose();
- binFs.Dispose();
+ foreach (var e in EntitiesBySerial.Values)
+ {
+ var thread = e.SerializedThread;
+ var heapStart = e.SerializedPosition;
+ var heapLength = e.SerializedLength;
- if (binBytesWritten > 1)
+ idx.Write(e.GetType());
+ idx.Write(e.Serial);
+ idx.Write(e.Created.Ticks);
+ idx.Write(binPosition);
+ idx.Write(heapLength);
+
+ try
{
- wroteFile = true;
+ binFs.Write(threads[thread].GetHeap(heapStart, heapLength));
}
- else
+ catch (Exception error)
{
- File.Delete(idxFilePath);
- File.Delete(binFilePath);
- folderPath = Path.GetDirectoryName(idxFilePath);
+ logger.Error(
+ error,
+ "Error writing entity: {Entity} (Thread: {Thread} - {Start} {Length})",
+ e,
+ thread,
+ heapStart,
+ heapLength
+ );
}
- }
- if (!wroteFile && folderPath != null)
- {
- Directory.Delete(folderPath);
+ binPosition += heapLength;
}
}
public override void Serialize()
{
- World.ResetRoundRobin();
foreach (var entity in EntitiesBySerial.Values)
{
- World.PushToCache((entity, this));
+ World.PushToCache(entity);
}
+
+ World.PushToCache(this);
}
private static ConstructorInfo GetConstructorFor(string typeName, Type t, Type[] constructorTypes)
@@ -205,7 +178,7 @@ private static ConstructorInfo GetConstructorFor(string typeName, Type t, Type[]
*/
private unsafe Dictionary ReadTypes(string savePath)
{
- string typesPath = Path.Combine(savePath, _name, $"{_name}.tdb");
+ string typesPath = Path.Combine(savePath, Name, $"{Name}.tdb");
if (!File.Exists(typesPath))
{
return null;
@@ -236,59 +209,57 @@ private unsafe Dictionary ReadTypes(string savePath)
public virtual void DeserializeIndexes(string savePath, Dictionary typesDb)
{
- string indexPath = Path.Combine(savePath, _name, $"{_name}.idx");
+ string indexPath = Path.Combine(savePath, Name, $"{Name}.idx");
+
+ _entities ??= [];
+
+ // Support for legacy MUO Serialization that used split files
if (!File.Exists(indexPath))
{
- TryDeserializeMultithreadIndexes(savePath, typesDb);
+ TryDeserializeSplitFileIndexes(savePath, typesDb);
return;
}
- _entities = [InternalDeserializeIndexes(indexPath, typesDb)];
+ InternalDeserializeIndexes(indexPath, typesDb, _entities[0] = []);
}
- private void TryDeserializeMultithreadIndexes(string savePath, Dictionary typesDb)
+ private void TryDeserializeSplitFileIndexes(string savePath, Dictionary typesDb)
{
var index = 0;
- var fileList = new List();
while (true)
{
- var path = Path.Combine(savePath, _name, $"{_name}_{index}.idx");
+ var path = Path.Combine(savePath, Name, $"{Name}_{index}.idx");
var fi = new FileInfo(path);
if (!fi.Exists)
{
break;
}
- if (fi.Length != 0)
+ if (fi.Length == 0)
{
- fileList.Add(path);
+ continue;
}
+ InternalDeserializeIndexes(path, typesDb, _entities[index] = []);
index++;
}
-
- _entities = new List>[fileList.Count];
- for (var i = 0; i < fileList.Count; i++)
- {
- _entities[i] = InternalDeserializeIndexes(fileList[i], typesDb);
- }
}
- private unsafe List> InternalDeserializeIndexes(string filePath, Dictionary typesDb)
+ private unsafe void InternalDeserializeIndexes(
+ string filePath, Dictionary typesDb, List> entities
+ )
{
- object[] ctorArgs = new object[1];
- List> entities = [];
-
using var mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open);
using var accessor = mmf.CreateViewStream();
byte* ptr = null;
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
- UnmanagedDataReader dataReader = new UnmanagedDataReader(ptr, accessor.Length, typesDb);
+ var dataReader = new UnmanagedDataReader(ptr, accessor.Length);
var version = dataReader.ReadInt();
- Dictionary ctors = null;
+ Dictionary ctors = [];
+
if (version < 2)
{
ctors = ReadTypes(Path.GetDirectoryName(filePath));
@@ -296,15 +267,16 @@ private unsafe List> InternalDeserializeIndexes(string filePath, D
if (typesDb == null && ctors == null)
{
- return entities;
+ return;
}
- int count = dataReader.ReadInt();
-
var now = DateTime.UtcNow;
+ var ctorArgs = new object[1];
Type[] ctorArguments = [typeof(Serial)];
- for (int i = 0; i < count; ++i)
+ var count = dataReader.ReadInt();
+
+ for (var i = 0; i < count; ++i)
{
ConstructorInfo ctor;
// Version 2 & 3 with SerializedTypes.db
@@ -331,6 +303,7 @@ private unsafe List> InternalDeserializeIndexes(string filePath, D
{
dataReader.ReadLong(); // LastSerialized
}
+
var pos = dataReader.ReadLong();
var length = dataReader.ReadInt();
@@ -344,45 +317,39 @@ private unsafe List> InternalDeserializeIndexes(string filePath, D
if (ctor.Invoke(ctorArgs) is T entity)
{
entity.Created = created;
- entities.Add(new EntitySpan(entity, pos, (int)length));
+ entities.Add(new EntitySpan(entity, pos, length));
EntitiesBySerial[serial] = entity;
}
}
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- entities.TrimExcess();
if (EntitiesBySerial.Count > 0)
{
_lastEntitySerial = EntitiesBySerial.Keys.Max();
}
-
- return entities;
}
public override void Deserialize(string savePath, Dictionary typesDb)
{
- string dataPath = Path.Combine(savePath, _name, $"{_name}.bin");
+ string dataPath = Path.Combine(savePath, Name, $"{Name}.bin");
var fi = new FileInfo(dataPath);
if (!fi.Exists)
{
TryDeserializeMultithread(savePath, typesDb);
}
- else
+ else if (fi.Length > 0)
{
- if (fi.Length == 0)
- {
- return;
- }
-
InternalDeserialize(dataPath, 0, typesDb);
}
+ _entities.Clear();
+ _entities.TrimExcess();
_entities = null;
}
- private static unsafe void InternalDeserialize(string filePath, int index, Dictionary typesDb)
+ private unsafe void InternalDeserialize(string filePath, int index, Dictionary typesDb)
{
using var mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open);
using var accessor = mmf.CreateViewStream();
@@ -390,6 +357,9 @@ private static unsafe void InternalDeserialize(string filePath, int index, Dicti
byte* ptr = null;
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
UnmanagedDataReader dataReader = new UnmanagedDataReader(ptr, accessor.Length, typesDb);
+
+ Deserialize(dataReader);
+
var deleteAllFailures = false;
foreach (var entry in _entities[index])
@@ -460,15 +430,25 @@ private void TryDeserializeMultithread(string savePath, Dictionary max)
{
- last = (Serial)_minSerial;
+ last = min;
}
- if (FindEntity(last) == null)
+ if (FindEntity((Serial)last) == null)
{
- return _lastEntitySerial = last;
+ return _lastEntitySerial = (Serial)last;
}
}
- OutOfMemory($"No serials left to allocate for {_name}");
+ OutOfMemory($"No serials left to allocate for {Name}");
return Serial.MinusOne;
}
}
@@ -530,17 +511,21 @@ public void AddEntity(T entity)
goto case WorldState.Loading;
}
case WorldState.Loading:
+ case WorldState.WritingSave:
{
if (_pendingDelete.Remove(entity.Serial))
{
- logger.Warning("Deleted then added {Entity} during {WorldState} state.", entity.GetType().Name, worldState.ToString());
+ logger.Warning(
+ "Deleted then added {Entity} during {WorldState} state.",
+ entity.GetType().Name,
+ worldState.ToString()
+ );
}
_pendingAdd[entity.Serial] = entity;
break;
}
case WorldState.PendingSave:
- case WorldState.WritingSave:
case WorldState.Running:
{
ref var entityEntry = ref CollectionsMarshal.GetValueRefOrAddDefault(EntitiesBySerial, entity.Serial, out bool exists);
@@ -591,13 +576,13 @@ public void RemoveEntity(T entity)
goto case WorldState.Loading;
}
case WorldState.Loading:
+ case WorldState.WritingSave:
{
_pendingAdd.Remove(entity.Serial);
_pendingDelete[entity.Serial] = entity;
break;
}
case WorldState.PendingSave:
- case WorldState.WritingSave:
case WorldState.Running:
{
EntitiesBySerial.Remove(entity.Serial);
@@ -613,8 +598,6 @@ private void ProcessSafetyQueues()
AddEntity(entity);
}
- _pendingAdd.Clear();
-
foreach (var entity in _pendingDelete.Values)
{
if (_pendingAdd.ContainsKey(entity.Serial))
@@ -625,10 +608,11 @@ private void ProcessSafetyQueues()
RemoveEntity(entity);
}
+ _pendingAdd.Clear();
_pendingDelete.Clear();
}
- private void AppendSafetyLog(string action, ISerializable entity)
+ private static void AppendSafetyLog(string action, ISerializable entity)
{
var message =
$"Warning: Attempted to {{Action}} {{Entity}} during world save.{Environment.NewLine}This action could cause inconsistent state.{Environment.NewLine}It is strongly advised that the offending scripts be corrected.";
@@ -638,7 +622,7 @@ private void AppendSafetyLog(string action, ISerializable entity)
try
{
using var op = new StreamWriter("world-save-errors.log", true);
- op.WriteLine("{0}\t{1}", DateTime.UtcNow, message);
+ op.WriteLine($"{DateTime.UtcNow}\t{message}");
op.WriteLine(new StackTrace(2).ToString());
op.WriteLine();
}
@@ -649,17 +633,9 @@ private void AppendSafetyLog(string action, ISerializable entity)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public T Find(Serial serial) => FindEntity(serial, false, false);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public T Find(Serial serial, bool returnDeleted) => FindEntity(serial, returnDeleted, false);
+ public T Find(Serial serial, bool returnDeleted = false) => FindEntity(serial, returnDeleted);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public T Find(Serial serial, bool returnDeleted, bool returnPending) => FindEntity(serial, returnDeleted, returnPending);
-
- public R FindEntity(Serial serial) where R : class, T => FindEntity(serial, false, false);
-
- public R FindEntity(Serial serial, bool returnDeleted, bool returnPending) where R : class, T
+ public R FindEntity(Serial serial, bool returnDeleted = false) where R : class, T
{
switch (World.WorldState)
{
@@ -669,14 +645,14 @@ public R FindEntity(Serial serial, bool returnDeleted, bool returnPending) wh
}
case WorldState.Loading:
case WorldState.Saving:
+ case WorldState.WritingSave:
{
- if (returnDeleted && returnPending && _pendingDelete.TryGetValue(serial, out var entity))
+ if (returnDeleted && _pendingDelete.TryGetValue(serial, out var entity))
{
return entity as R;
}
- if (returnPending && _pendingAdd.TryGetValue(serial, out entity) ||
- EntitiesBySerial.TryGetValue(serial, out entity))
+ if (_pendingAdd.TryGetValue(serial, out entity) || EntitiesBySerial.TryGetValue(serial, out entity))
{
return entity as R;
}
@@ -684,7 +660,6 @@ public R FindEntity(Serial serial, bool returnDeleted, bool returnPending) wh
return null;
}
case WorldState.PendingSave:
- case WorldState.WritingSave:
case WorldState.Running:
{
return EntitiesBySerial.TryGetValue(serial, out var entity) ? entity as R : null;
diff --git a/Projects/Server/Serialization/GenericPersistence.cs b/Projects/Server/Serialization/GenericPersistence.cs
index a584b76a3e..a89e191411 100644
--- a/Projects/Server/Serialization/GenericPersistence.cs
+++ b/Projects/Server/Serialization/GenericPersistence.cs
@@ -14,65 +14,105 @@
*************************************************************************/
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
+using System.IO.MemoryMappedFiles;
namespace Server;
public abstract class GenericPersistence : Persistence, IGenericSerializable
{
- private long _initialSize = 1024 * 1024;
- private MemoryMapFileWriter _fileToSave;
-
public string Name { get; }
+ public string SaveFilePath { get; protected set; } // "/.bin"
- public GenericPersistence(string name, int priority) : base(priority) => Name = name;
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
- public override void Preserialize(string savePath, ConcurrentQueue types)
+ public GenericPersistence(string name, int priority) : base(priority)
{
- var path = Path.Combine(savePath, Name);
- var filePath = Path.Combine(path, $"{Name}.bin");
- PathUtility.EnsureDirectory(path);
-
- _fileToSave = new MemoryMapFileWriter(new FileStream(filePath, FileMode.Create), _initialSize, types);
+ Name = name;
+ SaveFilePath = Path.Combine(Name, $"{Name}.bin");
}
public override void Serialize()
{
- World.ResetRoundRobin();
- World.PushToCache((this, this));
+ World.PushToCache(this);
}
- public override void WriteSnapshot()
+ public override void WriteSnapshot(string savePath, HashSet typeSet)
{
- string folderPath = null;
- using (var fs = _fileToSave.FileStream)
+ if (SerializedLength == 0)
{
- if (fs.Position > _initialSize)
- {
- _initialSize = fs.Position;
- }
+ return;
+ }
- _fileToSave.Dispose();
- if (_fileToSave.Position == 0)
- {
- folderPath = Path.GetDirectoryName(fs.Name);
- }
+ var file = Path.Combine(savePath, SaveFilePath);
+ var dir = Path.GetDirectoryName(file);
+ PathUtility.EnsureDirectory(dir);
+
+ var threads = World._threadWorkers;
+
+ using var binFs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
+
+ var thread = SerializedThread;
+ var heapStart = SerializedPosition;
+ var heapLength = SerializedLength;
+
+ binFs.Write(threads[thread].GetHeap(heapStart, heapLength));
+ }
+
+ public override unsafe void Deserialize(string savePath, Dictionary typesDb)
+ {
+ // Assume savePath has the Core.BaseDirectory already prepended
+ var dataPath = Path.GetFullPath(SaveFilePath, savePath);
+ var file = new FileInfo(dataPath);
+
+ if (!file.Exists || file.Length <= 0)
+ {
+ return;
}
- if (folderPath != null)
+ var fileLength = file.Length;
+
+ string error;
+
+ try
+ {
+ using var mmf = MemoryMappedFile.CreateFromFile(dataPath, FileMode.Open);
+ using var accessor = mmf.CreateViewStream();
+
+ byte* ptr = null;
+ accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
+ var dataReader = new UnmanagedDataReader(ptr, accessor.Length, typesDb);
+ Deserialize(dataReader);
+
+ error = dataReader.Position != fileLength
+ ? $"Serialized {fileLength} bytes, but {dataReader.Position} bytes deserialized"
+ : null;
+
+ accessor.SafeMemoryMappedViewHandle.ReleasePointer();
+ }
+ catch (Exception e)
{
- Directory.Delete(folderPath);
+ error = e.ToString();
}
- }
- public override void Serialize(IGenericSerializable e, int threadIndex) => Serialize(_fileToSave);
+ if (error != null)
+ {
+ Console.WriteLine($"***** Bad deserialize of {file.FullName} *****");
+ Console.WriteLine(error);
- public abstract void Serialize(IGenericWriter writer);
+ Console.Write("Skip this file and continue? (y/n): ");
+ var y = Console.ReadLine();
- public override void Deserialize(string savePath, Dictionary typesDb) =>
- AdhocPersistence.Deserialize(Path.Combine(savePath, Name, $"{Name}.bin"), Deserialize);
+ if (!y.InsensitiveEquals("y"))
+ {
+ throw new Exception("Deserialization failed.");
+ }
+ }
+ }
+ public abstract void Serialize(IGenericWriter writer);
public abstract void Deserialize(IGenericReader reader);
}
diff --git a/Projects/Server/World/IGenericSerializable.cs b/Projects/Server/Serialization/IGenericSerializable.cs
similarity index 90%
rename from Projects/Server/World/IGenericSerializable.cs
rename to Projects/Server/Serialization/IGenericSerializable.cs
index 02e6487afd..ecc54042de 100644
--- a/Projects/Server/World/IGenericSerializable.cs
+++ b/Projects/Server/Serialization/IGenericSerializable.cs
@@ -17,5 +17,9 @@ namespace Server;
public interface IGenericSerializable
{
+ byte SerializedThread { get; set; }
+ int SerializedPosition { get; set; }
+ int SerializedLength { get; set; }
+
void Serialize(IGenericWriter writer);
}
diff --git a/Projects/Server/Serialization/MemoryMapFileWriter.cs b/Projects/Server/Serialization/MemoryMapFileWriter.cs
index e5de0849fe..67de0a4ab6 100644
--- a/Projects/Server/Serialization/MemoryMapFileWriter.cs
+++ b/Projects/Server/Serialization/MemoryMapFileWriter.cs
@@ -15,7 +15,7 @@
using System;
using System.Buffers.Binary;
-using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
@@ -29,7 +29,7 @@ public unsafe class MemoryMapFileWriter : IGenericWriter, IDisposable
{
private readonly Encoding _encoding;
- private readonly ConcurrentQueue _types;
+ private readonly HashSet _types;
private readonly FileStream _fileStream;
private MemoryMappedFile _mmf;
private MemoryMappedViewAccessor _accessor;
@@ -37,7 +37,7 @@ public unsafe class MemoryMapFileWriter : IGenericWriter, IDisposable
private long _position;
private long _size;
- public MemoryMapFileWriter(FileStream fileStream, long initialSize, ConcurrentQueue types = null)
+ public MemoryMapFileWriter(FileStream fileStream, long initialSize, HashSet types = null)
{
_types = types;
_fileStream = fileStream;
@@ -244,7 +244,7 @@ public void Write(Type type)
{
Write((byte)0x2); // xxHash3 64bit
Write(AssemblyHandler.GetTypeHash(type));
- _types.Enqueue(type);
+ _types.Add(type);
}
}
diff --git a/Projects/Server/Serialization/Persistence.cs b/Projects/Server/Serialization/Persistence.cs
index d98f6551c7..3c41c564e6 100644
--- a/Projects/Server/Serialization/Persistence.cs
+++ b/Projects/Server/Serialization/Persistence.cs
@@ -14,7 +14,6 @@
*************************************************************************/
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
@@ -53,7 +52,7 @@ public static void Load(string path)
}
}
- private unsafe static Dictionary LoadTypes(string path)
+ private static unsafe Dictionary LoadTypes(string path)
{
var db = new Dictionary();
@@ -85,32 +84,31 @@ private unsafe static Dictionary LoadTypes(string path)
}
// Note: This is strictly on a background thread
- internal static void PreSerializeAll(string path, ConcurrentQueue types)
+ internal static void WriteSnapshotAll(string path, HashSet typeSet)
{
foreach (var p in _registry)
{
- p.Preserialize(path, types);
+ p.WriteSnapshot(path, typeSet);
}
- }
- private static readonly HashSet _typesSet = [];
+ WriteSerializedTypesSnapshot(path, typeSet);
+ }
- // Note: This is strictly on a background thread
- internal static void WriteSnapshotAll(string path, ConcurrentQueue types)
+ public static void WriteSerializedTypesSnapshot(string path, HashSet types)
{
- foreach (var p in _registry)
- {
- p.WriteSnapshot();
- }
+ string typesPath = Path.Combine(path, "SerializedTypes.db");
+ using var fs = new FileStream(typesPath, FileMode.Create);
+ using var writer = new MemoryMapFileWriter(fs, 1024 * 1024 * 4);
+
+ writer.Write(0); // version
+ writer.Write(types.Count);
- // Dedupe the queue.
foreach (var type in types)
{
- _typesSet.Add(type);
+ var fullName = type.FullName;
+ writer.Write(HashUtility.ComputeHash64(fullName));
+ writer.WriteStringRaw(fullName);
}
-
- WriteSerializedTypesSnapshot(path, _typesSet);
- _typesSet.Clear();
}
internal static void SerializeAll()
@@ -137,32 +135,8 @@ internal static void PostDeserializeAll()
}
}
- public static void WriteSerializedTypesSnapshot(string path, HashSet types)
- {
- string typesPath = Path.Combine(path, "SerializedTypes.db");
- using var fs = new FileStream(typesPath, FileMode.Create);
- using var writer = new MemoryMapFileWriter(fs, 1024 * 1024 * 4);
-
- writer.Write(0); // version
- writer.Write(types.Count);
-
- foreach (var type in types)
- {
- var fullName = type.FullName;
- writer.Write(HashUtility.ComputeHash64(fullName));
- writer.WriteStringRaw(fullName);
- }
- }
-
- // Open file streams, MMFs, prepare data structures
- // Note: This should only be run on a background thread
- public abstract void Preserialize(string savePath, ConcurrentQueue types);
-
- // Note: This should only be run on a background thread
- public abstract void Serialize(IGenericSerializable e, int threadIndex);
-
// Note: This should only be run on a background thread
- public abstract void WriteSnapshot();
+ public abstract void WriteSnapshot(string savePath, HashSet typeSet);
public abstract void Serialize();
diff --git a/Projects/Server/Serialization/SerializationExtensions.cs b/Projects/Server/Serialization/SerializationExtensions.cs
index 0e89cbca36..198837dc0a 100644
--- a/Projects/Server/Serialization/SerializationExtensions.cs
+++ b/Projects/Server/Serialization/SerializationExtensions.cs
@@ -21,10 +21,10 @@ namespace Server;
public static class SerializationExtensions
{
- private static readonly Dictionary> _directFinderTable = new();
- private static readonly Dictionary> _searchTable = new();
+ private static readonly Dictionary> _directFinderTable = new();
+ private static readonly Dictionary> _searchTable = new();
- public static void RegisterFindEntity(this Type type, Func func)
+ public static void RegisterFindEntity(this Type type, Func func)
{
_searchTable[type] = func;
}
@@ -47,12 +47,12 @@ public static T ReadEntity(this IGenericReader reader) where T : class, ISeri
if (typeof(IEntity).IsAssignableFrom(typeT))
{
- return World.FindEntity(serial, returnPending: false) as T;
+ return World.FindEntity(serial) as T;
}
if (_directFinderTable.TryGetValue(typeT, out var finder))
{
- return finder(serial, false, false) as T;
+ return finder(serial, false) as T;
}
Type type = null;
@@ -87,7 +87,7 @@ public static T ReadEntity(this IGenericReader reader) where T : class, ISeri
finder = _searchTable[type];
_directFinderTable[type] = finder;
- return finder(serial, false, false) as T;
+ return finder(serial, false) as T;
}
public static List ReadEntityList(
diff --git a/Projects/Server/Serialization/SerializationThreadWorker.cs b/Projects/Server/Serialization/SerializationThreadWorker.cs
new file mode 100644
index 0000000000..d01dca4704
--- /dev/null
+++ b/Projects/Server/Serialization/SerializationThreadWorker.cs
@@ -0,0 +1,113 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2024 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: SerializationThreadWorker.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace Server;
+
+public class SerializationThreadWorker
+{
+ private const int MinHeapSize = 1024 * 1024; // 1MB
+ private readonly int _index;
+ private readonly Thread _thread;
+ private readonly AutoResetEvent _startEvent; // Main thread tells the thread to start working
+ private readonly AutoResetEvent _stopEvent; // Main thread waits for the worker finish draining
+ private bool _pause;
+ private bool _exit;
+ private byte[] _heap;
+
+ private readonly ConcurrentQueue _entities;
+
+ public SerializationThreadWorker(int index)
+ {
+ _index = index;
+ _startEvent = new AutoResetEvent(false);
+ _stopEvent = new AutoResetEvent(false);
+ _entities = new ConcurrentQueue();
+ _thread = new Thread(Execute);
+ _thread.Start(this);
+ }
+
+ public void Wake()
+ {
+ _startEvent.Set();
+ }
+
+ public void Sleep()
+ {
+ Volatile.Write(ref _pause, true);
+ _stopEvent.WaitOne();
+ }
+
+ public void Exit()
+ {
+ _exit = true;
+ Wake();
+ Sleep();
+ }
+
+ public void AllocateHeap() => _heap ??= GC.AllocateUninitializedArray(MinHeapSize); // 1MB
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Push(IGenericSerializable entity) => _entities.Enqueue(entity);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan GetHeap(int start, int length) => _heap.AsSpan(start, length);
+
+ private static void Execute(object obj)
+ {
+ var worker = (SerializationThreadWorker)obj;
+ var threadIndex = (byte)worker._index;
+
+ var queue = worker._entities;
+ var serializedTypes = World.SerializedTypes;
+
+ while (worker._startEvent.WaitOne())
+ {
+ var writer = new BufferWriter(worker._heap, true, serializedTypes);
+
+ while (true)
+ {
+ var pauseRequested = Volatile.Read(ref worker._pause);
+ if (queue.TryDequeue(out var e))
+ {
+ e.SerializedThread = threadIndex;
+ var start = e.SerializedPosition = (int)writer.Position;
+ e.Serialize(writer);
+ e.SerializedLength = (int)(writer.Position - start);
+ }
+ else if (pauseRequested) // Break when finished
+ {
+ break;
+ }
+ }
+
+ worker._heap = writer.Buffer;
+
+ writer.Close();
+
+ worker._stopEvent.Set(); // Allow the main thread to continue now that we are finished
+ worker._pause = false;
+
+ if (Core.Closing || worker._exit)
+ {
+ return;
+ }
+ }
+ }
+}
diff --git a/Projects/Server/Serialization/UnmanagedDataReader.cs b/Projects/Server/Serialization/UnmanagedDataReader.cs
index ed9675abb5..9babb86fda 100644
--- a/Projects/Server/Serialization/UnmanagedDataReader.cs
+++ b/Projects/Server/Serialization/UnmanagedDataReader.cs
@@ -25,19 +25,31 @@
namespace Server;
+///
+/// Read bits of data raw from a serialized file using Little-endian.
+///
public unsafe class UnmanagedDataReader : IGenericReader
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(UnmanagedDataReader));
private readonly byte* _ptr;
- private long _position;
private readonly long _size;
private readonly Dictionary _typesDb;
private readonly Encoding _encoding;
- public long Position => _position;
-
+ ///
+ /// How many bits deep into the file is the reader at currently.
+ ///
+ public long Position { get; private set; }
+
+ ///
+ /// Read bits of data raw from a serialized file using Little-endian.
+ ///
+ /// The starting address for reading bits.
+ /// The total size of memory to be read.
+ /// The custom type dictionary. Will throw an error if left null ReadType is called.
+ /// by default.
public UnmanagedDataReader(byte* ptr, long size, Dictionary typesDb = null, Encoding encoding = null)
{
_encoding = encoding ?? TextEncoding.UTF8;
@@ -46,9 +58,19 @@ public UnmanagedDataReader(byte* ptr, long size, Dictionary types
_size = size;
}
+ ///
+ /// Reads the next bit as a bool. If that bit is true, return an else null.
+ ///
+ ///
+ /// Next string value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString(bool intern = false) => ReadBool() ? ReadStringRaw(intern) : null;
+ ///
+ /// Returns the next set of bits that make up a string.
+ ///
+ ///
+ /// Next string value.
public string ReadStringRaw(bool intern = false)
{
// ReadEncodedInt
@@ -57,7 +79,7 @@ public string ReadStringRaw(bool intern = false)
do
{
- b = *(_ptr + _position++);
+ b = *(_ptr + Position++);
length |= (b & 0x7F) << shift;
shift += 7;
}
@@ -68,95 +90,153 @@ public string ReadStringRaw(bool intern = false)
return "".Intern();
}
- var str = TextEncoding.GetString(new ReadOnlySpan(_ptr + _position, length), _encoding);
- _position += length;
+ var str = TextEncoding.GetString(new ReadOnlySpan(_ptr + Position, length), _encoding);
+ Position += length;
return intern ? str.Intern() : str;
}
+ ///
+ /// Read the next 64 bits to make up a long (int64).
+ ///
+ /// Next long value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadLong()
{
- var v = BinaryPrimitives.ReadInt64LittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(long)));
- _position += sizeof(long);
+ var v = BinaryPrimitives.ReadInt64LittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(long)));
+ Position += sizeof(long);
return v;
}
+ ///
+ /// Read the next 64 bits to make up an unsigned long (uint64).
+ ///
+ /// Next ulong value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadULong()
{
- var v = BinaryPrimitives.ReadUInt64LittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(ulong)));
- _position += sizeof(ulong);
+ var v = BinaryPrimitives.ReadUInt64LittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(ulong)));
+ Position += sizeof(ulong);
return v;
}
+ ///
+ /// Read the next 32 bits to make up an int (int32).
+ ///
+ /// Next int value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt()
{
- var v = BinaryPrimitives.ReadInt32LittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(int)));
- _position += sizeof(int);
+ var v = BinaryPrimitives.ReadInt32LittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(int)));
+ Position += sizeof(int);
return v;
}
+ ///
+ /// Read the next 32 bits to make up an unsigned int (uint32).
+ ///
+ /// Next uint value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadUInt()
{
- var v = BinaryPrimitives.ReadUInt32LittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(uint)));
- _position += sizeof(uint);
+ var v = BinaryPrimitives.ReadUInt32LittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(uint)));
+ Position += sizeof(uint);
return v;
}
+ ///
+ /// Read the next 16 bits to make up a short (int16).
+ ///
+ /// Next short value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public short ReadShort()
{
- var v = BinaryPrimitives.ReadInt16LittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(short)));
- _position += sizeof(short);
+ var v = BinaryPrimitives.ReadInt16LittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(short)));
+ Position += sizeof(short);
return v;
}
+ ///
+ /// Read the next 16 bits to make up an unsigned short (int16).
+ ///
+ /// Next ushort value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUShort()
{
- var v = BinaryPrimitives.ReadUInt16LittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(ushort)));
- _position += sizeof(ushort);
+ var v = BinaryPrimitives.ReadUInt16LittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(ushort)));
+ Position += sizeof(ushort);
return v;
}
+ ///
+ /// Read the next 8 bits to make up a float point double.
+ ///
+ /// Next double value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double ReadDouble()
{
- var v = BinaryPrimitives.ReadDoubleLittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(double)));
- _position += sizeof(double);
+ var v = BinaryPrimitives.ReadDoubleLittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(double)));
+ Position += sizeof(double);
return v;
}
+ ///
+ /// Read the next 4 bits to make up a float point.
+ ///
+ /// Next float value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadFloat()
{
- var v = BinaryPrimitives.ReadSingleLittleEndian(new ReadOnlySpan(_ptr + _position, sizeof(float)));
- _position += sizeof(float);
+ var v = BinaryPrimitives.ReadSingleLittleEndian(new ReadOnlySpan(_ptr + Position, sizeof(float)));
+ Position += sizeof(float);
return v;
}
+ ///
+ /// Read the next 8 bits to make up a byte.
+ ///
+ /// Next byte value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public byte ReadByte() => *(_ptr + _position++);
-
+ public byte ReadByte() => *(_ptr + Position++);
+ ///
+ /// Read the next 8 bits to make up a signed byte.
+ ///
+ /// Next sbyte value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public sbyte ReadSByte() => (sbyte)ReadByte();
-
+ ///
+ /// Read the next Byte and return true if it's value returns zero.
+ ///
+ /// Next bool value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ReadBool() => ReadByte() != 0;
+ ///
+ /// Read the next 32 bytes to make up a .
+ ///
+ /// Next uint value cast as a struct.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Serial ReadSerial() => (Serial)ReadUInt();
+ ///
+ /// Reads the next Byte which helps determin how to read the following Type.
+ ///
If the byte returns 1 => and translate into a Type via the
+ ///
If the byte returns 2 =>
+ ///
else return null
+ ///
+ /// Next Type value
public Type ReadType() =>
ReadByte() switch
{
- 0 => null,
1 => AssemblyHandler.FindTypeByFullName(ReadStringRaw()), // Backward compatibility
- 2 => ReadTypeByHash()
+ 2 => ReadTypeByHash(),
+ _ => null,
};
+ ///
+ /// Reads the next to create a hash and convert that into a Type using the .
+ /// Will log an if typesDb is null or typesDb doesn't contain the hash and return null
+ ///
+ /// Next Type value
public Type ReadTypeByHash()
{
var hash = ReadULong();
@@ -205,19 +285,33 @@ public Type ReadTypeByHash()
return t;
}
+ ///
+ /// Reads the next set of bytes to fill the buffer.
+ ///
+ /// A reference span that will be filled with the next set of bytes.
+ /// The length of the buffer.
+ /// Thrown if the buffer is larger than the remaining data to read in the file.
public int Read(Span buffer)
{
var length = buffer.Length;
- if (length > _size - _position)
+ if (length > _size - Position)
{
throw new OutOfMemoryException();
}
- new ReadOnlySpan(_ptr + _position, length).CopyTo(buffer);
- _position += length;
+ new ReadOnlySpan(_ptr + Position, length).CopyTo(buffer);
+ Position += length;
return length;
}
+ ///
+ /// Sets the current position of the stream to a specified value.
+ ///
+ /// The new position, relative to the parameter.
+ /// The reference point for the parameter. It can be one of the values of .
+ /// The new position in the stream, in bytes.
+ /// Thrown when the or the resulting position is out of the valid range.
+ /// Thrown if the stream does not support seeking.
public virtual long Seek(long offset, SeekOrigin origin)
{
Debug.Assert(
@@ -229,18 +323,16 @@ public virtual long Seek(long offset, SeekOrigin origin)
"Attempting to seek to an invalid position using SeekOrigin.Begin"
);
Debug.Assert(
- origin != SeekOrigin.Current || _position + offset >= 0 && _position + offset < _size,
+ origin != SeekOrigin.Current || Position + offset >= 0 && Position + offset < _size,
"Attempting to seek to an invalid position using SeekOrigin.Current"
);
- var position = Math.Max(0L, origin switch
+ Position = Math.Max(0L, origin switch
{
- SeekOrigin.Current => _position + offset,
+ SeekOrigin.Current => Position + offset,
SeekOrigin.End => _size + offset,
_ => offset // Begin
});
-
- _position = position;
- return _position;
+ return Position;
}
}
diff --git a/Projects/Server/Server.csproj b/Projects/Server/Server.csproj
index fc974e96c2..a47b3fb1a6 100644
--- a/Projects/Server/Server.csproj
+++ b/Projects/Server/Server.csproj
@@ -33,7 +33,7 @@
-
+
diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs
index 87fbad7062..ad4630a22f 100644
--- a/Projects/Server/Timer/Timer.TimerWheel.cs
+++ b/Projects/Server/Timer/Timer.TimerWheel.cs
@@ -136,9 +136,6 @@ private static void Execute(Timer timer)
{
var finished = timer.Count != 0 && timer.Index + 1 >= timer.Count;
- var prof = timer.GetProfile();
- prof?.Start();
-
// Stop the timer from running so that way if Start() is called in OnTick, the timer will be started.
if (finished)
{
@@ -149,7 +146,6 @@ private static void Execute(Timer timer)
var version = timer.Version;
timer.OnTick();
- prof?.Finish();
// Starting doesn't change the timer version, so we need to check if it's finished and if it's still running.
if (timer.Version != version || finished && timer.Running)
diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs
index 69b30ba9cb..536faedf9a 100644
--- a/Projects/Server/Timer/Timer.cs
+++ b/Projects/Server/Timer/Timer.cs
@@ -15,7 +15,6 @@
using System;
using System.Diagnostics;
-using Server.Diagnostics;
using Server.Logging;
namespace Server;
@@ -54,13 +53,6 @@ protected void Init(TimeSpan delay, TimeSpan interval, int count)
Next = Core.Now + Delay;
_ring = -1;
_slot = -1;
-
- var prof = GetProfile();
-
- if (prof != null)
- {
- prof.Created++;
- }
}
protected int Version { get; set; } // Used to determine if a timer was altered and we should abandon it.
@@ -73,8 +65,6 @@ protected void Init(TimeSpan delay, TimeSpan interval, int count)
public int RemainingCount => Count == 0 ? int.MaxValue : Count - Index;
public bool Running { get; private set; }
- public TimerProfile GetProfile() => !Core.Profiling ? null : TimerProfile.Acquire(ToString() ?? "null");
-
public override string ToString() => GetType().FullName;
public Timer Start()
@@ -111,13 +101,6 @@ public Timer Start()
Running = true;
AddTimer(this, (long)Delay.TotalMilliseconds);
- var prof = GetProfile();
-
- if (prof != null)
- {
- prof.Started++;
- }
-
return this;
}
@@ -174,12 +157,6 @@ private void InternalStop()
{
_executingRings[_ring] = _nextTimer;
}
-
- var prof = GetProfile();
- if (prof != null)
- {
- prof.Stopped++;
- }
}
protected virtual void OnTick()
diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs
index 8eff6002dc..61545077d5 100644
--- a/Projects/Server/Utilities/Utility.cs
+++ b/Projects/Server/Utilities/Utility.cs
@@ -17,7 +17,7 @@
namespace Server;
-public static class Utility
+public static partial class Utility
{
private static Dictionary _ipAddressTable;
@@ -237,6 +237,8 @@ public static PooledArraySpanFormattable FixHtmlFormattable(this ReadOnlySpan.Shared.Rent(str.Length);
var span = chars.AsSpan(0, str.Length);
+ str.CopyTo(span);
+
var formattable = new PooledArraySpanFormattable(chars, str.Length);
if (!str.IsNullOrWhiteSpace())
@@ -1458,4 +1460,12 @@ public static int C32216(this int c32)
return (r << 10) | (g << 5) | b;
}
+
+ public static void AddOrUpdate(this ConditionalWeakTable table, TKey key, TValue value)
+ where TKey : class
+ where TValue : class
+ {
+ table.Remove(key);
+ table.Add(key, value);
+ }
}
diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs
index 32659f7512..234c76d0b5 100644
--- a/Projects/Server/World/World.cs
+++ b/Projects/Server/World/World.cs
@@ -45,7 +45,7 @@ public static class World
private static readonly GenericEntityPersistence _guildPersistence = new("Guilds", 3, 1, 0x7FFFFFFF);
private static int _threadId;
- private static readonly SerializationThreadWorker[] _threadWorkers = new SerializationThreadWorker[Math.Max(Environment.ProcessorCount - 1, 1)];
+ internal static SerializationThreadWorker[] _threadWorkers;
private static readonly ManualResetEvent _diskWriteHandle = new(true);
private static readonly ConcurrentQueue- _decayQueue = new();
@@ -88,6 +88,7 @@ public static Serial NewVirtual
public static Dictionary Mobiles => _mobilePersistence.EntitiesBySerial;
public static Dictionary Guilds => _guildPersistence.EntitiesBySerial;
+ public static bool UseMultiThreadedSaves { get; private set; }
public static string SavePath { get; private set; }
public static WorldState WorldState { get; private set; }
public static bool Saving => WorldState == WorldState.Saving;
@@ -101,13 +102,12 @@ public static void Configure()
var savePath = ServerConfiguration.GetOrUpdateSetting("world.savePath", "Saves");
SavePath = PathUtility.GetFullPath(savePath);
+
+ UseMultiThreadedSaves = ServerConfiguration.GetOrUpdateSetting("world.useMultithreadedSaves", true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void WaitForWriteCompletion()
- {
- _diskWriteHandle.WaitOne();
- }
+ public static void WaitForWriteCompletion() => _diskWriteHandle.WaitOne();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnqueueForDecay(Item item)
@@ -207,6 +207,9 @@ public static void Load()
);
// Create the serialization threads.
+ var threadCount = UseMultiThreadedSaves ? Math.Max(Environment.ProcessorCount - 1, 1) : 1;
+ _threadWorkers = new SerializationThreadWorker[threadCount];
+
for (var i = 0; i < _threadWorkers.Length; i++)
{
_threadWorkers[i] = new SerializationThreadWorker(i);
@@ -267,17 +270,26 @@ public static void Save()
return;
}
+ WaitForWriteCompletion(); // Blocks Save until current disk flush is done.
+ _diskWriteHandle.Reset();
+
WorldState = WorldState.PendingSave;
ThreadPool.QueueUserWorkItem(Preserialize);
}
- internal static void Preserialize(object state)
+ private static void Preserialize(object state)
{
var tempPath = PathUtility.EnsureRandomPath(_tempSavePath);
try
{
- Persistence.PreSerializeAll(tempPath, SerializedTypes);
+ // Allocate the heaps for the GC
+ foreach (var worker in _threadWorkers)
+ {
+ worker.AllocateHeap();
+ }
+
+ WakeSerializationThreads();
Core.RequestSnapshot(tempPath);
}
catch (Exception ex)
@@ -296,9 +308,7 @@ internal static void Snapshot(string snapshotPath)
return;
}
- WaitForWriteCompletion(); // Blocks Save until current disk flush is done.
-
- _diskWriteHandle.Reset();
+ NetState.FlushAll();
WorldState = WorldState.Saving;
@@ -314,7 +324,6 @@ internal static void Snapshot(string snapshotPath)
{
_serializationStart = Core.Now;
- WakeSerializationThreads();
Persistence.SerializeAll();
PauseSerializationThreads();
EventSink.InvokeWorldSave();
@@ -324,9 +333,8 @@ internal static void Snapshot(string snapshotPath)
exception = ex;
}
- WorldState = WorldState.PendingSave;
+ WorldState = WorldState.WritingSave;
ThreadPool.QueueUserWorkItem(WriteFiles, snapshotPath);
- Persistence.PostWorldSaveAll(); // Process safety queues
watch.Stop();
if (exception == null)
@@ -345,6 +353,8 @@ internal static void Snapshot(string snapshotPath)
}
}
+ private static readonly HashSet _typesSet = [];
+
private static void WriteFiles(object state)
{
var snapshotPath = (string)state;
@@ -352,7 +362,16 @@ private static void WriteFiles(object state)
{
var watch = Stopwatch.StartNew();
logger.Information("Writing world save snapshot");
- Persistence.WriteSnapshotAll(snapshotPath, SerializedTypes);
+
+ // Dedupe the types
+ while (SerializedTypes.TryDequeue(out var type))
+ {
+ _typesSet.Add(type);
+ }
+
+ Persistence.WriteSnapshotAll(snapshotPath, _typesSet);
+
+ _typesSet.Clear();
try
{
@@ -380,7 +399,13 @@ private static void WriteFiles(object state)
SerializedTypes.Clear();
_diskWriteHandle.Set();
+ Core.LoopContext.Post(FinishWorldSave);
+ }
+
+ private static void FinishWorldSave()
+ {
WorldState = WorldState.Running;
+ Persistence.PostWorldSaveAll(); // Process decay and safety queues
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -408,9 +433,9 @@ internal static void PauseSerializationThreads()
internal static void ResetRoundRobin() => _threadId = 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static void PushToCache((IGenericSerializable e, Persistence p) ep)
+ internal static void PushToCache(IGenericSerializable e)
{
- _threadWorkers[_threadId++].Push(ep);
+ _threadWorkers[_threadId++].Push(e);
if (_threadId == _threadWorkers.Length)
{
_threadId = 0;
@@ -474,20 +499,20 @@ public static void RemoveEntity(IEntity entity)
// Legacy: Only used for retrieving Items and Mobiles.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static IEntity FindEntity(Serial serial, bool returnDeleted = false, bool returnPending = false) =>
- FindEntity(serial, returnDeleted, returnPending);
+ public static IEntity FindEntity(Serial serial, bool returnDeleted = false) =>
+ FindEntity(serial, returnDeleted);
// Legacy: Only used for retrieving Items and Mobiles.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static T FindEntity(Serial serial, bool returnDeleted = false, bool returnPending = false)
+ public static T FindEntity(Serial serial, bool returnDeleted = false)
where T : class, IEntity
{
if (serial.IsItem)
{
- return _itemPersistence.Find(serial, returnDeleted, returnPending) as T;
+ return _itemPersistence.Find(serial, returnDeleted) as T;
}
- return _mobilePersistence.Find(serial, returnDeleted, returnPending) as T;
+ return _mobilePersistence.Find(serial, returnDeleted) as T;
}
private class ItemPersistence : GenericEntityPersistence
-
@@ -520,7 +545,7 @@ public override void Serialize()
EnqueueForDecay(item);
}
- PushToCache((item, this));
+ PushToCache(item);
}
}
@@ -551,78 +576,4 @@ public override void PostDeserialize()
}
}
}
-
- private class SerializationThreadWorker
- {
- private readonly int _index;
- private readonly Thread _thread;
- private readonly AutoResetEvent _startEvent; // Main thread tells the thread to start working
- private readonly AutoResetEvent _stopEvent; // Main thread waits for the worker finish draining
- private bool _pause;
- private bool _exit;
- private readonly ConcurrentQueue<(IGenericSerializable, Persistence)> _entities;
-
- public SerializationThreadWorker(int index)
- {
- _index = index;
- _startEvent = new AutoResetEvent(false);
- _stopEvent = new AutoResetEvent(false);
- _entities = new ConcurrentQueue<(IGenericSerializable, Persistence)>();
- _thread = new Thread(Execute);
- _thread.Start(this);
- }
-
- public void Wake()
- {
- _startEvent.Set();
- }
-
- public void Sleep()
- {
- Volatile.Write(ref _pause, true);
- _stopEvent.WaitOne();
- }
-
- public void Exit()
- {
- _exit = true;
- Wake();
- Sleep();
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Push((IGenericSerializable e, Persistence p) ep) => _entities.Enqueue(ep);
-
- private static void Execute(object obj)
- {
- SerializationThreadWorker worker = (SerializationThreadWorker)obj;
-
- var reader = worker._entities;
-
- while (worker._startEvent.WaitOne())
- {
- while (true)
- {
- bool pauseRequested = Volatile.Read(ref worker._pause);
- if (reader.TryDequeue(out var ep))
- {
- var (e, p) = ep;
- p.Serialize(e, worker._index);
- }
- else if (pauseRequested) // Break when finished
- {
- break;
- }
- }
-
- worker._stopEvent.Set(); // Allow the main thread to continue now that we are finished
- worker._pause = false;
-
- if (Core.Closing || worker._exit)
- {
- return;
- }
- }
- }
- }
}
diff --git a/Projects/UOContent.Tests/UOContent.Tests.csproj b/Projects/UOContent.Tests/UOContent.Tests.csproj
index 2ff1f5880f..b2cc0b6b9c 100644
--- a/Projects/UOContent.Tests/UOContent.Tests.csproj
+++ b/Projects/UOContent.Tests/UOContent.Tests.csproj
@@ -4,8 +4,8 @@
Debug;Release;Analyze
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs
index 46c2b0071a..ac7da1abe2 100644
--- a/Projects/UOContent/Accounting/Account.cs
+++ b/Projects/UOContent/Accounting/Account.cs
@@ -284,6 +284,10 @@ public bool Inactive
public Serial Serial { get; set; }
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
[AfterDeserialization(false)]
private void AfterDeserialization()
{
@@ -362,13 +366,17 @@ public void Delete()
public void SetPassword(string plainPassword)
{
- Password = AccountSecurity.CurrentPasswordProtection.EncryptPassword(plainPassword);
+ var phrase = _passwordAlgorithm is PasswordProtectionAlgorithm.SHA1 or PasswordProtectionAlgorithm.SHA2
+ ? $"{_username}{plainPassword}"
+ : plainPassword;
+
+ Password = AccountSecurity.CurrentPasswordProtection.EncryptPassword(phrase);
PasswordAlgorithm = AccountSecurity.CurrentAlgorithm;
}
public bool CheckPassword(string plainPassword)
{
- var phrase = _passwordAlgorithm == PasswordProtectionAlgorithm.SHA1
+ var phrase = _passwordAlgorithm is PasswordProtectionAlgorithm.SHA1 or PasswordProtectionAlgorithm.SHA2
? $"{_username}{plainPassword}"
: plainPassword;
diff --git a/Projects/UOContent/Accounting/AccountHandler.cs b/Projects/UOContent/Accounting/AccountHandler.cs
index 12f9a96474..fce134355b 100644
--- a/Projects/UOContent/Accounting/AccountHandler.cs
+++ b/Projects/UOContent/Accounting/AccountHandler.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Net;
+using ModernUO.CodeGeneratedEvents;
using Server.Accounting;
+using Server.Engines.CharacterCreation;
using Server.Engines.Help;
using Server.Logging;
using Server.Network;
@@ -65,7 +67,6 @@ public static void Configure()
public static void Initialize()
{
EventSink.AccountLogin += EventSink_AccountLogin;
- EventSink.GameLogin += EventSink_GameLogin;
}
[Usage("Password ")]
@@ -348,7 +349,8 @@ public static void EventSink_AccountLogin(AccountLoginEventArgs e)
}
}
- public static void EventSink_GameLogin(GameLoginEventArgs e)
+ [OnEvent(nameof(GameServer.GameServerLoginEvent))]
+ public static void OnGameServerLogin(GameServer.GameLoginEventArgs e)
{
var un = e.Username;
var pw = e.Password;
diff --git a/Projects/UOContent/Commands/Profiling.cs b/Projects/UOContent/Commands/Profiling.cs
deleted file mode 100644
index c1c362fa87..0000000000
--- a/Projects/UOContent/Commands/Profiling.cs
+++ /dev/null
@@ -1,427 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using Server.Diagnostics;
-
-namespace Server.Commands
-{
- public static class Profiling
- {
- public static void Configure()
- {
- CommandSystem.Register("DumpTimers", AccessLevel.Administrator, DumpTimers_OnCommand);
- CommandSystem.Register("CountObjects", AccessLevel.Administrator, CountObjects_OnCommand);
- CommandSystem.Register("ProfileWorld", AccessLevel.Administrator, ProfileWorld_OnCommand);
- CommandSystem.Register("TraceInternal", AccessLevel.Administrator, TraceInternal_OnCommand);
- CommandSystem.Register("TraceExpanded", AccessLevel.Administrator, TraceExpanded_OnCommand);
- CommandSystem.Register("WriteProfiles", AccessLevel.Administrator, WriteProfiles_OnCommand);
- CommandSystem.Register("SetProfiles", AccessLevel.Administrator, SetProfiles_OnCommand);
- }
-
- [Usage("WriteProfiles")]
- [Description("Generates a log files containing performance diagnostic information.")]
- public static void WriteProfiles_OnCommand(CommandEventArgs e)
- {
- try
- {
- using var sw = new StreamWriter("profiles.log", true);
- sw.WriteLine("# Dump on {0:f}", Core.Now);
- sw.WriteLine($"# Core profiling for {Core.ProfileTime}");
-
- sw.WriteLine("# Packet send");
- BaseProfile.WriteAll(sw, PacketSendProfile.Profiles);
- sw.WriteLine();
-
- sw.WriteLine("# Packet receive");
- BaseProfile.WriteAll(sw, PacketReceiveProfile.Profiles);
- sw.WriteLine();
-
- sw.WriteLine("# Timer");
- BaseProfile.WriteAll(sw, TimerProfile.Profiles);
- sw.WriteLine();
-
- sw.WriteLine("# Gump response");
- BaseProfile.WriteAll(sw, GumpProfile.Profiles);
- sw.WriteLine();
-
- sw.WriteLine("# Target response");
- BaseProfile.WriteAll(sw, TargetProfile.Profiles);
- sw.WriteLine();
- }
- catch
- {
- // ignored
- }
- }
-
- [Usage("SetProfiles [true | false]"),
- Description("Enables, disables, or toggles the state of core packet and timer profiling.")]
- public static void SetProfiles_OnCommand(CommandEventArgs e)
- {
- if (e.Length == 1)
- {
- Core.Profiling = e.GetBoolean(0);
- }
- else
- {
- Core.Profiling = !Core.Profiling;
- }
-
- if (Core.Profiling)
- {
- e.Mobile.SendMessage($"Profiling has been enabled.");
- }
- else
- {
- e.Mobile.SendMessage($"Profiling has been disabled.");
- }
- }
-
- [Usage("DumpTimers"),
- Description("Generates a log file of all currently executing timers. Used for tracing timer leaks.")]
- public static void DumpTimers_OnCommand(CommandEventArgs e)
- {
- try
- {
- using var sw = new StreamWriter("timerdump.log", true);
- Timer.DumpInfo(sw);
- e.Mobile.SendMessage("Timers dumped to timerdump.log");
- }
- catch
- {
- // ignored
- }
- }
-
- [Usage("CountObjects")]
- [Description("Generates a log file detailing all item and mobile types in the world.")]
- public static void CountObjects_OnCommand(CommandEventArgs e)
- {
- using (var op = new StreamWriter("objects.log"))
- {
- var table = new Dictionary();
-
- foreach (var item in World.Items.Values)
- {
- var type = item.GetType();
-
- table[type] = (table.TryGetValue(type, out var value) ? value : 0) + 1;
- }
-
- var items = table.ToList();
- table.Clear();
-
- foreach (var m in World.Mobiles.Values)
- {
- var type = m.GetType();
-
- table[type] = (table.TryGetValue(type, out var value) ? value : 0) + 1;
- }
-
- var mobiles = table.ToList();
-
- items.Sort(new CountSorter());
- mobiles.Sort(new CountSorter());
-
- op.WriteLine("# Object count table generated on {0}", Core.Now);
- op.WriteLine();
- op.WriteLine();
-
- op.WriteLine("# Items:");
-
- items.ForEach(
- kvp =>
- op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / World.Items.Count, kvp.Key)
- );
-
- op.WriteLine();
- op.WriteLine();
-
- op.WriteLine("#Mobiles:");
-
- mobiles.ForEach(
- kvp =>
- op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / World.Mobiles.Count, kvp.Key)
- );
- }
-
- e.Mobile.SendMessage("Object table has been generated. See the file : objects.log");
- }
-
- [Usage("TraceExpanded")]
- [Description("Generates a log file describing all items using expanded memory.")]
- public static void TraceExpanded_OnCommand(CommandEventArgs e)
- {
- var typeTable = new Dictionary();
-
- foreach (var item in World.Items.Values)
- {
- var flags = item.GetExpandFlags();
-
- if ((flags & ~(ExpandFlag.TempFlag | ExpandFlag.SaveFlag)) == 0)
- {
- continue;
- }
-
- var itemType = item.GetType();
-
- do
- {
- if (!typeTable.TryGetValue(itemType, out var countTable))
- {
- typeTable[itemType] = countTable = new int[9];
- }
-
- if ((flags & ExpandFlag.Name) != 0)
- {
- ++countTable[0];
- }
-
- if ((flags & ExpandFlag.Items) != 0)
- {
- ++countTable[1];
- }
-
- if ((flags & ExpandFlag.Bounce) != 0)
- {
- ++countTable[2];
- }
-
- if ((flags & ExpandFlag.Holder) != 0)
- {
- ++countTable[3];
- }
-
- if ((flags & ExpandFlag.Blessed) != 0)
- {
- ++countTable[4];
- }
-
- /*if (( flags & ExpandFlag.TempFlag ) != 0)
- ++countTable[5];
-
- if (( flags & ExpandFlag.SaveFlag ) != 0)
- ++countTable[6];*/
-
- if ((flags & ExpandFlag.Weight) != 0)
- {
- ++countTable[7];
- }
-
- if ((flags & ExpandFlag.Spawner) != 0)
- {
- ++countTable[8];
- }
-
- itemType = itemType.BaseType;
- } while (itemType != typeof(object));
- }
-
- try
- {
- using var op = new StreamWriter("expandedItems.log", true);
- string[] names =
- {
- "Name",
- "Items",
- "Bounce",
- "Holder",
- "Blessed",
- "TempFlag",
- "SaveFlag",
- "Weight",
- "Spawner"
- };
-
- var list = typeTable.ToList();
-
- list.Sort(new CountsSorter());
-
- foreach (var kvp in list)
- {
- var countTable = kvp.Value;
-
- op.WriteLine("# {0}", kvp.Key.FullName);
-
- for (var i = 0; i < countTable.Length; ++i)
- {
- if (countTable[i] > 0)
- {
- op.WriteLine("{0}\t{1:N0}", names[i], countTable[i]);
- }
- }
-
- op.WriteLine();
- }
- }
- catch
- {
- // ignored
- }
- }
-
- [Usage("TraceInternal")]
- [Description("Generates a log file describing all items in the 'internal' map.")]
- public static void TraceInternal_OnCommand(CommandEventArgs e)
- {
- var totalCount = 0;
- var table = new Dictionary();
-
- foreach (var item in World.Items.Values)
- {
- if (item.Parent != null || item.Map != Map.Internal)
- {
- continue;
- }
-
- ++totalCount;
-
- var type = item.GetType();
-
- if (table.TryGetValue(type, out var parms))
- {
- parms[0]++;
- parms[1] += item.Amount;
- }
- else
- {
- table[type] = new[] { 1, item.Amount };
- }
- }
-
- using var op = new StreamWriter("internal.log");
- op.WriteLine("# {0} items found", totalCount);
- op.WriteLine("# {0} different types", table.Count);
- op.WriteLine();
- op.WriteLine();
- op.WriteLine("Type\t\tCount\t\tAmount\t\tAvg. Amount");
-
- foreach (var de in table)
- {
- var parms = de.Value;
-
- op.WriteLine("{0}\t\t{1}\t\t{2}\t\t{3:F2}", de.Key.Name, parms[0], parms[1], (double)parms[1] / parms[0]);
- }
- }
-
- [Usage("ProfileWorld"),
- Description("Prints the amount of data serialized for every object type in your world file.")]
- public static void ProfileWorld_OnCommand(CommandEventArgs e)
- {
- ProfileWorld("items", "worldprofile_items.log");
- ProfileWorld("mobiles", "worldprofile_mobiles.log");
- }
-
- public static void ProfileWorld(string type, string opFile)
- {
- try
- {
- var types = new List();
-
- using (var bin = new BinaryReader(
- new FileStream(
- string.Format("Saves/{0}/{0}.tdb", type),
- FileMode.Open,
- FileAccess.Read,
- FileShare.Read
- )
- ))
- {
- var count = bin.ReadInt32();
-
- for (var i = 0; i < count; ++i)
- {
- types.Add(AssemblyHandler.FindTypeByFullName(bin.ReadString()));
- }
- }
-
- long total = 0;
-
- var table = new Dictionary();
-
- using (var bin = new BinaryReader(
- new FileStream(
- string.Format("Saves/{0}/{0}.idx", type),
- FileMode.Open,
- FileAccess.Read,
- FileShare.Read
- )
- ))
- {
- var count = bin.ReadInt32();
-
- for (var i = 0; i < count; ++i)
- {
- var typeID = bin.ReadInt32();
- var serial = bin.ReadInt32();
- var pos = bin.ReadInt64();
- var length = bin.ReadInt32();
- var objType = types[typeID];
-
- while (objType != null && objType != typeof(object))
- {
- table[objType] = length + (table.TryGetValue(objType, out var value) ? value : 0);
- objType = objType.BaseType;
- total += length;
- }
- }
- }
-
- var list = table.ToList();
-
- list.Sort(new CountSorter());
-
- using var op = new StreamWriter(opFile);
- op.WriteLine("# Profile of world {0}", type);
- op.WriteLine("# Generated on {0}", Core.Now);
- op.WriteLine();
- op.WriteLine();
-
- list.ForEach(
- kvp =>
- op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / total, kvp.Key)
- );
- }
- catch
- {
- // ignored
- }
- }
-
- private class CountSorter : IComparer>
- {
- public int Compare(KeyValuePair x, KeyValuePair y)
- {
- var aCount = x.Value;
- var bCount = y.Value;
-
- var v = -aCount.CompareTo(bCount);
-
- return v != 0 ? v : string.CompareOrdinal(x.Key.FullName, y.Key.FullName);
- }
- }
-
- private class CountsSorter : IComparer>
- {
- public int Compare(KeyValuePair x, KeyValuePair y)
- {
- var aCount = 0;
- foreach (var val in x.Value)
- {
- aCount += val;
- }
-
- var bCount = 0;
- foreach (var val in y.Value)
- {
- bCount += val;
- }
-
- var v = -aCount.CompareTo(bCount);
-
- return v != 0 ? v : string.CompareOrdinal(x.Key.FullName, y.Key.FullName);
- }
- }
- }
-}
diff --git a/Projects/UOContent/Diagnostics/GumpProfile.cs b/Projects/UOContent/Diagnostics/GumpProfile.cs
deleted file mode 100644
index 5946c7e930..0000000000
--- a/Projects/UOContent/Diagnostics/GumpProfile.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Server.Diagnostics;
-
-public class GumpProfile : BaseProfile
-{
- private static readonly Dictionary _profiles = new();
-
- public GumpProfile(Type type) : base(type.FullName)
- {
- }
-
- public static IEnumerable Profiles => _profiles.Values;
-
- public static GumpProfile Acquire(Type type)
- {
- if (!Core.Profiling)
- {
- return null;
- }
-
- if (!_profiles.TryGetValue(type, out var prof))
- {
- _profiles.Add(type, prof = new GumpProfile(type));
- }
-
- return prof;
- }
-}
diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchCommand.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchCommand.cs
index c1027acda3..1a16e82727 100644
--- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchCommand.cs
+++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchCommand.cs
@@ -14,8 +14,6 @@ public static void Configure()
[Aliases("AdvSrch", "AS", "XmlFind")]
private static void OnCommand(CommandEventArgs e)
{
- var from = e.Mobile;
-
- from.SendGump(new AdvancedSearchGump(from), true);
+ e.Mobile.SendGump(new AdvancedSearchGump(), true);
}
}
diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchGump.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchGump.cs
index 95589ab6ac..6b3048b89d 100644
--- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchGump.cs
+++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchGump.cs
@@ -2,6 +2,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using System.Threading;
using CommunityToolkit.HighPerformance;
using Server.Commands;
using Server.Commands.Generic;
@@ -121,9 +122,9 @@ public bool AllSelected
public int DisplayFrom { get; set; }
public string CommandString { get; set; }
- public AdvancedSearchGump(Mobile from) : base(50, 50) => Build(from);
+ public AdvancedSearchGump() : base(50, 50) => Build();
- private void Build(Mobile from)
+ private void Build()
{
const int height = 500;
var haveResults = SearchResults?.Length > 0;
@@ -264,12 +265,6 @@ private void Build(Mobile from)
AddButton(5, y, 0xFAE, 0xFAF, 154);
AddLabel(38, y, 0x384, "Bring");
- // AddButton(150, y - 25, 0xFA8, 0xFAA, 159);
- // AddLabel(183, y - 25, 0x384, "Save to file:");
- //
- // AddImageTiled(270, y - 25, 180, 19, 0xBBC);
- // AddTextEntry(270, y - 25, 180, 19, 0, 300, SaveFilename);
-
AddButton(470, y - 25, 0xFA8, 0xFAA, 160);
AddLabel(503, y - 25, 0x384, "Command:");
@@ -323,9 +318,12 @@ private void Build(Mobile from)
AddButton(670, y, AllSelected ? 0xD3 : 0xD2, AllSelected ? 0xD2 : 0xD3, 1);
var allDisplayedSelected = true;
+
for (int i = 0; i < MaxEntries; i++)
{
- var index = i + DisplayFrom;
+ var offset = SortDescending ? MaxEntries - 1 - i : i;
+ var index = offset + DisplayFrom;
+
if (index >= SearchResults.Length)
{
break;
@@ -343,14 +341,14 @@ private void Build(Mobile from)
if (entry.Entity is BaseSpawner)
{
- AddButton(145, 22 * i + 30, 0xFBD, 0xFBE, 2000 + i);
+ AddButton(145, 22 * i + 30, 0xFBD, 0xFBE, 2000 + offset);
}
// Goto button
- AddButton(205, 22 * i + 30, 0xFAE, 0xFAF, 1000 + i);
+ AddButton(205, 22 * i + 30, 0xFAE, 0xFAF, 1000 + offset);
// Interface button
- AddButton(175, 22 * i + 30, 0xFAB, 0xFAD, 3000 + i);
+ AddButton(175, 22 * i + 30, 0xFAB, 0xFAD, 3000 + offset);
var textHue = 0;
var parentName = "";
@@ -394,7 +392,7 @@ private void Build(Mobile from)
AddLabelCropped(640, 22 * i + 31, 90, 21, textHue, parentName);
// display the selection button
- AddButton(730, 22 * i + 32, entry.Selected ? 0xD3 : 0xD2, entry.Selected ? 0xD2 : 0xD3, 4000 + i);
+ AddButton(730, 22 * i + 32, entry.Selected ? 0xD3 : 0xD2, entry.Selected ? 0xD2 : 0xD3, 4000 + offset);
}
AddButton(730, 5, allDisplayedSelected ? 0xD3 : 0xD2, allDisplayedSelected ? 0xD2 : 0xD3, 2); // Select all displayed
@@ -506,7 +504,7 @@ public override void OnResponse(NetState state, in RelayInfo info)
case 3: // Search
{
DoSearch(from);
- break;
+ return; // Don't resend the gump, it's sent async
}
case 154: // Bring
{
@@ -622,14 +620,11 @@ public override void OnResponse(NetState state, in RelayInfo info)
SortDescending = !SortDescending;
var radioSwitch = info.Switches.Length > 0 ? info.Switches[0] : -1;
- if (radioSwitch is < 0 or > 4)
- {
- Array.Reverse(SearchResults);
- }
- else
+ if (radioSwitch is >= 0 and <= 4)
{
Sort(from);
}
+
break;
}
case >= 1000 and <= 1999: // Goto
@@ -780,32 +775,42 @@ private void DoSearch(Mobile from)
}
}
- // Block until everything is processed
- for (var i = 0; i < _threadWorkers.Length; i++)
+ ThreadPool.QueueUserWorkItem(state =>
{
- _threadWorkers[i].Sleep();
- }
+ // Block until everything is processed
+ for (var i = 0; i < _threadWorkers.Length; i++)
+ {
+ _threadWorkers[i].Sleep();
+ }
- var ignoredEntities = new HashSet(ignoreQueue);
+ var ignoredEntities = new HashSet(ignoreQueue);
- // Force the GC to collect the ignored entities
- ignoreQueue.Clear();
+ // Force the GC to collect the ignored entities
+ ignoreQueue.Clear();
- var resultsList = new List(results.Count);
- foreach (var result in results)
- {
- if (!ignoredEntities.Contains(result.Entity))
+ var resultsList = new List(results.Count);
+ foreach (var result in results)
{
- resultsList.Add(result);
+ if (!ignoredEntities.Contains(result.Entity))
+ {
+ resultsList.Add(result);
+ }
}
- }
- SearchResults = resultsList.ToArray();
+ SearchResults = resultsList.ToArray();
- // Force the GC to collect the results
- resultsList.Clear();
+ // Force the GC to collect the results
+ resultsList.Clear();
+ resultsList.TrimExcess();
- AutoSave.SavesEnabled = autoSave;
+ // Send the gump on the main thread
+ Core.LoopContext.Post(
+ autoSaveState =>
+ {
+ AutoSave.SavesEnabled = (bool)autoSaveState!;
+ Resend(from);
+ }, state);
+ }, autoSave);
}
private void SetSortSwitches(int radioSwitch)
@@ -953,7 +958,7 @@ private void ExecuteCommand(Mobile from, string commandString)
public void Resend(Mobile m)
{
Reset();
- Build(m);
+ Build();
m.SendGump(this);
}
}
diff --git a/Projects/UOContent/Engines/Bulk Orders/Books/BaseBOBEntry.cs b/Projects/UOContent/Engines/Bulk Orders/Books/BaseBOBEntry.cs
index d3bbadb45d..163dc3a328 100644
--- a/Projects/UOContent/Engines/Bulk Orders/Books/BaseBOBEntry.cs
+++ b/Projects/UOContent/Engines/Bulk Orders/Books/BaseBOBEntry.cs
@@ -26,6 +26,10 @@ public abstract partial class BaseBOBEntry : IBOBEntry
public Serial Serial { get; }
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
public bool Deleted { get; private set; }
public BaseBOBEntry()
diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs
index 76c791ef1a..1aca46e316 100755
--- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs
+++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs
@@ -69,14 +69,17 @@ public partial class ChampionSpawn : Item
[SerializedCommandProperty(AccessLevel.GameMaster)]
private ChampionSpawnType _type;
+ [Tidy]
[SerializableField(14)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private List _creatures;
+ [Tidy]
[SerializableField(15)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private List
- _redSkulls;
+ [Tidy]
[SerializableField(16)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private List
- _whiteSkulls;
@@ -524,126 +527,128 @@ public void OnSlice()
Champion = null;
Stop();
}
- }
- else
- {
- int kills = _kills;
-
- for (var i = 0; i < _creatures.Count; ++i)
- {
- Mobile m = _creatures[i];
-
- if (m.Deleted)
- {
- if (m.Corpse is { Deleted: false })
- {
- ((Corpse)m.Corpse).BeginDecay(TimeSpan.FromMinutes(1));
- }
- RemoveFromCreaturesAt(i);
- --i;
- ++_kills;
-
- Mobile killer = m.FindMostRecentDamager(false);
+ return;
+ }
- RegisterDamageTo(m);
+ int kills = _kills;
- if (killer is BaseCreature creature)
- {
- killer = creature.GetMaster();
- }
+ for (var i = 0; i < _creatures.Count; ++i)
+ {
+ Mobile m = _creatures[i];
- if (killer is not PlayerMobile pm)
- {
- continue;
- }
+ if (!m.Deleted)
+ {
+ continue;
+ }
- int mobSubLevel = GetSubLevelfor(m) + 1;
+ if (m.Corpse is { Deleted: false })
+ {
+ ((Corpse)m.Corpse).BeginDecay(TimeSpan.FromMinutes(1));
+ }
- if (mobSubLevel >= 0)
- {
- bool gainedPath = false;
+ RemoveFromCreaturesAt(i);
+ --i;
+ ++_kills;
- int pointsToGain = mobSubLevel * 40;
+ Mobile killer = m.FindMostRecentDamager(false);
- if (VirtueSystem.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath))
- {
- if (gainedPath)
- {
- pm.SendLocalizedMessage(1054032); // You have gained a path in Valor!
- }
- else
- {
- pm.SendLocalizedMessage(1054030); // You have gained in Valor!
- }
+ RegisterDamageTo(m);
- // No delay on Valor gains
- }
+ if (killer is BaseCreature creature)
+ {
+ killer = creature.GetMaster();
+ }
- pm.ChampionTitles.Award(_type, mobSubLevel);
- }
+ if (killer is not PlayerMobile pm)
+ {
+ continue;
+ }
- if (!Core.ML)
- {
- continue;
- }
+ int mobSubLevel = GetSubLevelfor(m) + 1;
- var isFel = Map == Map.Felucca;
- double mapChance;
- if (isFel)
- {
- mapChance = 0.001;
- }
- else if (Map == Map.Ilshenar || Map == Map.Tokuno)
- {
- mapChance = 0.0015;
- }
- else // Nothing drops in Trammel, Malas or TerMur
- {
- continue;
- }
+ if (mobSubLevel >= 0)
+ {
+ bool gainedPath = false;
- if (Utility.RandomDouble() >= mapChance)
- {
- continue;
- }
+ int pointsToGain = mobSubLevel * 40;
- if (!isFel || Utility.RandomBool())
+ if (VirtueSystem.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath))
+ {
+ if (gainedPath)
{
- GiveScrollTo(pm, CreateRandomSoT(isFel), isFel);
+ pm.SendLocalizedMessage(1054032); // You have gained a path in Valor!
}
else
{
- GiveScrollTo(pm, CreateRandomPS());
+ pm.SendLocalizedMessage(1054030); // You have gained in Valor!
}
+
+ // No delay on Valor gains
}
+
+ pm.ChampionTitles.Award(_type, mobSubLevel);
}
- // Only really needed once.
- if (_kills > kills)
+ if (!Core.ML)
{
- InvalidateProperties();
+ continue;
}
- double n = _kills / (double)MaxKills;
- int p = (int)(n * 100);
-
- if (p >= 99)
+ var isFel = Map == Map.Felucca;
+ double mapChance;
+ if (isFel)
{
- AdvanceLevel();
+ mapChance = 0.001;
}
- else if (p > 0)
+ else if (Map == Map.Ilshenar || Map == Map.Tokuno)
{
- SetWhiteSkullCount(p / 20);
+ mapChance = 0.0015;
+ }
+ else // Nothing drops in Trammel, Malas or TerMur
+ {
+ continue;
+ }
+
+ if (Utility.RandomDouble() >= mapChance)
+ {
+ continue;
}
- if (Core.Now >= ExpireTime)
+ if (!isFel || Utility.RandomBool())
+ {
+ GiveScrollTo(pm, CreateRandomSoT(isFel), isFel);
+ }
+ else
{
- Expire();
+ GiveScrollTo(pm, CreateRandomPS());
}
+ }
+
+ // Only really needed once.
+ if (_kills > kills)
+ {
+ InvalidateProperties();
+ }
+
+ double n = _kills / (double)MaxKills;
+ int p = (int)(n * 100);
+
+ if (p >= 99)
+ {
+ AdvanceLevel();
+ }
+ else if (p > 0)
+ {
+ SetWhiteSkullCount(p / 20);
+ }
- Respawn();
+ if (Core.Now >= ExpireTime)
+ {
+ Expire();
}
+
+ Respawn();
}
public void AdvanceLevel()
@@ -1361,67 +1366,72 @@ public override void AlterLightLevel(Mobile m, ref int global, ref int personal)
public override void OnEnter(Mobile m)
{
- if (m.Player && m.AccessLevel == AccessLevel.Player && !Spawn.Active)
+ if (!m.Player || m.AccessLevel != AccessLevel.Player || Spawn.Active)
{
- Region parent = Parent ?? this;
+ return;
+ }
- if (Spawn.ReadyToActivate)
+ Region parent = Parent ?? this;
+
+ if (Spawn.ReadyToActivate)
+ {
+ Spawn.Start();
+ return;
+ }
+
+ if (!Spawn.ProximitySpawn || Spawn.ActivatedByProximity || Core.Now < Spawn.NextProximityTime)
+ {
+ return;
+ }
+
+ List players = parent.GetPlayers();
+ List addresses = new List();
+ for (var i = 0; i < players.Count; i++)
+ {
+ if (players[i].AccessLevel == AccessLevel.Player && players[i].NetState != null &&
+ !addresses.Contains(players[i].NetState.Address) && !((PlayerMobile)players[i]).Young)
{
- Spawn.Start();
+ addresses.Add(players[i].NetState.Address);
}
- else if (Spawn.ProximitySpawn && !Spawn.ActivatedByProximity && Core.Now >= Spawn.NextProximityTime)
- {
- List players = parent.GetPlayers();
- List addresses = new List();
- for (var i = 0; i < players.Count; i++)
- {
- if (players[i].AccessLevel == AccessLevel.Player && players[i].NetState != null &&
- !addresses.Contains(players[i].NetState.Address) && !((PlayerMobile)players[i]).Young)
- {
- addresses.Add(players[i].NetState.Address);
- }
- }
-
- if (addresses.Count >= 15)
- {
- foreach (Mobile player in players)
- {
- player.SendMessage(0x20, Spawn.BroadcastMessage);
- }
+ }
- Spawn.ActivatedByProximity = true;
- Spawn.BeginRestart(TimeSpan.FromMinutes(5.0));
- }
+ if (addresses.Count >= 15)
+ {
+ foreach (Mobile player in players)
+ {
+ player.SendMessage(0x20, Spawn.BroadcastMessage);
}
+
+ Spawn.ActivatedByProximity = true;
+ Spawn.BeginRestart(TimeSpan.FromMinutes(5.0));
}
}
public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
{
- if (base.OnMoveInto(m, d, newLocation, oldLocation))
+ if (!base.OnMoveInto(m, d, newLocation, oldLocation))
{
- if (m.Player)
- {
- if (((PlayerMobile)m).Young)
- {
- m.SendMessage("You decide against going here because of the danger.");
- }
- else if (!m.Alive)
- {
- m.SendMessage("A magical force prevents ghosts from entering this region.");
- }
- else
- {
- return true;
- }
- }
- else
- {
- return true;
- }
+ return false;
+ }
+
+ if (!m.Player)
+ {
+ return true;
}
- return false;
+ if (((PlayerMobile)m).Young)
+ {
+ m.SendMessage("You decide against going here because of the danger.");
+ return false;
+ }
+
+ if (!m.Alive)
+ {
+ m.SendMessage("A magical force prevents ghosts from entering this region.");
+ return false;
+ }
+
+ return true;
}
public override bool OnBeforeDeath(Mobile m)
@@ -1454,22 +1464,24 @@ public EjectTimer(Mobile from, ChampionSpawnRegion region) : base(TimeSpan.FromM
protected override void OnTick()
{
//See if they are dead, or logged out!
- if (m_Region.Spawn != null && m_Region.CanSpawn() && !m_From.Alive)
+ if (m_Region.Spawn == null || !m_Region.CanSpawn() || m_From.Alive)
{
- if (m_From.NetState != null)
- {
- if (m_From.Region.IsPartOf(m_Region))
- {
- m_From.MoveToWorld(m_Region.Spawn.EjectLocation, m_Region.Spawn.EjectMap);
- m_From.SendMessage("A magical force forces you out of the area.");
- }
- }
- else if (Find(m_From.LogoutLocation, m_From.LogoutMap).IsPartOf(m_Region))
+ return;
+ }
+
+ if (m_From.NetState != null)
+ {
+ if (m_From.Region.IsPartOf(m_Region))
{
- m_From.LogoutLocation = m_Region.Spawn.EjectLocation;
- m_From.LogoutMap = m_Region.Spawn.EjectMap;
+ m_From.MoveToWorld(m_Region.Spawn.EjectLocation, m_Region.Spawn.EjectMap);
+ m_From.SendMessage("A magical force forces you out of the area.");
}
}
+ else if (Find(m_From.LogoutLocation, m_From.LogoutMap).IsPartOf(m_Region))
+ {
+ m_From.LogoutLocation = m_Region.Spawn.EjectLocation;
+ m_From.LogoutMap = m_Region.Spawn.EjectMap;
+ }
}
}
}
diff --git a/Projects/UOContent/Engines/Character Creation/CharacterCreatedEvent.cs b/Projects/UOContent/Engines/Character Creation/CharacterCreatedEvent.cs
new file mode 100644
index 0000000000..4fddbf9b71
--- /dev/null
+++ b/Projects/UOContent/Engines/Character Creation/CharacterCreatedEvent.cs
@@ -0,0 +1,46 @@
+using Server.Accounting;
+using Server.Network;
+
+namespace Server.Engines.CharacterCreation;
+
+public class CharacterCreatedEventArgs(
+ NetState state, IAccount a, string name, bool female,
+ int hue, byte[] stats, CityInfo city, (SkillName, byte)[] skills,
+ int shirtHue, int pantsHue, int hairId, int hairHue,
+ int beardId, int beardHue, int profession, Race race
+)
+{
+ public NetState State { get; } = state;
+
+ public IAccount Account { get; } = a;
+
+ public Mobile Mobile { get; set; }
+
+ public string Name { get; } = name;
+
+ public bool Female { get; } = female;
+
+ public int Hue { get; } = hue;
+
+ public byte[] Stats { get; } = stats;
+
+ public CityInfo City { get; } = city;
+
+ public (SkillName, byte)[] Skills { get; } = skills;
+
+ public int ShirtHue { get; } = shirtHue;
+
+ public int PantsHue { get; } = pantsHue;
+
+ public int HairID { get; } = hairId;
+
+ public int HairHue { get; } = hairHue;
+
+ public int BeardID { get; } = beardId;
+
+ public int BeardHue { get; } = beardHue;
+
+ public int Profession { get; set; } = profession;
+
+ public Race Race { get; } = race;
+}
diff --git a/Projects/UOContent/Misc/CharacterCreation.cs b/Projects/UOContent/Engines/Character Creation/CharacterCreation.cs
similarity index 93%
rename from Projects/UOContent/Misc/CharacterCreation.cs
rename to Projects/UOContent/Engines/Character Creation/CharacterCreation.cs
index b171431e6b..b743b9da75 100644
--- a/Projects/UOContent/Misc/CharacterCreation.cs
+++ b/Projects/UOContent/Engines/Character Creation/CharacterCreation.cs
@@ -1,16 +1,18 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
+using ModernUO.CodeGeneratedEvents;
using Server.Accounting;
using Server.Items;
using Server.Logging;
using Server.Maps;
+using Server.Misc;
using Server.Mobiles;
using Server.Network;
-namespace Server.Misc;
+namespace Server.Engines.CharacterCreation;
-public static class CharacterCreation
+public static partial class CharacterCreation
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(CharacterCreation));
@@ -78,45 +80,45 @@ public static class CharacterCreation
public static readonly CityInfo[] OldHavenStartingCities =
[
- new CityInfo("Haven", "The Bountiful Harvest Inn", 3677, 2625, 0, Map.Trammel),
- new CityInfo("Britain", "Sweet Dreams Inn", 1075074, 1496, 1628, 10, Map.Trammel),
- new CityInfo("Magincia", "The Great Horns Tavern", 1075077, 3734, 2222, 20, Map.Trammel),
+ new("Haven", "The Bountiful Harvest Inn", 3677, 2625, 0, Map.Trammel),
+ new("Britain", "Sweet Dreams Inn", 1075074, 1496, 1628, 10, Map.Trammel),
+ new("Magincia", "The Great Horns Tavern", 1075077, 3734, 2222, 20, Map.Trammel),
];
public static readonly CityInfo[] FeluccaStartingCities =
[
- new CityInfo("Yew", "The Empath Abbey", 1075072, 633, 858, 0, Map.Felucca),
- new CityInfo("Minoc", "The Barnacle", 1075073, 2476, 413, 15, Map.Felucca),
- new CityInfo("Britain", "Sweet Dreams Inn", 1075074, 1496, 1628, 10, Map.Felucca),
- new CityInfo("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0, Map.Felucca),
- new CityInfo("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0, Map.Felucca),
- new CityInfo("Magincia", "The Great Horns Tavern", 1075077, 3734, 2222, 20, Map.Felucca),
- new CityInfo("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0, Map.Felucca),
- new CityInfo("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0, Map.Felucca),
- new CityInfo("Vesper", "The Ironwood Inn", 1075080, 2771, 976, 0, Map.Felucca)
+ new("Yew", "The Empath Abbey", 1075072, 633, 858, 0, Map.Felucca),
+ new("Minoc", "The Barnacle", 1075073, 2476, 413, 15, Map.Felucca),
+ new("Britain", "Sweet Dreams Inn", 1075074, 1496, 1628, 10, Map.Felucca),
+ new("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0, Map.Felucca),
+ new("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0, Map.Felucca),
+ new("Magincia", "The Great Horns Tavern", 1075077, 3734, 2222, 20, Map.Felucca),
+ new("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0, Map.Felucca),
+ new("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0, Map.Felucca),
+ new("Vesper", "The Ironwood Inn", 1075080, 2771, 976, 0, Map.Felucca)
];
public static readonly CityInfo[] TrammelStartingCities =
[
- new CityInfo("Yew", "The Empath Abbey", 1075072, 633, 858, 0, Map.Trammel),
- new CityInfo("Minoc", "The Barnacle", 1075073, 2476, 413, 15, Map.Trammel),
- new CityInfo("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0, Map.Trammel),
- new CityInfo("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0, Map.Trammel),
- new CityInfo("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0, Map.Trammel),
- new CityInfo("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0, Map.Trammel),
- new CityInfo("Vesper", "The Ironwood Inn", 1075080, 2771, 976, 0, Map.Trammel),
+ new("Yew", "The Empath Abbey", 1075072, 633, 858, 0, Map.Trammel),
+ new("Minoc", "The Barnacle", 1075073, 2476, 413, 15, Map.Trammel),
+ new("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0, Map.Trammel),
+ new("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0, Map.Trammel),
+ new("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0, Map.Trammel),
+ new("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0, Map.Trammel),
+ new("Vesper", "The Ironwood Inn", 1075080, 2771, 976, 0, Map.Trammel),
];
public static readonly CityInfo[] NewHavenStartingCities =
[
- new CityInfo("New Haven", "The Bountiful Harvest Inn", 1150168, 3503, 2574, 14, Map.Trammel),
- new CityInfo("Britain", "The Wayfarer's Inn", 1075074, 1602, 1591, 20, Map.Trammel)
+ new("New Haven", "The Bountiful Harvest Inn", 1150168, 3503, 2574, 14, Map.Trammel),
+ new("Britain", "The Wayfarer's Inn", 1075074, 1602, 1591, 20, Map.Trammel)
// Magincia removed because it burned down.
];
public static readonly CityInfo[] StartingCitiesSA =
[
- new CityInfo("Royal City", "Royal City Inn", 1150169, 738, 3486, -19, Map.TerMur)
+ new("Royal City", "Royal City Inn", 1150169, 738, 3486, -19, Map.TerMur)
];
private static CityInfo[] _availableStartingCities;
@@ -155,11 +157,8 @@ private static CityInfo[] ConstructAvailableStartingCities()
return [];
}
- public static void Initialize()
- {
- // Register our event handler
- EventSink.CharacterCreated += EventSink_CharacterCreated;
- }
+ [GeneratedEvent(nameof(CharacterCreatedEvent))]
+ public static partial void CharacterCreatedEvent(CharacterCreatedEventArgs e);
private static void AddBackpack(this Mobile m)
{
@@ -197,7 +196,8 @@ private static Mobile CreateMobile(Account a)
return null;
}
- private static void EventSink_CharacterCreated(CharacterCreatedEventArgs args)
+ [OnEvent(nameof(CharacterCreatedEvent))]
+ private static void OnCharacterCreated(CharacterCreatedEventArgs args)
{
if (!ProfessionInfo.GetProfession(args.Profession, out var profession))
{
@@ -396,24 +396,13 @@ private static CityInfo GetStartLocation(CharacterCreatedEventArgs args)
return args.City;
}
- private static void SetStats(Mobile m, NetState state, StatNameValue[] stats)
+ private static void SetStats(Mobile m, NetState state, byte[] stats)
{
var maxStats = state.NewCharacterCreation ? 90 : 80;
- var str = 0;
- var dex = 0;
- var intel = 0;
-
- for (var i = 0; i < stats.Length; i++)
- {
- var (statType, value) = stats[i];
- switch (statType)
- {
- case StatType.Str: str = value; break;
- case StatType.Dex: dex = value; break;
- case StatType.Int: intel = value; break;
- }
- }
+ var str = stats[0];
+ var dex = stats[1];
+ var intel = stats[2];
if (str is < 10 or > 60 || dex is < 10 or > 60 || intel is < 10 or > 60 || str + dex + intel != maxStats)
{
@@ -437,7 +426,7 @@ private static void SetName(Mobile m, string name)
m.Name = name;
}
- private static bool ValidateSkills(int raceFlag, SkillNameValue[] skills)
+ private static bool ValidateSkills(int raceFlag, (SkillName, byte)[] skills)
{
var total = 0;
@@ -445,7 +434,7 @@ private static bool ValidateSkills(int raceFlag, SkillNameValue[] skills)
{
var (name, value) = skills[i];
- if (value is < 0 or > 50 || !_allowedStartingSkills.Contains(name))
+ if (value > 50 || !_allowedStartingSkills.Contains(name))
{
return false;
}
@@ -473,6 +462,7 @@ private static bool ValidateSkills(int raceFlag, SkillNameValue[] skills)
for (var j = i + 1; j < skills.Length; ++j)
{
var (nameCheck, valueCheck) = skills[j];
+
if (valueCheck > 0 && nameCheck == name)
{
return false;
@@ -483,7 +473,7 @@ private static bool ValidateSkills(int raceFlag, SkillNameValue[] skills)
return total is 100 or 120;
}
- private static void SetSkills(Mobile m, SkillNameValue[] skills)
+ private static void SetSkills(Mobile m, (SkillName, byte)[] skills)
{
if (!ValidateSkills(m.Race.RaceFlag, skills))
{
diff --git a/Projects/UOContent/Engines/ConPVP/DuelContext.cs b/Projects/UOContent/Engines/ConPVP/DuelContext.cs
index c44bb85c23..6004b6ba34 100644
--- a/Projects/UOContent/Engines/ConPVP/DuelContext.cs
+++ b/Projects/UOContent/Engines/ConPVP/DuelContext.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using ModernUO.CodeGeneratedEvents;
using Server.Engines.PartySystem;
using Server.Factions;
using Server.Gumps;
@@ -596,6 +597,9 @@ public void OnLocationChanged(Mobile mob)
}
}
+ [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
+ public static void OnPlayerDeath(PlayerMobile m) => m.DuelContext?.OnDeath(m, m.Corpse);
+
public void OnDeath(Mobile mob, Container corpse)
{
if (!Registered || !Started)
@@ -1843,7 +1847,7 @@ public static void Debuff(Mobile mob)
}
TransformationSpellHelper.RemoveContext(mob, true);
- AnimalForm.RemoveContext(mob, true);
+ AnimalForm.RemoveContext(mob);
DisguisePersistence.StopTimer(mob);
if (!mob.CanBeginAction())
diff --git a/Projects/UOContent/Engines/Ethics/Core/Ethic.cs b/Projects/UOContent/Engines/Ethics/Core/Ethic.cs
index 22bb85c799..7c38c8c362 100644
--- a/Projects/UOContent/Engines/Ethics/Core/Ethic.cs
+++ b/Projects/UOContent/Engines/Ethics/Core/Ethic.cs
@@ -10,37 +10,23 @@ namespace Server.Ethics;
[SerializationGenerator(1)]
public abstract partial class Ethic : EthicsEntity
{
- public static Ethic Hero { get; private set; }
- public static Ethic Evil { get; private set; }
+ public static Ethic Hero => Ethics[0];
+ public static Ethic Evil => Ethics[1];
- public static readonly Ethic[] Ethics =
- {
- Hero,
- Evil
- };
+ private static Ethic[] Ethics => [null, null];
public static bool RegisterEthic(Ethic ethic)
{
- if (ethic is HeroEthic)
+ if (ethic is HeroEthic && Hero == null)
{
- if (Hero == null)
- {
- Hero = ethic;
- return true;
- }
-
- return false;
+ Ethics[0] = ethic;
+ return true;
}
- if (ethic is EvilEthic)
+ if (ethic is EvilEthic && Evil == null)
{
- if (Evil == null)
- {
- Evil = ethic;
- return true;
- }
-
- return false;
+ Ethics[1] = ethic;
+ return true;
}
return false;
@@ -58,6 +44,11 @@ public static Ethic Find(Item item)
{
if ((item.SavedFlags & 0x100) != 0)
{
+ if (Hero == null)
+ {
+ return null;
+ }
+
if (item.Hue == Hero.Definition.PrimaryHue)
{
return Hero;
@@ -68,6 +59,11 @@ public static Ethic Find(Item item)
if ((item.SavedFlags & 0x200) != 0)
{
+ if (Evil == null)
+ {
+ return null;
+ }
+
if (item.Hue == Evil.Definition.PrimaryHue)
{
return Evil;
@@ -172,7 +168,7 @@ public static void EventSink_Speech(SpeechEventArgs e)
{
var ethic = Ethics[i];
- if (!ethic.IsEligible(e.Mobile))
+ if (ethic?.IsEligible(e.Mobile) != true)
{
continue;
}
@@ -240,11 +236,7 @@ public static void EventSink_Speech(SpeechEventArgs e)
}
}
- public static Ethic Find(Mobile mob) => Find(mob, false, false);
-
- public static Ethic Find(Mobile mob, bool inherit) => Find(mob, inherit, false);
-
- public static Ethic Find(Mobile mob, bool inherit, bool allegiance)
+ public static Ethic Find(Mobile mob, bool inherit = false, bool allegiance = false)
{
var pl = Player.Find(mob);
@@ -255,14 +247,10 @@ public static Ethic Find(Mobile mob, bool inherit, bool allegiance)
if (inherit && mob is BaseCreature bc)
{
- if (bc.Controlled)
- {
- return Find(bc.ControlMaster, false);
- }
-
- if (bc.Summoned)
+ var master = bc.GetMaster();
+ if (master != null)
{
- return Find(bc.SummonMaster, false);
+ return Find(master);
}
if (allegiance)
diff --git a/Projects/UOContent/Engines/Ethics/Core/EthicsEntity.cs b/Projects/UOContent/Engines/Ethics/Core/EthicsEntity.cs
index 5ce677cb81..aaf5af3be4 100644
--- a/Projects/UOContent/Engines/Ethics/Core/EthicsEntity.cs
+++ b/Projects/UOContent/Engines/Ethics/Core/EthicsEntity.cs
@@ -16,6 +16,10 @@ public EthicsEntity()
public Serial Serial { get; }
+ public byte SerializedThread { get; set; }
+ public int SerializedPosition { get; set; }
+ public int SerializedLength { get; set; }
+
public bool Deleted { get; private set; }
public void Delete()
diff --git a/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholyFamiliar.cs b/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholyFamiliar.cs
index 84632e394f..6b46807d65 100644
--- a/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholyFamiliar.cs
+++ b/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholyFamiliar.cs
@@ -54,15 +54,19 @@ public UnholyFamiliar() : base(AIType.AI_Melee)
public override string ApplyNameSuffix(string suffix)
{
- if (suffix.Length == 0)
+ var ethic = Ethic.Evil;
+ if (ethic == null)
{
- suffix = Ethic.Evil.Definition.Adjunct.String;
+ return base.ApplyNameSuffix("");
}
- else
+
+ var adjunct = ethic.Definition.Adjunct;
+
+ if (suffix.Length == 0)
{
- suffix = $"{suffix} {Ethic.Evil.Definition.Adjunct.String}";
+ return base.ApplyNameSuffix(adjunct);
}
- return base.ApplyNameSuffix(suffix);
+ return base.ApplyNameSuffix($"{suffix} {adjunct}");
}
}
diff --git a/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs b/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs
index 9c8360e666..7ac77c40cc 100644
--- a/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs
+++ b/Projects/UOContent/Engines/Ethics/Evil/Mobiles/UnholySteed.cs
@@ -53,17 +53,25 @@ public UnholySteed() : base(0x74, 0x3EA7, AIType.AI_Melee, FightMode.Aggressor)
public override string ApplyNameSuffix(string suffix)
{
+ var ethic = Ethic.Evil;
+ if (ethic == null)
+ {
+ return base.ApplyNameSuffix("");
+ }
+
+ var adjunct = ethic.Definition.Adjunct;
+
if (suffix.Length == 0)
{
- return base.ApplyNameSuffix(Ethic.Evil.Definition.Adjunct.String);
+ return base.ApplyNameSuffix(adjunct);
}
- return base.ApplyNameSuffix($"{suffix} {Ethic.Evil.Definition.Adjunct.String}");
+ return base.ApplyNameSuffix($"{suffix} {adjunct}");
}
public override void OnDoubleClick(Mobile from)
{
- if (Ethic.Find(from) != Ethic.Evil)
+ if (Ethic.Evil == null || Ethic.Find(from) != Ethic.Evil)
{
from.SendMessage("You may not ride this steed.");
}
diff --git a/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolyFamiliar.cs b/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolyFamiliar.cs
index 37dc99fba7..9acdc8b1ed 100644
--- a/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolyFamiliar.cs
+++ b/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolyFamiliar.cs
@@ -55,15 +55,19 @@ public HolyFamiliar() : base(AIType.AI_Melee)
public override string ApplyNameSuffix(string suffix)
{
- if (suffix.Length == 0)
+ var ethic = Ethic.Hero;
+ if (ethic == null)
{
- suffix = Ethic.Hero.Definition.Adjunct.String;
+ return base.ApplyNameSuffix("");
}
- else
+
+ var adjunct = ethic.Definition.Adjunct;
+
+ if (suffix.Length == 0)
{
- suffix = $"{suffix} {Ethic.Hero.Definition.Adjunct.String}";
+ return base.ApplyNameSuffix(adjunct);
}
- return base.ApplyNameSuffix(suffix);
+ return base.ApplyNameSuffix($"{suffix} {adjunct}");
}
}
diff --git a/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs b/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs
index 7bb0eab5c5..edb076b799 100644
--- a/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs
+++ b/Projects/UOContent/Engines/Ethics/Hero/Mobiles/HolySteed.cs
@@ -53,17 +53,25 @@ public HolySteed() : base(0x75, 0x3EA8, AIType.AI_Melee, FightMode.Aggressor)
public override string ApplyNameSuffix(string suffix)
{
+ var ethic = Ethic.Hero;
+ if (ethic == null)
+ {
+ return base.ApplyNameSuffix("");
+ }
+
+ var adjunct = ethic.Definition.Adjunct;
+
if (suffix.Length == 0)
{
- return base.ApplyNameSuffix(Ethic.Hero.Definition.Adjunct.String);
+ return base.ApplyNameSuffix(adjunct);
}
- return base.ApplyNameSuffix($"{suffix} {Ethic.Hero.Definition.Adjunct.String}");
+ return base.ApplyNameSuffix($"{suffix} {adjunct}");
}
public override void OnDoubleClick(Mobile from)
{
- if (Ethic.Find(from) != Ethic.Hero)
+ if (Ethic.Hero == null || Ethic.Find(from) != Ethic.Hero)
{
from.SendMessage("You may not ride this steed.");
}
diff --git a/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs b/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs
index b028ddc856..d583c6be78 100644
--- a/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs
+++ b/Projects/UOContent/Engines/ML Quests/MLQuestSystem.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using ModernUO.CodeGeneratedEvents;
using Server.Commands;
using Server.Commands.Generic;
using Server.Engines.MLQuests.Gumps;
@@ -611,6 +612,7 @@ public static MLQuestContext GetOrCreateContext(PlayerMobile pm)
return context;
}
+ [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
public static void HandleDeath(PlayerMobile pm)
{
var context = GetContext(pm);
diff --git a/Projects/UOContent/Engines/Party/Party.cs b/Projects/UOContent/Engines/Party/Party.cs
index 23988158a1..e474885c8c 100644
--- a/Projects/UOContent/Engines/Party/Party.cs
+++ b/Projects/UOContent/Engines/Party/Party.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
+using ModernUO.CodeGeneratedEvents;
using Server.Factions;
+using Server.Mobiles;
using Server.Network;
using Server.Targeting;
@@ -99,7 +101,6 @@ public void OnStatsQuery(Mobile beholder, Mobile beheld)
public static void Configure()
{
EventSink.Logout += EventSink_Logout;
- EventSink.PlayerDeath += EventSink_PlayerDeath;
CommandSystem.Register("ListenToParty", AccessLevel.GameMaster, ListenToParty_OnCommand);
}
@@ -135,7 +136,8 @@ public static void ListenToParty_OnTarget(Mobile from, object obj)
}
}
- public static void EventSink_PlayerDeath(Mobile from)
+ [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
+ public static void OnPlayerDeathEvent(Mobile from)
{
var p = Get(from);
diff --git a/Projects/UOContent/Engines/Player Murder System/ReportMurdererGump.cs b/Projects/UOContent/Engines/Player Murder System/ReportMurdererGump.cs
index 7704444396..31b4a6bc30 100644
--- a/Projects/UOContent/Engines/Player Murder System/ReportMurdererGump.cs
+++ b/Projects/UOContent/Engines/Player Murder System/ReportMurdererGump.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using ModernUO.CodeGeneratedEvents;
using Server.Gumps;
using Server.Misc;
using Server.Mobiles;
@@ -26,16 +27,16 @@ private ReportMurdererGump(List killers, int idx = 0) : base(0, 0)
public static void Initialize()
{
_recentlyReportedDelay = ServerConfiguration.GetOrUpdateSetting("murderSystem.recentlyReportedDelay", TimeSpan.FromMinutes(10));
- EventSink.PlayerDeath += OnPlayerDeath;
}
- public static void OnPlayerDeath(Mobile m)
+ [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))]
+ public static void OnPlayerDeathEvent(PlayerMobile m)
{
List killers = null;
HashSet toGive = null;
// Guards won't take reports of the death of a thief!
- bool notInThievesGuild = m is not PlayerMobile { NpcGuild: NpcGuild.ThievesGuild };
+ bool notInThievesGuild = m.NpcGuild != NpcGuild.ThievesGuild;
foreach (var ai in m.Aggressors)
{
diff --git a/Projects/UOContent/Engines/Virtues/Justice.cs b/Projects/UOContent/Engines/Virtues/Justice.cs
index 01afa4de60..abbb8da1e9 100644
--- a/Projects/UOContent/Engines/Virtues/Justice.cs
+++ b/Projects/UOContent/Engines/Virtues/Justice.cs
@@ -227,7 +227,7 @@ public static void OnVirtueAccepted(PlayerMobile protector, PlayerMobile protect
}
else
{
- AddProtection(protectee, protector);
+ AddProtection(protector, protectee);
var args = $"{protector.Name}\t{protectee.Name}";
@@ -267,67 +267,75 @@ public static void CheckAtrophy(PlayerMobile pm)
}
}
-public class AcceptProtectorGump : Gump
+public class AcceptProtectorGump : StaticGump
{
- private readonly PlayerMobile _protectee;
private readonly PlayerMobile _protector;
+ private readonly PlayerMobile _protectee;
public AcceptProtectorGump(PlayerMobile protector, PlayerMobile protectee) : base(150, 50)
{
_protector = protector;
_protectee = protectee;
+ }
- Closable = false;
+ protected override void BuildLayout(ref StaticGumpBuilder builder)
+ {
+ builder.SetNoClose();
- AddPage(0);
+ builder.AddPage();
- AddBackground(0, 0, 396, 218, 3600);
+ builder.AddBackground(0, 0, 396, 218, 3600);
- AddImageTiled(15, 15, 365, 190, 2624);
- AddAlphaRegion(15, 15, 365, 190);
+ builder.AddImageTiled(15, 15, 365, 190, 2624);
+ builder.AddAlphaRegion(15, 15, 365, 190);
// Another player is offering you their protection:
- AddHtmlLocalized(30, 20, 360, 25, 1049365, 0x7FFF);
- AddLabel(90, 55, 1153, protector.Name);
+ builder.AddHtmlLocalized(30, 20, 360, 25, 1049365, 0x7FFF);
+ builder.AddLabelPlaceholder(90, 55, 1153, "protector");
- AddImage(50, 45, 9005);
- AddImageTiled(80, 80, 200, 1, 9107);
- AddImageTiled(95, 82, 200, 1, 9157);
+ builder.AddImage(50, 45, 9005);
+ builder.AddImageTiled(80, 80, 200, 1, 9107);
+ builder.AddImageTiled(95, 82, 200, 1, 9157);
- AddRadio(30, 110, 9727, 9730, true, 1);
- AddHtmlLocalized(65, 115, 300, 25, 1049444, 0x7FFF); // Yes, I would like their protection.
+ builder.AddRadio(30, 110, 9727, 9730, true, 1);
+ builder.AddHtmlLocalized(65, 115, 300, 25, 1049444, 0x7FFF); // Yes, I would like their protection.
- AddRadio(30, 145, 9727, 9730, false, 0);
- AddHtmlLocalized(65, 148, 300, 25, 1049445, 0x7FFF); // No thanks, I can take care of myself.
+ builder.AddRadio(30, 145, 9727, 9730, false, 0);
+ builder.AddHtmlLocalized(65, 148, 300, 25, 1049445, 0x7FFF); // No thanks, I can take care of myself.
- AddButton(160, 175, 247, 248, 2);
+ builder.AddButton(160, 175, 247, 248, 2);
- AddImage(215, 0, 50581);
+ builder.AddImage(215, 0, 50581);
- AddImageTiled(15, 14, 365, 1, 9107);
- AddImageTiled(380, 14, 1, 190, 9105);
- AddImageTiled(15, 205, 365, 1, 9107);
- AddImageTiled(15, 14, 1, 190, 9105);
- AddImageTiled(0, 0, 395, 1, 9157);
- AddImageTiled(394, 0, 1, 217, 9155);
- AddImageTiled(0, 216, 395, 1, 9157);
- AddImageTiled(0, 0, 1, 217, 9155);
+ builder.AddImageTiled(15, 14, 365, 1, 9107);
+ builder.AddImageTiled(380, 14, 1, 190, 9105);
+ builder.AddImageTiled(15, 205, 365, 1, 9107);
+ builder.AddImageTiled(15, 14, 1, 190, 9105);
+ builder.AddImageTiled(0, 0, 395, 1, 9157);
+ builder.AddImageTiled(394, 0, 1, 217, 9155);
+ builder.AddImageTiled(0, 216, 395, 1, 9157);
+ builder.AddImageTiled(0, 0, 1, 217, 9155);
+ }
+
+ protected override void BuildStrings(ref GumpStringsBuilder builder)
+ {
+ builder.SetStringSlot("protector", _protector.Name);
}
public override void OnResponse(NetState sender, in RelayInfo info)
{
- if (info.ButtonID == 2)
+ if (info.ButtonID != 2)
{
- var okay = info.IsSwitched(1);
+ return;
+ }
- if (okay)
- {
- JusticeVirtue.OnVirtueAccepted(_protector, _protectee);
- }
- else
- {
- JusticeVirtue.OnVirtueRejected(_protector, _protectee);
- }
+ if (info.IsSwitched(1)) // okay
+ {
+ JusticeVirtue.OnVirtueAccepted(_protector, _protectee);
+ }
+ else
+ {
+ JusticeVirtue.OnVirtueRejected(_protector, _protectee);
}
}
}
diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs
index 17d1f693aa..43d6c1bef5 100644
--- a/Projects/UOContent/Gumps/AdminGump.cs
+++ b/Projects/UOContent/Gumps/AdminGump.cs
@@ -169,7 +169,7 @@ public AdminGump(
AddLabel(150, 150, LabelHue, banned.ToString());
AddLabel(20, 170, LabelHue, "Firewalled:");
- AddLabel(150, 170, LabelHue, AdminFirewall.Set.Count.ToString());
+ AddLabel(150, 170, LabelHue, Firewall.FirewallSet.Count.ToString());
AddLabel(20, 190, LabelHue, "Clients:");
AddLabel(150, 190, LabelHue, NetState.Instances.Count.ToString());
@@ -1161,7 +1161,7 @@ public AdminGump(
{
AddFirewallHeader();
- m_List ??= AdminFirewall.Set.ToList