From f268d5d4e2c7e776f1e5ac13d52f8a84ed9b4b67 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 10 Oct 2022 21:47:08 -0700 Subject: [PATCH] fix: Cleans up core code (#1187) **Only one functional change** * Fixes a bug in LogFactory where `Warning` is being logged as `Information` Non-functional changes: * Updates/Fixes copyright headers * Removes namespace scopes for core files. View with [whitespace off](https://github.com/modernuo/ModernUO/pull/1187/files?w=1). --- .github/workflows/build-test.yml | 4 +- Directory.Build.props | 2 +- .../Packets/Outgoing/VendorSellPackets.cs | 2 +- Projects/Server/AssemblyHandler.cs | 541 +- Projects/Server/Attributes.cs | 221 +- Projects/Server/Buffers/CircularBuffer.cs | 193 +- .../Server/Buffers/CircularBufferReader.cs | 645 +- .../Server/Buffers/CircularBufferWriter.cs | 997 +- Projects/Server/Buffers/SpanReader.cs | 499 +- Projects/Server/CityInfo.cs | 129 +- .../Server/Collections/ArrayEnumerator.cs | 93 +- .../Server/Collections/CollectionHelpers.cs | 17 +- .../Collections/CollectionThrowStrings.cs | 2 +- Projects/Server/Collections/HashHelpers.cs | 201 +- Projects/Server/Collections/OrderedHashSet.cs | 963 +- .../Collections/PooledOrderedHashSet.cs | 2 +- Projects/Server/Commands.cs | 399 +- .../Server/Comparers/DescendingComparer.cs | 11 +- Projects/Server/Comparers/LocationComparer.cs | 61 +- .../Configuration/ServerConfiguration.cs | 2 +- .../Server/Configuration/ServerSettings.cs | 29 +- Projects/Server/ContextMenus/ContextMenu.cs | 133 +- .../Server/ContextMenus/ContextMenuEntry.cs | 129 +- .../Server/ContextMenus/OpenBackpackEntry.cs | 19 +- .../Server/ContextMenus/PaperdollEntry.cs | 21 +- Projects/Server/Diagnostics/BaseProfile.cs | 121 +- Projects/Server/Diagnostics/GumpProfile.cs | 35 +- Projects/Server/Diagnostics/PacketProfile.cs | 147 +- Projects/Server/Diagnostics/TargetProfile.cs | 37 +- Projects/Server/Diagnostics/TimerProfile.cs | 59 +- Projects/Server/Effects.cs | 835 +- Projects/Server/EventLoopTasks.cs | 79 +- Projects/Server/Events/AccountLoginEvent.cs | 49 +- .../Server/Events/AggressiveActionEvent.cs | 93 +- .../Server/Events/CharacterCreatedEvent.cs | 135 +- Projects/Server/Events/EventSink.cs | 273 +- Projects/Server/Events/FastwalkEvent.cs | 31 +- Projects/Server/Events/GameLoginEvent.cs | 49 +- Projects/Server/Events/MovementEvent.cs | 91 +- Projects/Server/Events/ServerCrashedEvent.cs | 31 +- Projects/Server/Events/ServerListEvent.cs | 63 +- .../Server/Events/SocketConnectionEvent.cs | 35 +- Projects/Server/Events/SpeechEvent.cs | 85 +- .../Server/Exceptions/ExceptionExtensions.cs | 37 +- .../Exceptions/InvalidThreadException.cs | 11 +- .../Exceptions/MaxConnectionsException.cs | 11 +- Projects/Server/ExpansionInfo.cs | 2 +- Projects/Server/Geometry/Point2D.cs | 137 +- Projects/Server/Geometry/Point3D.cs | 265 +- Projects/Server/Geometry/Point3DList.cs | 127 +- Projects/Server/Geometry/Rectangle2D.cs | 2 +- Projects/Server/Geometry/Rectangle3D.cs | 247 +- Projects/Server/Geometry/WorldLocation.cs | 219 +- Projects/Server/Guild.cs | 241 +- Projects/Server/Gumps/GumpECHandleInput.cs | 2 +- Projects/Server/Gumps/GumpMasterGump.cs | 2 +- Projects/Server/Gumps/GumpSpriteImage.cs | 2 +- Projects/Server/Gumps/GumpTooltip.cs | 2 +- Projects/Server/HuePicker.cs | 41 +- Projects/Server/IAccount.cs | 213 +- Projects/Server/IEntity.cs | 157 +- Projects/Server/Interfaces.cs | 141 +- Projects/Server/Items/BaseMulti.cs | 203 +- Projects/Server/Items/Container.cs | 3183 ++- Projects/Server/Items/Containers.cs | 231 +- Projects/Server/Items/Item.cs | 7955 ++++--- Projects/Server/Items/Layer.cs | 275 +- Projects/Server/Items/LightType.cs | 463 +- Projects/Server/Items/SecureTradeContainer.cs | 201 +- Projects/Server/Items/VirtualCheck.cs | 635 +- Projects/Server/Items/VirtualHair.cs | 233 +- .../ClientVersionConverterFactory.cs | 4 +- .../Server/Json/Converters/GuidConverter.cs | 4 +- .../Json/Converters/GuidConverterFactory.cs | 4 +- .../Json/Converters/IPEndPointConverter.cs | 4 +- .../Converters/IPEndPointConverterFactory.cs | 4 +- .../Server/Json/Converters/MapConverter.cs | 4 +- .../Json/Converters/MapConverterFactory.cs | 4 +- .../Json/Converters/Point2DConverter.cs | 4 +- .../Converters/Point2DConverterFactory.cs | 4 +- .../Json/Converters/Point3DConverter.cs | 4 +- .../Converters/Point3DConverterFactory.cs | 4 +- .../Json/Converters/Rectangle3DConverter.cs | 4 +- .../Converters/Rectangle3DConverterFactory.cs | 4 +- .../Json/Converters/TimeSpanConverter.cs | 4 +- .../Converters/TimeSpanConverterFactory.cs | 4 +- .../Server/Json/Converters/TypeConverter.cs | 2 +- .../Json/Converters/TypeConverterFactory.cs | 2 +- .../Json/Converters/WorldLocationConverter.cs | 4 +- .../WorldLocationConverterFactory.cs | 4 +- Projects/Server/Json/DynamicJson.cs | 75 +- Projects/Server/Json/JsonConfig.cs | 137 +- Projects/Server/Json/JsonUtilities.cs | 133 +- Projects/Server/KeywordList.cs | 85 +- Projects/Server/Logging/ILogger.cs | 29 +- Projects/Server/Logging/SerilogLogger.cs | 75 +- Projects/Server/Main.cs | 1187 +- Projects/Server/Maps/MapLoader.cs | 207 +- Projects/Server/Menus/IMenu.cs | 17 +- Projects/Server/Menus/ItemListMenu.cs | 83 +- Projects/Server/Menus/QuestionMenu.cs | 55 +- Projects/Server/Mobiles/AggressorInfo.cs | 373 +- Projects/Server/Mobiles/BaseVendor.cs | 163 +- Projects/Server/Mobiles/IMount.cs | 23 +- Projects/Server/Mobiles/IVendor.cs | 2 +- Projects/Server/Mobiles/Mobile.cs | 17149 ++++++++-------- Projects/Server/Mobiles/Movement.cs | 171 +- Projects/Server/Mobiles/Notoriety.cs | 53 +- Projects/Server/Mobiles/SimplePrompt.cs | 123 +- Projects/Server/Mobiles/SimpleTarget.cs | 67 +- .../Server/Network/NetState/DumpNetStates.cs | 33 +- .../NetState/NetState.ClientVersion.cs | 149 +- .../Network/NetState/NetState.Fastwalk.cs | 2 +- Projects/Server/Network/NetState/NetState.cs | 2 +- .../Network/NetState/ProtocolChanges.cs | 79 +- Projects/Server/Network/NetworkCompression.cs | 337 +- Projects/Server/Network/PacketUtilities.cs | 59 +- .../Network/Packets/IncomingAccountPackets.cs | 2 +- .../Network/Packets/IncomingEntityPackets.cs | 2 +- .../Network/Packets/IncomingHousePackets.cs | 2 +- .../Network/Packets/IncomingItemPackets.cs | 2 +- .../Network/Packets/IncomingMessagePackets.cs | 2 +- .../Network/Packets/IncomingMobilePackets.cs | 2 +- .../Packets/IncomingMovementPackets.cs | 2 +- .../Network/Packets/IncomingPlayerPackets.cs | 2 +- .../Packets/IncomingTargetingPackets.cs | 2 +- .../Network/Packets/IncomingVendorPackets.cs | 2 +- .../Network/Packets/OutgoingAccountPackets.cs | 2 +- .../Network/Packets/OutgoingCombatPackets.cs | 2 +- .../Packets/OutgoingContainerPackets.cs | 2 +- .../Network/Packets/OutgoingDamagePackets.cs | 2 +- .../Network/Packets/OutgoingEffectPackets.cs | 2 +- .../Network/Packets/OutgoingEntityPackets.cs | 2 +- .../Packets/OutgoingEquipmentPackets.cs | 2 +- .../Network/Packets/OutgoingItemPackets.cs | 2 +- .../Network/Packets/OutgoingLightPackets.cs | 2 +- .../Network/Packets/OutgoingMapPackets.cs | 2 +- .../Network/Packets/OutgoingMessagePackets.cs | 2 +- .../Network/Packets/OutgoingMobilePackets.cs | 2 +- .../Packets/OutgoingMovementPackets.cs | 2 +- .../Network/Packets/OutgoingPlayerPackets.cs | 2 +- .../Packets/OutgoingSecureTradePackets.cs | 2 +- .../Network/Packets/OutgoingTargetPackets.cs | 2 +- .../Packets/OutgoingVendorBuyPackets.cs | 2 +- .../Packets/OutgoingVendorSellPackets.cs | 2 +- Projects/Server/Network/Pipe.cs | 801 +- Projects/Server/Network/ServerInfo.cs | 75 +- Projects/Server/Network/TcpServer.cs | 293 +- Projects/Server/Party.cs | 25 +- Projects/Server/Poison.cs | 119 +- Projects/Server/Prompt.cs | 33 +- Projects/Server/Race.cs | 291 +- Projects/Server/Random/BaseRandomSource.cs | 303 +- Projects/Server/Random/IRandomSource.cs | 45 +- Projects/Server/Random/RandomSources.cs | 21 +- Projects/Server/Random/SecureRandom.cs | 33 +- Projects/Server/Random/Xoshiro256PlusPlus.cs | 309 +- Projects/Server/Regions/Region.cs | 1481 +- Projects/Server/Regions/RegionLoader.cs | 95 +- Projects/Server/Sector.cs | 327 +- Projects/Server/SecureTrade.cs | 757 +- Projects/Server/Serial.cs | 2 +- .../Server/Serialization/AdhocPersistence.cs | 3 +- .../ManualDirtyCheckingAttribute.cs | 19 +- .../Server/Serialization/BinaryFileReader.cs | 2 +- .../Server/Serialization/BinaryFileWriter.cs | 81 +- Projects/Server/Serialization/BufferReader.cs | 2 +- Projects/Server/Serialization/BufferWriter.cs | 597 +- .../Serialization/GenericPersistence.cs | 2 +- .../Server/Serialization/IGenericReader.cs | 199 +- .../Server/Serialization/IGenericWriter.cs | 283 +- .../Server/Serialization/ISerializable.cs | 115 +- .../Serialization/ISerializableExtensions.cs | 289 +- Projects/Server/Serialization/Persistence.cs | 201 +- .../Serialization/SerializationExtensions.cs | 15 + Projects/Server/Skills.cs | 1795 +- Projects/Server/Targeting/LandTarget.cs | 63 +- Projects/Server/Targeting/MultiTarget.cs | 33 +- Projects/Server/Targeting/StaticTarget.cs | 61 +- Projects/Server/Targeting/Target.cs | 483 +- Projects/Server/Targeting/TargetCancelType.cs | 15 +- Projects/Server/Targeting/TargetFlags.cs | 15 +- Projects/Server/Text/HexStringConverter.cs | 211 +- .../Server/Text/InsensitiveStringHelpers.cs | 129 +- Projects/Server/Text/OrdinalStringHelpers.cs | 123 +- Projects/Server/Text/TextEncoding.cs | 207 +- Projects/Server/TileData.cs | 685 +- Projects/Server/TileList.cs | 117 +- .../Server/TileMatrix/TileMatrixLoader.cs | 57 +- Projects/Server/TimeZones/TimeZoneHandler.cs | 67 +- .../Server/TimeZones/TimeZoneWithRegions.cs | 19 +- Projects/Server/Timer/Timer.DelayCall.cs | 2 +- Projects/Server/Timer/Timer.DelayStateCall.cs | 2 +- Projects/Server/Timer/Timer.Pool.cs | 2 +- Projects/Server/Timer/Timer.TimerWheel.cs | 2 +- Projects/Server/Timer/Timer.cs | 2 +- Projects/Server/Timer/TimerExecutionToken.cs | 2 +- .../Server/Utilities/ActivatorExtensions.cs | 221 +- .../Utilities/EntityActivatorExtensions.cs | 43 +- Projects/Server/Utilities/PathUtility.cs | 2 +- Projects/Server/VirtueInfo.cs | 257 +- Projects/Server/World/EntityPersistence.cs | 533 +- .../World/{EntityIndex.cs => EntitySpan.cs} | 37 +- Projects/Server/World/EntityTypeIndex.cs | 15 +- Projects/Server/World/IIndexInfo.cs | 15 +- Projects/Server/World/World.cs | 1181 +- Projects/Server/World/WorldEvents.cs | 51 +- Projects/UOContent/Accounting/Accounts.cs | 2 +- .../Accounting/Security/AccountSecurity.cs | 2 +- .../Security/Argon2PasswordProtection.cs | 2 +- .../HashAlgorithmPasswordProtection.cs | 2 +- .../Security/PBKDF2PasswordProtection.cs | 2 +- .../Commands/Object Creation/CAGCategory.cs | 2 +- .../Commands/Object Creation/CAGLoader.cs | 2 +- .../Commands/Object Creation/CAGNode.cs | 2 +- .../Commands/Object Creation/CAGObject.cs | 2 +- .../Configuration/EmailConfiguration.cs | 2 +- .../Engines/CannedEvil/CannedEvilTimer.cs | 2 +- .../Engines/CannedEvil/ChampionAltar.cs | 2 +- .../Engines/CannedEvil/ChampionCommands.cs | 2 +- .../Engines/CannedEvil/ChampionPlatform.cs | 2 +- .../Engines/CannedEvil/ChampionSkull.cs | 2 +- .../CannedEvil/ChampionSkullBrazier.cs | 2 +- .../CannedEvil/ChampionSkullPlatform.cs | 2 +- .../Engines/CannedEvil/ChampionSkullType.cs | 2 +- .../Engines/CannedEvil/ChampionSpawn.cs | 2 +- .../Engines/CannedEvil/ChampionSpawnType.cs | 2 +- .../CannedEvil/DungeonChampionSpawn.cs | 2 +- .../Engines/CannedEvil/GenChampEntry.cs | 2 +- .../UOContent/Engines/CannedEvil/GenChamps.cs | 2 +- .../Engines/CannedEvil/LLChampionSpawn.cs | 2 +- .../UOContent/Engines/Chat/ChatPackets.cs | 2 +- Projects/UOContent/Engines/Help/HelpTopic.cs | 2 +- .../Engines/ML Quests/MLQuestPackets.cs | 2 +- .../UOContent/Engines/Party/PartyPackets.cs | 2 +- .../Commands/ConvertPremiumSpawners.cs | 2 +- .../Spawners/Commands/EditSpawnerCommand.cs | 2 +- .../Commands/ExportSpawnersCommand.cs | 2 +- .../Commands/GenerateSpawnersCommand.cs | 2 +- .../Spawners/Commands/RespawnCommand.cs | 2 +- .../Spawners/Commands/SpawnPropsCommand.cs | 2 +- .../Engines/Spawners/ProximitySpawner.cs | 2 +- .../Engines/Spawners/RegionSpawner.cs | 2 +- .../Engines/Spawners/SpawnPropsGump.cs | 2 +- .../CharacterStatuePackets.cs | 2 +- Projects/UOContent/Items/Books/BookPackets.cs | 2 +- .../Bulletin Boards/BulletinBoardPackets.cs | 2 +- .../Items/Games/Mahjong/MahjongPackets.cs | 2 +- .../UOContent/Items/Maps/MapItemPackets.cs | 2 +- .../Items/Misc/Corpses/CorpsePackets.cs | 2 +- .../Converters/TextDefinitionConverter.cs | 2 +- .../TextDefinitionConverterFactory.cs | 2 +- .../UOContent/Multis/Boats/BoatPackets.cs | 2 +- .../UOContent/Multis/Houses/HousePackets.cs | 2 +- Projects/UOContent/Network/EntityPackets.cs | 2 +- .../UOContent/Network/FreeshardProtocol.cs | 2 +- Projects/UOContent/Network/MapUO.cs | 2 +- .../UOContent/Network/ProtocolExtensions.cs | 2 +- Projects/UOContent/Network/UOGateway.cs | 2 +- .../Skills/Tracking/OutgoingArrowPackets.cs | 2 +- Projects/UOContent/World Saves/AutoArchive.cs | 1 - azure-pipelines.yml | 4 +- 262 files changed, 28527 insertions(+), 28646 deletions(-) rename Projects/Server/World/{EntityIndex.cs => EntitySpan.cs} (62%) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 578efb061..4ff8d6748 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -26,7 +26,7 @@ jobs: - name: Setup .NET 6 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.400 + dotnet-version: 6.0.401 - name: Build run: ./publish.cmd Release - name: Test @@ -54,7 +54,7 @@ jobs: - name: Setup .NET 6 uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.400 + dotnet-version: 6.0.401 - name: Build run: ./publish.cmd Release - name: Test diff --git a/Directory.Build.props b/Directory.Build.props index 902442372..3f480b3b1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ Kamron Batman ModernUO - 2019-2020 + 2019-2022 net6.0 x64 x64 diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPackets.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPackets.cs index 2f060bb99..37a988035 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPackets.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/VendorSellPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: VendorSellPackets.cs * * * diff --git a/Projects/Server/AssemblyHandler.cs b/Projects/Server/AssemblyHandler.cs index 4c2d53e39..fcb2866a8 100644 --- a/Projects/Server/AssemblyHandler.cs +++ b/Projects/Server/AssemblyHandler.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: AssemblyHandler.cs * * * @@ -21,334 +21,333 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; -namespace Server +namespace Server; + +public static class AssemblyHandler { - public static class AssemblyHandler + private static readonly Dictionary m_TypeCaches = new(); + private static TypeCache m_NullCache; + public static Assembly[] Assemblies { get; set; } + + internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args) { - private static readonly Dictionary m_TypeCaches = new(); - private static TypeCache m_NullCache; - public static Assembly[] Assemblies { get; set; } - - internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args) + var assemblyName = new AssemblyName(args.Name); + var assembly = LoadAssemblyByAssemblyName(assemblyName); + if (assembly == null) { - var assemblyName = new AssemblyName(args.Name); - var assembly = LoadAssemblyByAssemblyName(assemblyName); - if (assembly == null) - { - throw new FileNotFoundException( - $"Could not load file or assembly {assemblyName}. The system cannot find the file specified. Review the assemblyDirectories field in {ServerConfiguration.ConfigurationFilePath}", - $"{assemblyName.Name}.dll" - ); - } - - return assembly; + throw new FileNotFoundException( + $"Could not load file or assembly {assemblyName}. The system cannot find the file specified. Review the assemblyDirectories field in {ServerConfiguration.ConfigurationFilePath}", + $"{assemblyName.Name}.dll" + ); } - private static void EnsureAssemblyDirectories() + return assembly; + } + + private static void EnsureAssemblyDirectories() + { + if (ServerConfiguration.AssemblyDirectories.Count == 0) { - if (ServerConfiguration.AssemblyDirectories.Count == 0) - { - ServerConfiguration.AssemblyDirectories.Add("./Assemblies"); - ServerConfiguration.Save(); - } + ServerConfiguration.AssemblyDirectories.Add("./Assemblies"); + ServerConfiguration.Save(); } + } - public static Assembly LoadAssemblyByAssemblyName(AssemblyName assemblyName) + public static Assembly LoadAssemblyByAssemblyName(AssemblyName assemblyName) + { + if (assemblyName?.Name == null) { - if (assemblyName?.Name == null) - { - return null; - } - - var fullName = assemblyName.FullName; - var fileName = $"{assemblyName.Name}.dll"; - - EnsureAssemblyDirectories(); - var assemblyDirectories = ServerConfiguration.AssemblyDirectories; - - foreach (var assemblyDir in assemblyDirectories) - { - var assemblyPath = PathUtility.GetFullPath(Path.Combine(assemblyDir, fileName), Core.BaseDirectory); - if (File.Exists(assemblyPath)) - { - var assemblyNameCheck = AssemblyName.GetAssemblyName(assemblyPath); - if (assemblyNameCheck.FullName == fullName) - { - return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); - } - } - } - return null; } - public static Assembly LoadAssemblyByFileName(string assemblyFile) - { - EnsureAssemblyDirectories(); - var assemblyDirectories = ServerConfiguration.AssemblyDirectories; + var fullName = assemblyName.FullName; + var fileName = $"{assemblyName.Name}.dll"; - foreach (var assemblyDir in assemblyDirectories) + EnsureAssemblyDirectories(); + var assemblyDirectories = ServerConfiguration.AssemblyDirectories; + + foreach (var assemblyDir in assemblyDirectories) + { + var assemblyPath = PathUtility.GetFullPath(Path.Combine(assemblyDir, fileName), Core.BaseDirectory); + if (File.Exists(assemblyPath)) { - var assemblyPath = Path.Combine(assemblyDir, assemblyFile); - if (File.Exists(assemblyPath)) + var assemblyNameCheck = AssemblyName.GetAssemblyName(assemblyPath); + if (assemblyNameCheck.FullName == fullName) { return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); } } + } + return null; + } + + public static Assembly LoadAssemblyByFileName(string assemblyFile) + { + EnsureAssemblyDirectories(); + var assemblyDirectories = ServerConfiguration.AssemblyDirectories; + + foreach (var assemblyDir in assemblyDirectories) + { + var assemblyPath = Path.Combine(assemblyDir, assemblyFile); + if (File.Exists(assemblyPath)) + { + return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); + } + } + + return null; + } + + public static void LoadAssemblies(string[] files) + { + var assemblies = new Assembly[files.Length]; + + for (var i = 0; i < files.Length; i++) + { + var assemblyFile = files[i]; + var assembly = LoadAssemblyByFileName(assemblyFile); + if (assembly == null) + { + throw new FileNotFoundException( + $"Could not load file or assembly {assemblyFile}. The system cannot find the file specified. Review the assemblyDirectories field in {ServerConfiguration.ConfigurationFilePath}", + assemblyFile + ); + } + + assemblies[i] = assembly; + } + + Assemblies = assemblies; + } + + public static void Invoke(string method) + { + var invoke = new List(); + + Core.Assembly.AddMethods(method, invoke); + + for (var i = 0; i < Assemblies.Length; i++) + { + Assemblies[i].AddMethods(method, invoke); + } + + invoke.Sort(new CallPriorityComparer()); + + for (var i = 0; i < invoke.Count; ++i) + { + invoke[i].Invoke(null, null); + } + } + + private static void AddMethods(this Assembly assembly, string method, List list) + { + var types = assembly.GetTypes(); + + for (int i = 0; i < types.Length; i++) + { + var m = types[i].GetMethod(method, BindingFlags.Static | BindingFlags.Public); + if (m?.GetParameters().Length == 0) + { + list.Add(m); + } + } + } + + + public static TypeCache GetTypeCache(Assembly asm) + { + if (asm == null) + { + return m_NullCache ??= new TypeCache(null); + } + + if (m_TypeCaches.TryGetValue(asm, out var c)) + { + return c; + } + + return m_TypeCaches[asm] = new TypeCache(asm); + } + + public static Type FindTypeByFullName(string name, bool ignoreCase = true) => + FindTypeByName(name, true, ignoreCase); + + public static Type FindTypeByName(string name, bool fullName = false, bool ignoreCase = true) + { + if (string.IsNullOrWhiteSpace(name)) + { return null; } - public static void LoadAssemblies(string[] files) + for (var i = 0; i < Assemblies.Length; i++) { - var assemblies = new Assembly[files.Length]; - - for (var i = 0; i < files.Length; i++) - { - var assemblyFile = files[i]; - var assembly = LoadAssemblyByFileName(assemblyFile); - if (assembly == null) - { - throw new FileNotFoundException( - $"Could not load file or assembly {assemblyFile}. The system cannot find the file specified. Review the assemblyDirectories field in {ServerConfiguration.ConfigurationFilePath}", - assemblyFile - ); - } - - assemblies[i] = assembly; - } - - Assemblies = assemblies; - } - - public static void Invoke(string method) - { - var invoke = new List(); - - Core.Assembly.AddMethods(method, invoke); - - for (var i = 0; i < Assemblies.Length; i++) - { - Assemblies[i].AddMethods(method, invoke); - } - - invoke.Sort(new CallPriorityComparer()); - - for (var i = 0; i < invoke.Count; ++i) - { - invoke[i].Invoke(null, null); - } - } - - private static void AddMethods(this Assembly assembly, string method, List list) - { - var types = assembly.GetTypes(); - - for (int i = 0; i < types.Length; i++) - { - var m = types[i].GetMethod(method, BindingFlags.Static | BindingFlags.Public); - if (m?.GetParameters().Length == 0) - { - list.Add(m); - } - } - } - - - public static TypeCache GetTypeCache(Assembly asm) - { - if (asm == null) - { - return m_NullCache ??= new TypeCache(null); - } - - if (m_TypeCaches.TryGetValue(asm, out var c)) - { - return c; - } - - return m_TypeCaches[asm] = new TypeCache(asm); - } - - public static Type FindTypeByFullName(string name, bool ignoreCase = true) => - FindTypeByName(name, true, ignoreCase); - - public static Type FindTypeByName(string name, bool fullName = false, bool ignoreCase = true) - { - if (string.IsNullOrWhiteSpace(name)) - { - return null; - } - - for (var i = 0; i < Assemblies.Length; i++) - { - foreach (var type in GetTypeCache(Assemblies[i]).GetTypesByName(name, fullName, ignoreCase)) - { - return type; - } - } - - foreach(var type in GetTypeCache(Core.Assembly).GetTypesByName(name, fullName, ignoreCase)) + foreach (var type in GetTypeCache(Assemblies[i]).GetTypesByName(name, fullName, ignoreCase)) { return type; } + } - return null; + foreach(var type in GetTypeCache(Core.Assembly).GetTypesByName(name, fullName, ignoreCase)) + { + return type; + } + + return null; + } +} + +public class TypeCache +{ + private readonly Dictionary _nameMap = new(); + private readonly Dictionary _nameMapInsensitive = new(); + private readonly Dictionary _fullNameMap = new(); + private readonly Dictionary _fullNameMapInsensitive = new(); + + public TypeCache(Assembly asm) + { + Types = asm?.GetTypes() ?? Type.EmptyTypes; + + var nameMap = new Dictionary>(); + var nameMapInsensitive = new Dictionary>(); + var fullNameMap = new Dictionary>(); + var fullNameMapInsensitive = new Dictionary>(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void addTypeToRefs(int index, string fullTypeName) + { + var typeName = fullTypeName[(fullTypeName.LastIndexOf('.') + 1)..]; + AddToRefs(index, typeName, nameMap); + AddToRefs(index, typeName.ToLower(), nameMapInsensitive); + AddToRefs(index, fullTypeName, fullNameMap); + AddToRefs(index, fullTypeName.ToLower(), fullNameMapInsensitive); + } + + var aliasType = typeof(TypeAliasAttribute); + for (var i = 0; i < Types.Length; i++) + { + var current = Types[i]; + addTypeToRefs(i, current.FullName); + if (current.GetCustomAttribute(aliasType, false) is TypeAliasAttribute alias) + { + for (var j = 0; j < alias.Aliases.Length; j++) + { + addTypeToRefs(i, alias.Aliases[j]); + } + } + } + + foreach (var (key, value) in nameMap) + { + _nameMap[key] = value.ToArray(); + } + + foreach (var (key, value) in nameMapInsensitive) + { + _nameMapInsensitive[key] = value.ToArray(); + } + + foreach (var (key, value) in fullNameMap) + { + _fullNameMap[key] = value.ToArray(); + } + + foreach (var (key, value) in fullNameMapInsensitive) + { + _fullNameMapInsensitive[key] = value.ToArray(); } } - public class TypeCache + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AddToRefs(int index, string key, Dictionary> map) { - private readonly Dictionary _nameMap = new(); - private readonly Dictionary _nameMapInsensitive = new(); - private readonly Dictionary _fullNameMap = new(); - private readonly Dictionary _fullNameMapInsensitive = new(); - - public TypeCache(Assembly asm) + if (key == null) { - Types = asm?.GetTypes() ?? Type.EmptyTypes; + return; + } - var nameMap = new Dictionary>(); - var nameMapInsensitive = new Dictionary>(); - var fullNameMap = new Dictionary>(); - var fullNameMapInsensitive = new Dictionary>(); + if (map.TryGetValue(key, out var refs)) + { + refs.Add(index); + } + else + { + refs = new HashSet { index }; + map.Add(key, refs); + } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void addTypeToRefs(int index, string fullTypeName) - { - var typeName = fullTypeName[(fullTypeName.LastIndexOf('.') + 1)..]; - AddToRefs(index, typeName, nameMap); - AddToRefs(index, typeName.ToLower(), nameMapInsensitive); - AddToRefs(index, fullTypeName, fullNameMap); - AddToRefs(index, fullTypeName.ToLower(), fullNameMapInsensitive); - } + public Type[] Types { get; } - var aliasType = typeof(TypeAliasAttribute); - for (var i = 0; i < Types.Length; i++) - { - var current = Types[i]; - addTypeToRefs(i, current.FullName); - if (current.GetCustomAttribute(aliasType, false) is TypeAliasAttribute alias) - { - for (var j = 0; j < alias.Aliases.Length; j++) - { - addTypeToRefs(i, alias.Aliases[j]); - } - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TypeEnumerable GetTypesByName(string name, bool full, bool ignoreCase) => new(name, this, full, ignoreCase); - foreach (var (key, value) in nameMap) - { - _nameMap[key] = value.ToArray(); - } + public ref struct TypeEnumerable + { + private readonly TypeCache _cache; + private readonly string _name; + private readonly bool _ignoreCase; + private readonly bool _full; - foreach (var (key, value) in nameMapInsensitive) - { - _nameMapInsensitive[key] = value.ToArray(); - } - - foreach (var (key, value) in fullNameMap) - { - _fullNameMap[key] = value.ToArray(); - } - - foreach (var (key, value) in fullNameMapInsensitive) - { - _fullNameMapInsensitive[key] = value.ToArray(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TypeEnumerable(string name, TypeCache cache, bool full, bool ignoreCase) + { + _name = name; + _cache = cache; + _ignoreCase = ignoreCase; + _full = full; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddToRefs(int index, string key, Dictionary> map) - { - if (key == null) - { - return; - } + public TypeEnumerator GetEnumerator() => new(_name, _cache, _full, _ignoreCase); + } - if (map.TryGetValue(key, out var refs)) + public ref struct TypeEnumerator + { + private readonly TypeCache _cache; + private readonly int[] _values; + private int _index; + private Type _current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal TypeEnumerator(string name, TypeCache cache, bool full, bool ignoreCase) + { + _cache = cache; + + if (ignoreCase) { - refs.Add(index); + var map = full ? _cache._fullNameMapInsensitive : _cache._nameMapInsensitive; + _values = map.TryGetValue(name.ToLower(), out var values) ? values : Array.Empty(); } else { - refs = new HashSet { index }; - map.Add(key, refs); + var map = full ? _cache._fullNameMap : _cache._nameMap; + _values = map.TryGetValue(name, out var values) ? values : Array.Empty(); } - } - public Type[] Types { get; } + _index = 0; + _current = default; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TypeEnumerable GetTypesByName(string name, bool full, bool ignoreCase) => new(name, this, full, ignoreCase); - - public ref struct TypeEnumerable + public bool MoveNext() { - private readonly TypeCache _cache; - private readonly string _name; - private readonly bool _ignoreCase; - private readonly bool _full; + int[] localList = _values; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TypeEnumerable(string name, TypeCache cache, bool full, bool ignoreCase) + if ((uint)_index < (uint)localList.Length) { - _name = name; - _cache = cache; - _ignoreCase = ignoreCase; - _full = full; + _current = _cache.Types[_values[_index++]]; + + return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TypeEnumerator GetEnumerator() => new(_name, _cache, _full, _ignoreCase); + return false; } - public ref struct TypeEnumerator + public Type Current { - private readonly TypeCache _cache; - private readonly int[] _values; - private int _index; - private Type _current; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal TypeEnumerator(string name, TypeCache cache, bool full, bool ignoreCase) - { - _cache = cache; - - if (ignoreCase) - { - var map = full ? _cache._fullNameMapInsensitive : _cache._nameMapInsensitive; - _values = map.TryGetValue(name.ToLower(), out var values) ? values : Array.Empty(); - } - else - { - var map = full ? _cache._fullNameMap : _cache._nameMap; - _values = map.TryGetValue(name, out var values) ? values : Array.Empty(); - } - - _index = 0; - _current = default; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - int[] localList = _values; - - if ((uint)_index < (uint)localList.Length) - { - _current = _cache.Types[_values[_index++]]; - - return true; - } - - return false; - } - - public Type Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _current; - } + get => _current; } } } diff --git a/Projects/Server/Attributes.cs b/Projects/Server/Attributes.cs index 000e50bd7..f6b9275d4 100644 --- a/Projects/Server/Attributes.cs +++ b/Projects/Server/Attributes.cs @@ -2,140 +2,139 @@ using System; using System.Collections.Generic; using System.Reflection; -namespace Server +namespace Server; + +[AttributeUsage(AttributeTargets.Property)] +public class HueAttribute : Attribute { - [AttributeUsage(AttributeTargets.Property)] - public class HueAttribute : Attribute - { - } +} - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] - public class PropertyObjectAttribute : Attribute - { - } +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class PropertyObjectAttribute : Attribute +{ +} - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] - public class NoSortAttribute : Attribute - { - } +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class NoSortAttribute : Attribute +{ +} - [AttributeUsage(AttributeTargets.Method)] - public class CallPriorityAttribute : Attribute - { - public CallPriorityAttribute(int priority) => Priority = priority; +[AttributeUsage(AttributeTargets.Method)] +public class CallPriorityAttribute : Attribute +{ + public CallPriorityAttribute(int priority) => Priority = priority; - public int Priority { get; set; } - } + public int Priority { get; set; } +} - public class CallPriorityComparer : IComparer +public class CallPriorityComparer : IComparer +{ + public int Compare(MethodInfo x, MethodInfo y) { - public int Compare(MethodInfo x, MethodInfo y) + if (x == null && y == null) { - if (x == null && y == null) - { - return 0; - } - - if (x == null) - { - return 1; - } - - if (y == null) - { - return -1; - } - - var xPriority = GetPriority(x); - var yPriority = GetPriority(y); - - if (xPriority > yPriority) - { - return 1; - } - - if (xPriority < yPriority) - { - return -1; - } - return 0; } - private int GetPriority(MethodInfo mi) - { - var objs = mi.GetCustomAttributes(typeof(CallPriorityAttribute), true); - - if (objs.Length == 0) - { - return 50; - } - - if (objs[0] is not CallPriorityAttribute attr) - { - return 50; - } - - return attr.Priority; - } - } - - [AttributeUsage(AttributeTargets.Class)] - public class TypeAliasAttribute : Attribute - { - public TypeAliasAttribute(params string[] aliases) => Aliases = aliases; - - public string[] Aliases { get; } - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] - public class ParsableAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] - public class CustomEnumAttribute : Attribute - { - public CustomEnumAttribute(string[] names) => Names = names; - - public string[] Names { get; } - } - - [AttributeUsage(AttributeTargets.Constructor)] - public class ConstructibleAttribute : Attribute - { - public ConstructibleAttribute() : - this(AccessLevel.Player) // Lowest accesslevel for current functionality (Level determined by access to [add) + if (x == null) { + return 1; } - public ConstructibleAttribute(AccessLevel accessLevel) => AccessLevel = accessLevel; + if (y == null) + { + return -1; + } - public AccessLevel AccessLevel { get; set; } + var xPriority = GetPriority(x); + var yPriority = GetPriority(y); + + if (xPriority > yPriority) + { + return 1; + } + + if (xPriority < yPriority) + { + return -1; + } + + return 0; } - [AttributeUsage(AttributeTargets.Property)] - public class CommandPropertyAttribute : Attribute + private int GetPriority(MethodInfo mi) { - public CommandPropertyAttribute( - AccessLevel level, - bool readOnly = false, - bool canModify = false - ) : this(level, level) + var objs = mi.GetCustomAttributes(typeof(CallPriorityAttribute), true); + + if (objs.Length == 0) { - ReadOnly = readOnly; - CanModify = canModify; + return 50; } - public CommandPropertyAttribute(AccessLevel readLevel, AccessLevel writeLevel) + if (objs[0] is not CallPriorityAttribute attr) { - ReadLevel = readLevel; - WriteLevel = writeLevel; + return 50; } - public AccessLevel ReadLevel { get; } - public AccessLevel WriteLevel { get; } - public bool ReadOnly { get; } - public bool CanModify { get; } + return attr.Priority; } } + +[AttributeUsage(AttributeTargets.Class)] +public class TypeAliasAttribute : Attribute +{ + public TypeAliasAttribute(params string[] aliases) => Aliases = aliases; + + public string[] Aliases { get; } +} + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class ParsableAttribute : Attribute +{ +} + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] +public class CustomEnumAttribute : Attribute +{ + public CustomEnumAttribute(string[] names) => Names = names; + + public string[] Names { get; } +} + +[AttributeUsage(AttributeTargets.Constructor)] +public class ConstructibleAttribute : Attribute +{ + public ConstructibleAttribute() : + this(AccessLevel.Player) // Lowest accesslevel for current functionality (Level determined by access to [add) + { + } + + public ConstructibleAttribute(AccessLevel accessLevel) => AccessLevel = accessLevel; + + public AccessLevel AccessLevel { get; set; } +} + +[AttributeUsage(AttributeTargets.Property)] +public class CommandPropertyAttribute : Attribute +{ + public CommandPropertyAttribute( + AccessLevel level, + bool readOnly = false, + bool canModify = false + ) : this(level, level) + { + ReadOnly = readOnly; + CanModify = canModify; + } + + public CommandPropertyAttribute(AccessLevel readLevel, AccessLevel writeLevel) + { + ReadLevel = readLevel; + WriteLevel = writeLevel; + } + + public AccessLevel ReadLevel { get; } + public AccessLevel WriteLevel { get; } + public bool ReadOnly { get; } + public bool CanModify { get; } +} diff --git a/Projects/Server/Buffers/CircularBuffer.cs b/Projects/Server/Buffers/CircularBuffer.cs index 20a9d3a55..059ec4f74 100644 --- a/Projects/Server/Buffers/CircularBuffer.cs +++ b/Projects/Server/Buffers/CircularBuffer.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CircularBuffer.cs * * * @@ -13,128 +13,127 @@ * along with this program. If not, see . * *************************************************************************/ -namespace System.Buffers +namespace System.Buffers; + +public readonly ref struct CircularBuffer { - public readonly ref struct CircularBuffer + private readonly Span _first; + private readonly Span _second; + + public int Length { get; } + + public CircularBuffer(ArraySegment[] buffers) : this(buffers[0], buffers[1]) { - private readonly Span _first; - private readonly Span _second; + } - public int Length { get; } + public CircularBuffer(Span first, Span second) + { + _first = first; + _second = second; + Length = first.Length + second.Length; + } - public CircularBuffer(ArraySegment[] buffers) : this(buffers[0], buffers[1]) + public T this[int index] + { + get { - } - - public CircularBuffer(Span first, Span second) - { - _first = first; - _second = second; - Length = first.Length + second.Length; - } - - public T this[int index] - { - get + if (index < 0 || index > Length) { - if (index < 0 || index > Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return index < _first.Length ? _first[index] : _second[index - _first.Length]; + throw new ArgumentOutOfRangeException(nameof(index)); } - set - { - if (index < 0 || index > Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - if (index < _first.Length) - { - _first[index] = value; - } - else - { - _second[index - _first.Length] = value; - } + return index < _first.Length ? _first[index] : _second[index - _first.Length]; + } + set + { + if (index < 0 || index > Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (index < _first.Length) + { + _first[index] = value; + } + else + { + _second[index - _first.Length] = value; } } + } - public void CopyFrom(ReadOnlySpan bytes) + public void CopyFrom(ReadOnlySpan bytes) + { + var remaining = bytes.Length; + var offset = 0; + + if (remaining == 0) { - var remaining = bytes.Length; - var offset = 0; + return; + } + + for (int i = 0; i < 2; i++) + { + var buffer = i == 0 ? _first : _second; + + if (buffer.Length == 0) + { + continue; + } + + var sz = Math.Min(remaining, buffer.Length); + bytes.Slice(offset, sz).CopyTo(buffer); + + remaining -= sz; + offset += sz; if (remaining == 0) { return; } - - for (int i = 0; i < 2; i++) - { - var buffer = i == 0 ? _first : _second; - - if (buffer.Length == 0) - { - continue; - } - - var sz = Math.Min(remaining, buffer.Length); - bytes.Slice(offset, sz).CopyTo(buffer); - - remaining -= sz; - offset += sz; - - if (remaining == 0) - { - return; - } - } - - throw new OutOfMemoryException(); } - public void CopyTo(Span bytes) + throw new OutOfMemoryException(); + } + + public void CopyTo(Span bytes) + { + if (bytes.Length < Length) { - if (bytes.Length < Length) - { - throw new ArgumentOutOfRangeException(nameof(bytes)); - } - - if (_first.Length > 0) - { - _first.CopyTo(bytes); - } - - if (_second.Length > 0) - { - _second.CopyTo(bytes[_first.Length..]); - } + throw new ArgumentOutOfRangeException(nameof(bytes)); } - public CircularBuffer Slice(int offset, int count) + if (_first.Length > 0) { - var firstCount = Math.Min(count, _first.Length - offset); - var first = offset < _first.Length - ? _first.Slice(offset, firstCount) - : Span.Empty; - - var secondCount = offset > _first.Length ? count : count - firstCount; - var second = secondCount > 0 ? _second.Slice(Math.Max(0, offset - _first.Length), secondCount) : Span.Empty; - - return new CircularBuffer(first, second); + _first.CopyTo(bytes); } - public Span GetSpan(int index) + if (_second.Length > 0) { - if (index is < 0 or > 1) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return index == 0 ? _first : _second; + _second.CopyTo(bytes[_first.Length..]); } } + + public CircularBuffer Slice(int offset, int count) + { + var firstCount = Math.Min(count, _first.Length - offset); + var first = offset < _first.Length + ? _first.Slice(offset, firstCount) + : Span.Empty; + + var secondCount = offset > _first.Length ? count : count - firstCount; + var second = secondCount > 0 ? _second.Slice(Math.Max(0, offset - _first.Length), secondCount) : Span.Empty; + + return new CircularBuffer(first, second); + } + + public Span GetSpan(int index) + { + if (index is < 0 or > 1) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return index == 0 ? _first : _second; + } } diff --git a/Projects/Server/Buffers/CircularBufferReader.cs b/Projects/Server/Buffers/CircularBufferReader.cs index aa8b9aa08..6ad86409d 100644 --- a/Projects/Server/Buffers/CircularBufferReader.cs +++ b/Projects/Server/Buffers/CircularBufferReader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PacketReader.cs * * * @@ -21,375 +21,374 @@ using System.Runtime.CompilerServices; using System.Text; using Server.Text; -namespace Server.Network +namespace Server.Network; + +public ref struct CircularBufferReader { - public ref struct CircularBufferReader + private readonly ReadOnlySpan _first; + private readonly ReadOnlySpan _second; + + public int Length { get; } + public int Position { get; private set; } + public int Remaining => Length - Position; + + // Only used for debugging! + public ReadOnlySpan First => _first; + public ReadOnlySpan Second => _second; + + public CircularBufferReader(ref CircularBuffer buffer) : this(buffer.GetSpan(0), buffer.GetSpan(1)) { - private readonly ReadOnlySpan _first; - private readonly ReadOnlySpan _second; + } - public int Length { get; } - public int Position { get; private set; } - public int Remaining => Length - Position; + public CircularBufferReader(ArraySegment[] buffer) : this(buffer[0], buffer[1]) + { + } - // Only used for debugging! - public ReadOnlySpan First => _first; - public ReadOnlySpan Second => _second; + public CircularBufferReader(ReadOnlySpan first, ReadOnlySpan second) + { + _first = first; + _second = second; + Position = 0; + Length = first.Length + second.Length; + } - public CircularBufferReader(ref CircularBuffer buffer) : this(buffer.GetSpan(0), buffer.GetSpan(1)) + public void Trace(NetState state) + { + // We don't have data, so nothing to trace + if (_first.Length == 0) { + return; } - public CircularBufferReader(ArraySegment[] buffer) : this(buffer[0], buffer[1]) + try { + using var sw = new StreamWriter("unhandled-packets.log", true); + sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, _first[0]); + sw.FormatBuffer(_first, _second, Length); + sw.WriteLine(); + sw.WriteLine(); + } + catch + { + // ignored + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte() + { + if (Position < _first.Length) + { + return _first[Position++]; } - public CircularBufferReader(ReadOnlySpan first, ReadOnlySpan second) + if (Position < Length) { - _first = first; - _second = second; - Position = 0; - Length = first.Length + second.Length; + return _second[Position++ - _first.Length]; } - public void Trace(NetState state) - { - // We don't have data, so nothing to trace - if (_first.Length == 0) - { - return; - } + throw new OutOfMemoryException(); + } - try + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBoolean() => ReadByte() > 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadSByte() => (sbyte)ReadByte(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16() + { + short value; + + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryReadInt16BigEndian(_first[Position..], out value)) { - using var sw = new StreamWriter("unhandled-packets.log", true); - sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, _first[0]); - sw.FormatBuffer(_first, _second, Length); - sw.WriteLine(); - sw.WriteLine(); - } - catch - { - // ignored + // Not enough space. Split the spans + return (short)((ReadByte() >> 8) | ReadByte()); } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte() + else if (!BinaryPrimitives.TryReadInt16BigEndian(_second[(Position - _first.Length)..], out value)) { - if (Position < _first.Length) - { - return _first[Position++]; - } - - if (Position < Length) - { - return _second[Position++ - _first.Length]; - } - throw new OutOfMemoryException(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ReadBoolean() => ReadByte() > 0; + Position += 2; + return value; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte() => (sbyte)ReadByte(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16() + { + ushort value; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16() + if (Position < _first.Length) { - short value; - - if (Position < _first.Length) + if (!BinaryPrimitives.TryReadUInt16BigEndian(_first[Position..], out value)) { - if (!BinaryPrimitives.TryReadInt16BigEndian(_first[Position..], out value)) - { - // Not enough space. Split the spans - return (short)((ReadByte() >> 8) | ReadByte()); - } + // Not enough space. Split the spans + return (ushort)((ReadByte() >> 8) | ReadByte()); } - else if (!BinaryPrimitives.TryReadInt16BigEndian(_second[(Position - _first.Length)..], out value)) + } + else if (!BinaryPrimitives.TryReadUInt16BigEndian(_second[(Position - _first.Length)..], out value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32() + { + int value; + + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryReadInt32BigEndian(_first[Position..], out value)) + { + // Not enough space. Split the spans + return (ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte(); + } + } + else if (!BinaryPrimitives.TryReadInt32BigEndian(_second[(Position - _first.Length)..], out value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32() + { + uint value; + + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryReadUInt32BigEndian(_first[Position..], out value)) + { + // Not enough space. Split the spans + return (uint)((ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte()); + } + } + else if (!BinaryPrimitives.TryReadUInt32BigEndian(_second[(Position - _first.Length)..], out value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64() + { + long value; + + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryReadInt64BigEndian(_first[Position..], out value)) + { + // Not enough space. Split the spans + return ((long)ReadByte() >> 56) | + ((long)ReadByte() >> 48) | + ((long)ReadByte() >> 40) | + ((long)ReadByte() >> 32) | + ((long)ReadByte() >> 24) | + ((long)ReadByte() >> 16) | + ((long)ReadByte() >> 8) | + ReadByte(); + } + } + else if (!BinaryPrimitives.TryReadInt64BigEndian(_second[(Position - _first.Length)..], out value)) + { + throw new OutOfMemoryException(); + } + + Position += 8; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64() + { + ulong value; + + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryReadUInt64BigEndian(_first[Position..], out value)) + { + // Not enough space. Split the spans + return ((ulong)ReadByte() >> 56) | + ((ulong)ReadByte() >> 48) | + ((ulong)ReadByte() >> 40) | + ((ulong)ReadByte() >> 32) | + ((ulong)ReadByte() >> 24) | + ((ulong)ReadByte() >> 16) | + ((ulong)ReadByte() >> 8) | + ReadByte(); + } + } + else if (!BinaryPrimitives.TryReadUInt64BigEndian(_second[(Position - _first.Length)..], out value)) + { + throw new OutOfMemoryException(); + } + + Position += 8; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) + { + int byteLength = encoding.GetByteLengthForEncoding(); + + bool isFixedLength = fixedLength > -1; + + var remaining = Remaining; + int size; + + if (isFixedLength) + { + size = fixedLength * byteLength; + if (size > Remaining) { throw new OutOfMemoryException(); } - - Position += 2; - return value; + } + else + { + size = remaining - (remaining & (byteLength - 1)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16() + ReadOnlySpan span; + int index; + + if (Position < _first.Length) { - ushort value; + var firstLength = Math.Min(_first.Length - Position, size); - if (Position < _first.Length) + // Find terminator + index = _first.Slice(Position, firstLength).IndexOfTerminator(byteLength); + + if (index < 0) { - if (!BinaryPrimitives.TryReadUInt16BigEndian(_first[Position..], out value)) + remaining = size - firstLength; + // We don't have a terminator, but a fixed size to the end of the first span, so stop there + if (remaining <= 0) { - // Not enough space. Split the spans - return (ushort)((ReadByte() >> 8) | ReadByte()); - } - } - else if (!BinaryPrimitives.TryReadUInt16BigEndian(_second[(Position - _first.Length)..], out value)) - { - throw new OutOfMemoryException(); - } - - Position += 2; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32() - { - int value; - - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryReadInt32BigEndian(_first[Position..], out value)) - { - // Not enough space. Split the spans - return (ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte(); - } - } - else if (!BinaryPrimitives.TryReadInt32BigEndian(_second[(Position - _first.Length)..], out value)) - { - throw new OutOfMemoryException(); - } - - Position += 4; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32() - { - uint value; - - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryReadUInt32BigEndian(_first[Position..], out value)) - { - // Not enough space. Split the spans - return (uint)((ReadByte() >> 24) | (ReadByte() >> 16) | (ReadByte() >> 8) | ReadByte()); - } - } - else if (!BinaryPrimitives.TryReadUInt32BigEndian(_second[(Position - _first.Length)..], out value)) - { - throw new OutOfMemoryException(); - } - - Position += 4; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadInt64() - { - long value; - - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryReadInt64BigEndian(_first[Position..], out value)) - { - // Not enough space. Split the spans - return ((long)ReadByte() >> 56) | - ((long)ReadByte() >> 48) | - ((long)ReadByte() >> 40) | - ((long)ReadByte() >> 32) | - ((long)ReadByte() >> 24) | - ((long)ReadByte() >> 16) | - ((long)ReadByte() >> 8) | - ReadByte(); - } - } - else if (!BinaryPrimitives.TryReadInt64BigEndian(_second[(Position - _first.Length)..], out value)) - { - throw new OutOfMemoryException(); - } - - Position += 8; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadUInt64() - { - ulong value; - - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryReadUInt64BigEndian(_first[Position..], out value)) - { - // Not enough space. Split the spans - return ((ulong)ReadByte() >> 56) | - ((ulong)ReadByte() >> 48) | - ((ulong)ReadByte() >> 40) | - ((ulong)ReadByte() >> 32) | - ((ulong)ReadByte() >> 24) | - ((ulong)ReadByte() >> 16) | - ((ulong)ReadByte() >> 8) | - ReadByte(); - } - } - else if (!BinaryPrimitives.TryReadUInt64BigEndian(_second[(Position - _first.Length)..], out value)) - { - throw new OutOfMemoryException(); - } - - Position += 8; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) - { - int byteLength = encoding.GetByteLengthForEncoding(); - - bool isFixedLength = fixedLength > -1; - - var remaining = Remaining; - int size; - - if (isFixedLength) - { - size = fixedLength * byteLength; - if (size > Remaining) - { - throw new OutOfMemoryException(); - } - } - else - { - size = remaining - (remaining & (byteLength - 1)); - } - - ReadOnlySpan span; - int index; - - if (Position < _first.Length) - { - var firstLength = Math.Min(_first.Length - Position, size); - - // Find terminator - index = _first.Slice(Position, firstLength).IndexOfTerminator(byteLength); - - if (index < 0) - { - remaining = size - firstLength; - // We don't have a terminator, but a fixed size to the end of the first span, so stop there - if (remaining <= 0) - { - index = firstLength; - } - else - { - index = _second[..remaining].IndexOfTerminator(byteLength); - - int secondLength = index < 0 ? remaining : index; - int length = firstLength + secondLength; - - // Assume no strings should be too long for the stack - Span bytes = stackalloc byte[length]; - _first[Position..].CopyTo(bytes); - _second[..secondLength].CopyTo(bytes[firstLength..]); - - Position += length + (index >= 0 ? byteLength : 0); - return TextEncoding.GetString(bytes, encoding, safeString); - } - } - - span = _first.Slice(Position, index); - } - else - { - size = Math.Min(remaining, size); - span = _second.Slice( Position - _first.Length, size); - index = span.IndexOfTerminator(byteLength); - - if (index >= 0) - { - span = span[..index]; + index = firstLength; } else { - index = size; + index = _second[..remaining].IndexOfTerminator(byteLength); + + int secondLength = index < 0 ? remaining : index; + int length = firstLength + secondLength; + + // Assume no strings should be too long for the stack + Span bytes = stackalloc byte[length]; + _first[Position..].CopyTo(bytes); + _second[..secondLength].CopyTo(bytes[firstLength..]); + + Position += length + (index >= 0 ? byteLength : 0); + return TextEncoding.GetString(bytes, encoding, safeString); } } - Position += isFixedLength ? size : index + byteLength; - return TextEncoding.GetString(span, encoding, safeString); + span = _first.Slice(Position, index); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUniSafe(int fixedLength) => ReadString(TextEncoding.UnicodeLE, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUniSafe() => ReadString(TextEncoding.UnicodeLE, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUni(int fixedLength) => ReadString(TextEncoding.UnicodeLE, false, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUni() => ReadString(TextEncoding.UnicodeLE); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUniSafe(int fixedLength) => ReadString(TextEncoding.Unicode, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUniSafe() => ReadString(TextEncoding.Unicode, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUni(int fixedLength) => ReadString(TextEncoding.Unicode, false, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUni() => ReadString(TextEncoding.Unicode); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8Safe(int fixedLength) => ReadString(TextEncoding.UTF8, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8Safe() => ReadString(TextEncoding.UTF8, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8() => ReadString(TextEncoding.UTF8); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAscii() => ReadString(Encoding.ASCII); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Seek(int offset, SeekOrigin origin) => - Position = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.End => Length + offset, - _ => Position + offset // Current - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Read(Span bytes) + else { - if (bytes.Length < Length) - { - throw new ArgumentOutOfRangeException(nameof(bytes)); - } + size = Math.Min(remaining, size); + span = _second.Slice( Position - _first.Length, size); + index = span.IndexOfTerminator(byteLength); - if (_first.Length > 0 && !_first.TryCopyTo(bytes)) + if (index >= 0) { - return false; + span = span[..index]; + } + else + { + index = size; } - - return _second.Length <= 0 || _second.TryCopyTo(bytes[_first.Length..]); } + + Position += isFixedLength ? size : index + byteLength; + return TextEncoding.GetString(span, encoding, safeString); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUniSafe(int fixedLength) => ReadString(TextEncoding.UnicodeLE, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUniSafe() => ReadString(TextEncoding.UnicodeLE, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUni(int fixedLength) => ReadString(TextEncoding.UnicodeLE, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUni() => ReadString(TextEncoding.UnicodeLE); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUniSafe(int fixedLength) => ReadString(TextEncoding.Unicode, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUniSafe() => ReadString(TextEncoding.Unicode, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUni(int fixedLength) => ReadString(TextEncoding.Unicode, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUni() => ReadString(TextEncoding.Unicode); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8Safe(int fixedLength) => ReadString(TextEncoding.UTF8, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8Safe() => ReadString(TextEncoding.UTF8, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8() => ReadString(TextEncoding.UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAscii() => ReadString(Encoding.ASCII); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Seek(int offset, SeekOrigin origin) => + Position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.End => Length + offset, + _ => Position + offset // Current + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Read(Span bytes) + { + if (bytes.Length < Length) + { + throw new ArgumentOutOfRangeException(nameof(bytes)); + } + + if (_first.Length > 0 && !_first.TryCopyTo(bytes)) + { + return false; + } + + return _second.Length <= 0 || _second.TryCopyTo(bytes[_first.Length..]); } } diff --git a/Projects/Server/Buffers/CircularBufferWriter.cs b/Projects/Server/Buffers/CircularBufferWriter.cs index 2bcdc2e29..ba7ceeda0 100644 --- a/Projects/Server/Buffers/CircularBufferWriter.cs +++ b/Projects/Server/Buffers/CircularBufferWriter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CircularBufferWriter.cs * * * @@ -19,505 +19,504 @@ using System.Runtime.CompilerServices; using System.Text; using Server.Text; -namespace System.Buffers +namespace System.Buffers; + +public ref struct CircularBufferWriter { - public ref struct CircularBufferWriter + private readonly Span _first; + private readonly Span _second; + + public int Length { get; } + public int Position { get; private set; } + + public CircularBufferWriter(CircularBuffer buffer) : this(buffer.GetSpan(0), buffer.GetSpan(1)) { - private readonly Span _first; - private readonly Span _second; - - public int Length { get; } - public int Position { get; private set; } - - public CircularBufferWriter(CircularBuffer buffer) : this(buffer.GetSpan(0), buffer.GetSpan(1)) - { - } - - public CircularBufferWriter(ArraySegment[] buffer) : this(buffer[0], buffer[1]) - { - } - - public CircularBufferWriter(Span first, Span second) - { - _first = first; - _second = second; - Position = 0; - Length = first.Length + second.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(byte value) - { - if (Position < _first.Length) - { - _first[Position++] = value; - } - else if (Position < Length) - { - _second[Position++ - _first.Length] = value; - } - else - { - throw new OutOfMemoryException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(bool value) - { - if (value) - { - Write((byte)1); - } - else - { - Write((byte)0); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(sbyte value) - { - Write((byte)value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(short value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteInt16BigEndian(_first[Position..], value)) - { - if (BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 8)); - Write((byte)value); - } - else - { - Position += 2; - } - } - else if (BinaryPrimitives.TryWriteInt16BigEndian(_second[(Position - _first.Length)..], value)) - { - Position += 2; - } - else - { - throw new OutOfMemoryException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ushort value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteUInt16BigEndian(_first[Position..], value)) - { - if (BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 8)); - Write((byte)value); - } - else - { - Position += 2; - } - } - else if (BinaryPrimitives.TryWriteUInt16BigEndian(_second[(Position - _first.Length)..], value)) - { - Position += 2; - } - else - { - throw new OutOfMemoryException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(int value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteInt32BigEndian(_first[Position..], value)) - { - if (BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 24)); - Write((byte)(value >> 16)); - Write((byte)(value >> 8)); - Write((byte)value); - } - else - { - Position += 4; - } - } - else if (BinaryPrimitives.TryWriteInt32BigEndian(_second[(Position - _first.Length)..], value)) - { - Position += 4; - } - else - { - throw new OutOfMemoryException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLE(int value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteInt32LittleEndian(_first[Position..], value)) - { - if (!BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 24)); - Write((byte)(value >> 16)); - Write((byte)(value >> 8)); - Write((byte)value); - } - else - { - Position += 4; - } - } - else if (BinaryPrimitives.TryWriteInt32LittleEndian(_second[(Position - _first.Length)..], value)) - { - Position += 4; - } - else - { - throw new OutOfMemoryException(); - } - } - - /// - /// Writes a 4-byte unsigned integer value to the underlying stream. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(uint value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteUInt32BigEndian(_first[Position..], value)) - { - if (BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 24)); - Write((byte)(value >> 16)); - Write((byte)(value >> 8)); - Write((byte)value); - } - else - { - Position += 4; - } - } - else if (BinaryPrimitives.TryWriteUInt32BigEndian(_second[(Position - _first.Length)..], value)) - { - Position += 4; - } - else - { - throw new OutOfMemoryException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLE(uint value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteUInt32LittleEndian(_first[Position..], value)) - { - if (!BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 24)); - Write((byte)(value >> 16)); - Write((byte)(value >> 8)); - Write((byte)value); - } - else - { - Position += 4; - } - } - else if (BinaryPrimitives.TryWriteUInt32LittleEndian(_second[(Position - _first.Length)..], value)) - { - Position += 4; - } - else - { - throw new OutOfMemoryException(); - } - } - - /// - /// Writes a 8-byte signed integer value to the underlying stream. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(long value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteInt64BigEndian(_first[Position..], value)) - { - if (BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 56)); - Write((byte)(value >> 48)); - Write((byte)(value >> 40)); - Write((byte)(value >> 32)); - Write((byte)(value >> 24)); - Write((byte)(value >> 16)); - Write((byte)(value >> 8)); - Write((byte)value); - - } - else - { - Position += 8; - } - } - else if (BinaryPrimitives.TryWriteInt64BigEndian(_second[(Position - _first.Length)..], value)) - { - Position += 8; - } - else - { - throw new OutOfMemoryException(); - } - } - - /// - /// Writes a 8-byte unsigned integer value to the underlying stream. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ulong value) - { - if (Position < _first.Length) - { - if (!BinaryPrimitives.TryWriteUInt64BigEndian(_first[Position..], value)) - { - if (BitConverter.IsLittleEndian) - { - value = BinaryPrimitives.ReverseEndianness(value); - } - - Write((byte)(value >> 56)); - Write((byte)(value >> 48)); - Write((byte)(value >> 40)); - Write((byte)(value >> 32)); - Write((byte)(value >> 24)); - Write((byte)(value >> 16)); - Write((byte)(value >> 8)); - Write((byte)value); - } - else - { - Position += 8; - } - } - else if (BinaryPrimitives.TryWriteUInt64BigEndian(_second[(Position - _first.Length)..], value)) - { - Position += 8; - } - else - { - throw new OutOfMemoryException(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ReadOnlySpan buffer) - { - if (Position + buffer.Length > Length) - { - throw new OutOfMemoryException(); - } - - if (Position < _first.Length) - { - var sz = Math.Min(buffer.Length, _first.Length - Position); - buffer[..sz].CopyTo(_first[Position..]); - if (sz < buffer.Length) - { - buffer[sz..].CopyTo(_second); - } - } - else if (Position < Length) - { - buffer.CopyTo(_second[(Position - _first.Length)..]); - } - else - { - throw new OutOfMemoryException(); - } - - Position += buffer.Length; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteString(ReadOnlySpan value, Encoding encoding) - { - var byteCount = encoding.GetByteCount(value); - - if (Position + byteCount > Length) - { - throw new OutOfMemoryException(); - } - - Span bytes = stackalloc byte[byteCount]; - encoding.GetBytes(value, bytes); - - int count; - if (Position < _first.Length) - { - count = Math.Min(_first.Length - Position, byteCount); - bytes[..count].CopyTo(_first[Position..]); - byteCount -= count; - Position += count; - } - else - { - count = 0; - } - - if (byteCount > 0) - { - bytes[count..].CopyTo(_second[Math.Max(0, Position - _first.Length)..]); - Position += byteCount; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLittleUni(string value, int fixedLength = -1) - { - if (fixedLength < 0) { fixedLength = value.Length; } - - WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), TextEncoding.UnicodeLE); - var count = fixedLength - value.Length; - if (count > 0) - { - Clear(count * 2); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLittleUniNull(string value) - { - WriteString(value, TextEncoding.UnicodeLE); - Write((ushort)0); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteBigUni(string value, int fixedLength = -1) - { - if (fixedLength < 0) { fixedLength = value.Length; } - - WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), TextEncoding.Unicode); - var count = fixedLength - value.Length; - if (count > 0) - { - Clear(count * 2); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteBigUniNull(string value) - { - WriteString(value, TextEncoding.Unicode); - Write((ushort)0); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUTF8(string value) => WriteString(value, TextEncoding.UTF8); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteUTF8Null(string value) - { - WriteString(value, TextEncoding.UTF8); - Write((byte)0); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteAscii(string value, int fixedLength = -1) - { - if (fixedLength < 0) { fixedLength = value.Length; } - - WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), Encoding.ASCII); - var count = fixedLength - value.Length; - if (count > 0) - { - Clear(count); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteAsciiNull(string value) - { - WriteString(value, Encoding.ASCII); - Write((byte)0); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(int amount) - { - if (Position + amount > Length) - { - throw new OutOfMemoryException(); - } - - int count; - if (Position < _first.Length) - { - count = Math.Min(amount, _first.Length - Position); - _first.Slice(Position, count).Clear(); - count = amount - count; - } - else - { - count = amount; - } - - if (count > 0) - { - _second.Slice(Position - _first.Length, count).Clear(); - } - - Position += amount; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Seek(int offset, SeekOrigin origin) => - Position = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.End => Length + offset, - _ => Position + offset // Current - }; } + + public CircularBufferWriter(ArraySegment[] buffer) : this(buffer[0], buffer[1]) + { + } + + public CircularBufferWriter(Span first, Span second) + { + _first = first; + _second = second; + Position = 0; + Length = first.Length + second.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(byte value) + { + if (Position < _first.Length) + { + _first[Position++] = value; + } + else if (Position < Length) + { + _second[Position++ - _first.Length] = value; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(bool value) + { + if (value) + { + Write((byte)1); + } + else + { + Write((byte)0); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(sbyte value) + { + Write((byte)value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(short value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteInt16BigEndian(_first[Position..], value)) + { + if (BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 2; + } + } + else if (BinaryPrimitives.TryWriteInt16BigEndian(_second[(Position - _first.Length)..], value)) + { + Position += 2; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ushort value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteUInt16BigEndian(_first[Position..], value)) + { + if (BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 2; + } + } + else if (BinaryPrimitives.TryWriteUInt16BigEndian(_second[(Position - _first.Length)..], value)) + { + Position += 2; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(int value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteInt32BigEndian(_first[Position..], value)) + { + if (BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 4; + } + } + else if (BinaryPrimitives.TryWriteInt32BigEndian(_second[(Position - _first.Length)..], value)) + { + Position += 4; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(int value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteInt32LittleEndian(_first[Position..], value)) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 4; + } + } + else if (BinaryPrimitives.TryWriteInt32LittleEndian(_second[(Position - _first.Length)..], value)) + { + Position += 4; + } + else + { + throw new OutOfMemoryException(); + } + } + + /// + /// Writes a 4-byte unsigned integer value to the underlying stream. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(uint value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteUInt32BigEndian(_first[Position..], value)) + { + if (BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 4; + } + } + else if (BinaryPrimitives.TryWriteUInt32BigEndian(_second[(Position - _first.Length)..], value)) + { + Position += 4; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLE(uint value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteUInt32LittleEndian(_first[Position..], value)) + { + if (!BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 4; + } + } + else if (BinaryPrimitives.TryWriteUInt32LittleEndian(_second[(Position - _first.Length)..], value)) + { + Position += 4; + } + else + { + throw new OutOfMemoryException(); + } + } + + /// + /// Writes a 8-byte signed integer value to the underlying stream. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(long value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteInt64BigEndian(_first[Position..], value)) + { + if (BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 56)); + Write((byte)(value >> 48)); + Write((byte)(value >> 40)); + Write((byte)(value >> 32)); + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + + } + else + { + Position += 8; + } + } + else if (BinaryPrimitives.TryWriteInt64BigEndian(_second[(Position - _first.Length)..], value)) + { + Position += 8; + } + else + { + throw new OutOfMemoryException(); + } + } + + /// + /// Writes a 8-byte unsigned integer value to the underlying stream. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ulong value) + { + if (Position < _first.Length) + { + if (!BinaryPrimitives.TryWriteUInt64BigEndian(_first[Position..], value)) + { + if (BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + Write((byte)(value >> 56)); + Write((byte)(value >> 48)); + Write((byte)(value >> 40)); + Write((byte)(value >> 32)); + Write((byte)(value >> 24)); + Write((byte)(value >> 16)); + Write((byte)(value >> 8)); + Write((byte)value); + } + else + { + Position += 8; + } + } + else if (BinaryPrimitives.TryWriteUInt64BigEndian(_second[(Position - _first.Length)..], value)) + { + Position += 8; + } + else + { + throw new OutOfMemoryException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan buffer) + { + if (Position + buffer.Length > Length) + { + throw new OutOfMemoryException(); + } + + if (Position < _first.Length) + { + var sz = Math.Min(buffer.Length, _first.Length - Position); + buffer[..sz].CopyTo(_first[Position..]); + if (sz < buffer.Length) + { + buffer[sz..].CopyTo(_second); + } + } + else if (Position < Length) + { + buffer.CopyTo(_second[(Position - _first.Length)..]); + } + else + { + throw new OutOfMemoryException(); + } + + Position += buffer.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteString(ReadOnlySpan value, Encoding encoding) + { + var byteCount = encoding.GetByteCount(value); + + if (Position + byteCount > Length) + { + throw new OutOfMemoryException(); + } + + Span bytes = stackalloc byte[byteCount]; + encoding.GetBytes(value, bytes); + + int count; + if (Position < _first.Length) + { + count = Math.Min(_first.Length - Position, byteCount); + bytes[..count].CopyTo(_first[Position..]); + byteCount -= count; + Position += count; + } + else + { + count = 0; + } + + if (byteCount > 0) + { + bytes[count..].CopyTo(_second[Math.Max(0, Position - _first.Length)..]); + Position += byteCount; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUni(string value, int fixedLength = -1) + { + if (fixedLength < 0) { fixedLength = value.Length; } + + WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), TextEncoding.UnicodeLE); + var count = fixedLength - value.Length; + if (count > 0) + { + Clear(count * 2); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLittleUniNull(string value) + { + WriteString(value, TextEncoding.UnicodeLE); + Write((ushort)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUni(string value, int fixedLength = -1) + { + if (fixedLength < 0) { fixedLength = value.Length; } + + WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), TextEncoding.Unicode); + var count = fixedLength - value.Length; + if (count > 0) + { + Clear(count * 2); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBigUniNull(string value) + { + WriteString(value, TextEncoding.Unicode); + Write((ushort)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8(string value) => WriteString(value, TextEncoding.UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUTF8Null(string value) + { + WriteString(value, TextEncoding.UTF8); + Write((byte)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAscii(string value, int fixedLength = -1) + { + if (fixedLength < 0) { fixedLength = value.Length; } + + WriteString(value.AsSpan(0, Math.Min(fixedLength, value.Length)), Encoding.ASCII); + var count = fixedLength - value.Length; + if (count > 0) + { + Clear(count); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteAsciiNull(string value) + { + WriteString(value, Encoding.ASCII); + Write((byte)0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int amount) + { + if (Position + amount > Length) + { + throw new OutOfMemoryException(); + } + + int count; + if (Position < _first.Length) + { + count = Math.Min(amount, _first.Length - Position); + _first.Slice(Position, count).Clear(); + count = amount - count; + } + else + { + count = amount; + } + + if (count > 0) + { + _second.Slice(Position - _first.Length, count).Clear(); + } + + Position += amount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Seek(int offset, SeekOrigin origin) => + Position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.End => Length + offset, + _ => Position + offset // Current + }; } diff --git a/Projects/Server/Buffers/SpanReader.cs b/Projects/Server/Buffers/SpanReader.cs index cbff16597..d523b4b13 100644 --- a/Projects/Server/Buffers/SpanReader.cs +++ b/Projects/Server/Buffers/SpanReader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SpanReader.cs * * * @@ -21,273 +21,272 @@ using System.Text; using Server; using Server.Text; -namespace System.Buffers +namespace System.Buffers; + +public ref struct SpanReader { - public ref struct SpanReader + private readonly ReadOnlySpan _buffer; + + public int Length { get; } + public int Position { get; private set; } + public int Remaining => Length - Position; + + public SpanReader(ReadOnlySpan span) { - private readonly ReadOnlySpan _buffer; + _buffer = span; + Position = 0; + Length = span.Length; + } - public int Length { get; } - public int Position { get; private set; } - public int Remaining => Length - Position; - - public SpanReader(ReadOnlySpan span) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte() + { + if (Position >= Length) { - _buffer = span; - Position = 0; - Length = span.Length; + throw new OutOfMemoryException(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte ReadByte() + return _buffer[Position++]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ReadBoolean() => ReadByte() > 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte ReadSByte() => (sbyte)ReadByte(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16() + { + if (!BinaryPrimitives.TryReadInt16BigEndian(_buffer[Position..], out var value)) { - if (Position >= Length) + throw new OutOfMemoryException(); + } + + Position += 2; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short ReadInt16LE() + { + if (!BinaryPrimitives.TryReadInt16LittleEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16() + { + if (!BinaryPrimitives.TryReadUInt16BigEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadUInt16LE() + { + if (!BinaryPrimitives.TryReadUInt16LittleEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 2; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt32() + { + if (!BinaryPrimitives.TryReadInt32BigEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32() + { + if (!BinaryPrimitives.TryReadUInt32BigEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadUInt32LE() + { + if (!BinaryPrimitives.TryReadUInt32LittleEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 4; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64() + { + if (!BinaryPrimitives.TryReadInt64BigEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 8; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64() + { + if (!BinaryPrimitives.TryReadUInt64BigEndian(_buffer[Position..], out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 8; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) + { + int byteLength = encoding.GetByteLengthForEncoding(); + + bool isFixedLength = fixedLength > -1; + + var remaining = Remaining; + int size; + if (isFixedLength) + { + size = fixedLength * byteLength; + if (size > Remaining) { throw new OutOfMemoryException(); } - - return _buffer[Position++]; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ReadBoolean() => ReadByte() > 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public sbyte ReadSByte() => (sbyte)ReadByte(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16() + else { - if (!BinaryPrimitives.TryReadInt16BigEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 2; - return value; + // In case the remaining is not evenly divisible + size = remaining - (remaining & (byteLength - 1)); + int index = _buffer.Slice(Position, size).IndexOfTerminator(byteLength); + size = index < 0 ? size : index; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short ReadInt16LE() + var span = _buffer.Slice(Position, size); + Position += size; + return TextEncoding.GetString(span, encoding, safeString); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUniSafe(int fixedLength) => ReadString(TextEncoding.UnicodeLE, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUniSafe() => ReadString(TextEncoding.UnicodeLE, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUni(int fixedLength) => ReadString(TextEncoding.UnicodeLE, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadLittleUni() => ReadString(TextEncoding.UnicodeLE); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUniSafe(int fixedLength) => ReadString(TextEncoding.Unicode, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUniSafe() => ReadString(TextEncoding.Unicode, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUni(int fixedLength) => ReadString(TextEncoding.Unicode, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadBigUni() => ReadString(TextEncoding.Unicode); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8Safe(int fixedLength) => ReadString(TextEncoding.UTF8, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8Safe() => ReadString(TextEncoding.UTF8, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadUTF8() => ReadString(TextEncoding.UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadAscii() => ReadString(Encoding.ASCII); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Seek(int offset, SeekOrigin origin) + { + Debug.Assert( + origin != SeekOrigin.End || offset <= 0, + "Attempting to seek to a position beyond capacity using SeekOrigin.End" + ); + + Debug.Assert( + origin != SeekOrigin.End || offset >= -_buffer.Length, + "Attempting to seek to a negative position using SeekOrigin.End" + ); + + Debug.Assert( + origin != SeekOrigin.Begin || offset >= 0, + "Attempting to seek to a negative position using SeekOrigin.Begin" + ); + + Debug.Assert( + origin != SeekOrigin.Begin || offset <= _buffer.Length, + "Attempting to seek to a position beyond the capacity using SeekOrigin.Begin" + ); + + Debug.Assert( + origin != SeekOrigin.Current || Position + offset >= 0, + "Attempting to seek to a negative position using SeekOrigin.Current" + ); + + Debug.Assert( + origin != SeekOrigin.Current || Position + offset <= _buffer.Length, + "Attempting to seek to a position beyond the capacity using SeekOrigin.Current" + ); + + return Position = Math.Max(0, origin switch { - if (!BinaryPrimitives.TryReadInt16LittleEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } + SeekOrigin.Current => Position + offset, + SeekOrigin.End => _buffer.Length + offset, + _ => offset // Begin + }); + } - Position += 2; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Read(Span bytes) + { + if (bytes.Length < Length) { - if (!BinaryPrimitives.TryReadUInt16BigEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 2; - return value; + throw new ArgumentOutOfRangeException(nameof(bytes)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ReadUInt16LE() - { - if (!BinaryPrimitives.TryReadUInt16LittleEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 2; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt32() - { - if (!BinaryPrimitives.TryReadInt32BigEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 4; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32() - { - if (!BinaryPrimitives.TryReadUInt32BigEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 4; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint ReadUInt32LE() - { - if (!BinaryPrimitives.TryReadUInt32LittleEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 4; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long ReadInt64() - { - if (!BinaryPrimitives.TryReadInt64BigEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 8; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong ReadUInt64() - { - if (!BinaryPrimitives.TryReadUInt64BigEndian(_buffer[Position..], out var value)) - { - throw new OutOfMemoryException(); - } - - Position += 8; - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) - { - int byteLength = encoding.GetByteLengthForEncoding(); - - bool isFixedLength = fixedLength > -1; - - var remaining = Remaining; - int size; - if (isFixedLength) - { - size = fixedLength * byteLength; - if (size > Remaining) - { - throw new OutOfMemoryException(); - } - } - else - { - // In case the remaining is not evenly divisible - size = remaining - (remaining & (byteLength - 1)); - int index = _buffer.Slice(Position, size).IndexOfTerminator(byteLength); - size = index < 0 ? size : index; - } - - var span = _buffer.Slice(Position, size); - Position += size; - return TextEncoding.GetString(span, encoding, safeString); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUniSafe(int fixedLength) => ReadString(TextEncoding.UnicodeLE, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUniSafe() => ReadString(TextEncoding.UnicodeLE, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUni(int fixedLength) => ReadString(TextEncoding.UnicodeLE, false, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadLittleUni() => ReadString(TextEncoding.UnicodeLE); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUniSafe(int fixedLength) => ReadString(TextEncoding.Unicode, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUniSafe() => ReadString(TextEncoding.Unicode, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUni(int fixedLength) => ReadString(TextEncoding.Unicode, false, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadBigUni() => ReadString(TextEncoding.Unicode); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8Safe(int fixedLength) => ReadString(TextEncoding.UTF8, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8Safe() => ReadString(TextEncoding.UTF8, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadUTF8() => ReadString(TextEncoding.UTF8); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAsciiSafe(int fixedLength) => ReadString(Encoding.ASCII, true, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAsciiSafe() => ReadString(Encoding.ASCII, true); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAscii(int fixedLength) => ReadString(Encoding.ASCII, false, fixedLength); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadAscii() => ReadString(Encoding.ASCII); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Seek(int offset, SeekOrigin origin) - { - Debug.Assert( - origin != SeekOrigin.End || offset <= 0, - "Attempting to seek to a position beyond capacity using SeekOrigin.End" - ); - - Debug.Assert( - origin != SeekOrigin.End || offset >= -_buffer.Length, - "Attempting to seek to a negative position using SeekOrigin.End" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Begin" - ); - - Debug.Assert( - origin != SeekOrigin.Begin || offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Begin" - ); - - Debug.Assert( - origin != SeekOrigin.Current || Position + offset >= 0, - "Attempting to seek to a negative position using SeekOrigin.Current" - ); - - Debug.Assert( - origin != SeekOrigin.Current || Position + offset <= _buffer.Length, - "Attempting to seek to a position beyond the capacity using SeekOrigin.Current" - ); - - return Position = Math.Max(0, origin switch - { - SeekOrigin.Current => Position + offset, - SeekOrigin.End => _buffer.Length + offset, - _ => offset // Begin - }); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Read(Span bytes) - { - if (bytes.Length < Length) - { - throw new ArgumentOutOfRangeException(nameof(bytes)); - } - - return _buffer.TryCopyTo(bytes); - } + return _buffer.TryCopyTo(bytes); } } diff --git a/Projects/Server/CityInfo.cs b/Projects/Server/CityInfo.cs index 9cd28d918..e8207c8a7 100644 --- a/Projects/Server/CityInfo.cs +++ b/Projects/Server/CityInfo.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CityInfo.cs * * * @@ -13,71 +13,70 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server +namespace Server; + +public sealed class CityInfo { - public sealed class CityInfo + private Point3D m_Location; + + public CityInfo(string city, string building, int description, int x, int y, int z, Map m) { - private Point3D m_Location; - - public CityInfo(string city, string building, int description, int x, int y, int z, Map m) - { - City = city; - Building = building; - Description = description; - Location = new Point3D(x, y, z); - Map = m; - } - - public CityInfo(string city, string building, int x, int y, int z, Map m) : this(city, building, 0, x, y, z, m) - { - } - - public CityInfo(string city, string building, int description, int x, int y, int z) : this( - city, - building, - description, - x, - y, - z, - Map.Trammel - ) - { - } - - public CityInfo(string city, string building, int x, int y, int z) : this(city, building, 0, x, y, z, Map.Trammel) - { - } - - public string City { get; set; } - - public string Building { get; set; } - - public int Description { get; set; } - - public int X - { - get => m_Location.X; - set => m_Location.X = value; - } - - public int Y - { - get => m_Location.Y; - set => m_Location.Y = value; - } - - public int Z - { - get => m_Location.Z; - set => m_Location.Z = value; - } - - public Point3D Location - { - get => m_Location; - set => m_Location = value; - } - - public Map Map { get; set; } + City = city; + Building = building; + Description = description; + Location = new Point3D(x, y, z); + Map = m; } + + public CityInfo(string city, string building, int x, int y, int z, Map m) : this(city, building, 0, x, y, z, m) + { + } + + public CityInfo(string city, string building, int description, int x, int y, int z) : this( + city, + building, + description, + x, + y, + z, + Map.Trammel + ) + { + } + + public CityInfo(string city, string building, int x, int y, int z) : this(city, building, 0, x, y, z, Map.Trammel) + { + } + + public string City { get; set; } + + public string Building { get; set; } + + public int Description { get; set; } + + public int X + { + get => m_Location.X; + set => m_Location.X = value; + } + + public int Y + { + get => m_Location.Y; + set => m_Location.Y = value; + } + + public int Z + { + get => m_Location.Z; + set => m_Location.Z = value; + } + + public Point3D Location + { + get => m_Location; + set => m_Location = value; + } + + public Map Map { get; set; } } diff --git a/Projects/Server/Collections/ArrayEnumerator.cs b/Projects/Server/Collections/ArrayEnumerator.cs index d3feb7ddf..ae79d0163 100644 --- a/Projects/Server/Collections/ArrayEnumerator.cs +++ b/Projects/Server/Collections/ArrayEnumerator.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ArrayEnumerator.cs * * * @@ -17,63 +17,62 @@ using System; using System.Collections; using System.Collections.Generic; -namespace Server.Collections +namespace Server.Collections; + +/// +/// Non-thread safe, non-guarded enumerator for classes that have internal arrays. +/// Recommended to copy this and use it as a nested struct. +/// Recommend adding version checking to properly guard against modification during enumeration. +/// +/// +public struct ArrayEnumerator : IEnumerator { - /// - /// Non-thread safe, non-guarded enumerator for classes that have internal arrays. - /// Recommended to copy this and use it as a nested struct. - /// Recommend adding version checking to properly guard against modification during enumeration. - /// - /// - public struct ArrayEnumerator : IEnumerator + private readonly T[] _array; + private int _index; + private T? _current; + + public ArrayEnumerator(T[] array) { - private readonly T[] _array; - private int _index; - private T? _current; + _array = array; + _index = 0; + _current = default; + } - public ArrayEnumerator(T[] array) + public void Dispose() + { + } + + public bool MoveNext() + { + T[] localList = _array; + + if ((uint)_index < (uint)localList.Length) { - _array = array; - _index = 0; - _current = default; + _current = _array[_index++]; + return true; } - public void Dispose() - { - } + return false; + } - public bool MoveNext() - { - T[] localList = _array; + public T? Current => _current!; - if ((uint)_index < (uint)localList.Length) + object IEnumerator.Current + { + get + { + if (_index == 0 || _index == _array.Length + 1) { - _current = _array[_index++]; - return true; + throw new InvalidOperationException(nameof(_index)); } - return false; - } - - public T? Current => _current!; - - object IEnumerator.Current - { - get - { - if (_index == 0 || _index == _array.Length + 1) - { - throw new InvalidOperationException(nameof(_index)); - } - - return _current; - } - } - - void IEnumerator.Reset() - { - _index = 0; - _current = default; + return _current; } } + + void IEnumerator.Reset() + { + _index = 0; + _current = default; + } } diff --git a/Projects/Server/Collections/CollectionHelpers.cs b/Projects/Server/Collections/CollectionHelpers.cs index 1989de97f..cca59ea55 100644 --- a/Projects/Server/Collections/CollectionHelpers.cs +++ b/Projects/Server/Collections/CollectionHelpers.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CollectionHelpers.cs * * * @@ -16,17 +16,16 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Server.Collections +namespace Server.Collections; + +public static class CollectionHelpers { - public static class CollectionHelpers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddNotNull(this ICollection coll, T t) where T : class { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AddNotNull(this ICollection coll, T t) where T : class + if (t != null) { - if (t != null) - { - coll.Add(t); - } + coll.Add(t); } } } diff --git a/Projects/Server/Collections/CollectionThrowStrings.cs b/Projects/Server/Collections/CollectionThrowStrings.cs index 1730c6334..b842c061a 100644 --- a/Projects/Server/Collections/CollectionThrowStrings.cs +++ b/Projects/Server/Collections/CollectionThrowStrings.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CollectionThrowStrings.cs * * * diff --git a/Projects/Server/Collections/HashHelpers.cs b/Projects/Server/Collections/HashHelpers.cs index ca58de66b..2e8f8ac8b 100644 --- a/Projects/Server/Collections/HashHelpers.cs +++ b/Projects/Server/Collections/HashHelpers.cs @@ -6,120 +6,119 @@ using System; using System.Diagnostics; using System.Runtime.CompilerServices; -namespace Microsoft.Collections.Extensions +namespace Microsoft.Collections.Extensions; + +internal static class HashHelpers { - internal static class HashHelpers + + // must never be written to + internal static readonly int[] SizeOneIntArray = new int[1]; + + // This is the maximum prime smaller than Array.MaxArrayLength + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + public static readonly int[] primes = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; + + public static bool IsPrime(int candidate) { - - // must never be written to - internal static readonly int[] SizeOneIntArray = new int[1]; - - // This is the maximum prime smaller than Array.MaxArrayLength - public const int MaxPrimeArrayLength = 0x7FEFFFFD; - - public const int HashPrime = 101; - - // Table of prime numbers to use as hash table sizes. - // A typical resize algorithm would pick the smallest prime number in this array - // that is larger than twice the previous capacity. - // Suppose our Hashtable currently has capacity x and enough elements are added - // such that a resize needs to occur. Resizing first computes 2x then finds the - // first prime in the table greater than 2x, i.e. if primes are ordered - // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. - // Doubling is important for preserving the asymptotic complexity of the - // hashtable operations such as add. Having a prime guarantees that double - // hashing does not lead to infinite loops. IE, your hash function will be - // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. - // We prefer the low computation costs of higher prime numbers over the increased - // memory allocation of a fixed prime number i.e. when right sizing a HashSet. - public static readonly int[] primes = { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; - - public static bool IsPrime(int candidate) + if ((candidate & 1) != 0) { - if ((candidate & 1) != 0) + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) { - int limit = (int)Math.Sqrt(candidate); - for (int divisor = 3; divisor <= limit; divisor += 2) + if (candidate % divisor == 0) { - if (candidate % divisor == 0) - { - return false; - } - } - return true; - } - return candidate == 2; - } - - public static int GetPrime(int min) - { - if (min < 0) - { - throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); - } - - for (int i = 0; i < primes.Length; i++) - { - int prime = primes[i]; - if (prime >= min) - { - return prime; + return false; } } - - //outside of our predefined table. - //compute the hard way. - for (int i = min | 1; i < int.MaxValue; i += 2) - { - if (IsPrime(i) && (i - 1) % HashPrime != 0) - { - return i; - } - } - return min; + return true; } + return candidate == 2; + } - // Returns size of hashtable to grow to. - public static int ExpandPrime(int oldSize) + public static int GetPrime(int min) + { + if (min < 0) { - int newSize = 2 * oldSize; - - // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) - { - Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); - return MaxPrimeArrayLength; - } - - return GetPrime(newSize); + throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); } - /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). - /// This should only be used on 64-bit. - public static ulong GetFastModMultiplier(uint divisor) => - ulong.MaxValue / divisor + 1; - - /// Performs a mod operation using the multiplier pre-computed with . - /// This should only be used on 64-bit. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint FastMod(uint value, uint divisor, ulong multiplier) + for (int i = 0; i < primes.Length; i++) { - // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), - // which allows to avoid the long multiplication if the divisor is less than 2**31. - Debug.Assert(divisor <= int.MaxValue); - - // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version - // is faster than BigMul currently because we only need the high bits. - uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); - - Debug.Assert(highbits == value % divisor); - return highbits; + int prime = primes[i]; + if (prime >= min) + { + return prime; + } } + + //outside of our predefined table. + //compute the hard way. + for (int i = min | 1; i < int.MaxValue; i += 2) + { + if (IsPrime(i) && (i - 1) % HashPrime != 0) + { + return i; + } + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). + /// This should only be used on 64-bit. + public static ulong GetFastModMultiplier(uint divisor) => + ulong.MaxValue / divisor + 1; + + /// Performs a mod operation using the multiplier pre-computed with . + /// This should only be used on 64-bit. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FastMod(uint value, uint divisor, ulong multiplier) + { + // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), + // which allows to avoid the long multiplication if the divisor is less than 2**31. + Debug.Assert(divisor <= int.MaxValue); + + // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version + // is faster than BigMul currently because we only need the high bits. + uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); + + Debug.Assert(highbits == value % divisor); + return highbits; } } diff --git a/Projects/Server/Collections/OrderedHashSet.cs b/Projects/Server/Collections/OrderedHashSet.cs index cc9b7231b..1442afdf1 100644 --- a/Projects/Server/Collections/OrderedHashSet.cs +++ b/Projects/Server/Collections/OrderedHashSet.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OrderedHashSet.cs * * * @@ -20,560 +20,559 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Collections.Extensions; -namespace Server.Collections +namespace Server.Collections; + +[DebuggerDisplay("Count = {Count}")] +public class OrderedHashSet : IList { - [DebuggerDisplay("Count = {Count}")] - public class OrderedHashSet : IList + private struct Entry { - private struct Entry - { - public uint HashCode; - public TValue Value; - public int Next; // the index of the next item in the same bucket, -1 if last - } + public uint HashCode; + public TValue Value; + public int Next; // the index of the next item in the same bucket, -1 if last + } - private static readonly Entry[] InitialEntries = new Entry[1]; - private int[] _buckets = HashHelpers.SizeOneIntArray; - private Entry[] _entries = InitialEntries; - private ulong _fastModMultiplier; - private int _count; - private int _version; + private static readonly Entry[] InitialEntries = new Entry[1]; + private int[] _buckets = HashHelpers.SizeOneIntArray; + private Entry[] _entries = InitialEntries; + private ulong _fastModMultiplier; + private int _count; + private int _version; #nullable enable - private readonly IEqualityComparer? _comparer; + private readonly IEqualityComparer? _comparer; #nullable disable - public int Count => _count; + public int Count => _count; #nullable enable - public IEqualityComparer? Comparer => _comparer; + public IEqualityComparer? Comparer => _comparer; #nullable disable - public OrderedHashSet() - : this(0) + public OrderedHashSet() + : this(0) + { + } + + public OrderedHashSet(IEqualityComparer comparer) + : this(0, comparer) + { + } + + public OrderedHashSet(int capacity, IEqualityComparer comparer = null) + { + if (capacity < 0) { + throw new ArgumentOutOfRangeException(nameof(capacity)); } - public OrderedHashSet(IEqualityComparer comparer) - : this(0, comparer) + if (capacity > 0) { - } - - public OrderedHashSet(int capacity, IEqualityComparer comparer = null) - { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } - - if (capacity > 0) - { - int newSize = HashHelpers.GetPrime(capacity); - _buckets = new int[newSize]; - _entries = new Entry[newSize]; - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - } - - if (comparer != EqualityComparer.Default) - { - _comparer = comparer; - } - } - - public OrderedHashSet(IEnumerable collection, IEqualityComparer comparer = null) - : this((collection as ICollection)?.Count ?? 0, comparer) - { - if (collection == null) - { - throw new ArgumentNullException(nameof(collection)); - } - - foreach (TValue value in collection) - { - Add(value); - } - } - - public bool Contains(TValue item) => TryGetValue(item, out var value) && EqualityComparer.Default.Equals(value); - - public void Clear() - { - if (_count > 0) - { - Array.Clear(_buckets, 0, _buckets.Length); - Array.Clear(_entries, 0, _count); - _count = 0; - ++_version; - } - } - - public int EnsureCapacity(int capacity) - { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } - - if (_entries.Length >= capacity) - { - return _entries.Length; - } int newSize = HashHelpers.GetPrime(capacity); + _buckets = new int[newSize]; + _entries = new Entry[newSize]; + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); + } + + if (comparer != EqualityComparer.Default) + { + _comparer = comparer; + } + } + + public OrderedHashSet(IEnumerable collection, IEqualityComparer comparer = null) + : this((collection as ICollection)?.Count ?? 0, comparer) + { + if (collection == null) + { + throw new ArgumentNullException(nameof(collection)); + } + + foreach (TValue value in collection) + { + Add(value); + } + } + + public bool Contains(TValue item) => TryGetValue(item, out var value) && EqualityComparer.Default.Equals(value); + + public void Clear() + { + if (_count > 0) + { + Array.Clear(_buckets, 0, _buckets.Length); + Array.Clear(_entries, 0, _count); + _count = 0; + ++_version; + } + } + + public int EnsureCapacity(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + if (_entries.Length >= capacity) + { + return _entries.Length; + } + int newSize = HashHelpers.GetPrime(capacity); + Resize(newSize); + ++_version; + return newSize; + } + + public Enumerator GetEnumerator() => new(this); + + void ICollection.Add(TValue item) => TryAdd(item); + + public bool Add(TValue item) => TryAdd(item); + + public int GetOrAdd(TValue value) => TryInsert(null, value); + + public int IndexOf(TValue value) => IndexOf(value, out _); + + public void Insert(int index, TValue value) + { + if ((uint)index > (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); + } + + TryInsert(index, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref int GetBucketRef(uint hashCode) + { + int[] buckets = _buckets!; + return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; + } + + public bool Remove(TValue value) + { + int index = IndexOf(value); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + public void RemoveAt(int index) + { + int count = Count; + if ((uint)index >= (uint)count) + { + throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); + } + + // Remove the entry from the bucket + RemoveEntryFromBucket(index); + + // Decrement the indices > index + Entry[] entries = _entries; + for (int i = index + 1; i < count; ++i) + { + entries[i - 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: -1); + } + --_count; + entries[_count] = default; + ++_version; + } + + public void TrimExcess(int capacity) + { + if (capacity < Count) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + int newSize = HashHelpers.GetPrime(capacity); + if (newSize < _entries.Length) + { Resize(newSize); ++_version; - return newSize; + } + } + + public bool TryAdd(TValue value) => TryInsert(null, value) != _count - 1; + + public bool TryGetValue(TValue value, out TValue actualValue) + { + int index = IndexOf(value); + if (index >= 0) + { + actualValue = _entries[index].Value; + return true; } - public Enumerator GetEnumerator() => new(this); + actualValue = default; + return false; + } - void ICollection.Add(TValue item) => TryAdd(item); - - public bool Add(TValue item) => TryAdd(item); - - public int GetOrAdd(TValue value) => TryInsert(null, value); - - public int IndexOf(TValue value) => IndexOf(value, out _); - - public void Insert(int index, TValue value) + public TValue this[int index] + { + get { - if ((uint)index > (uint)Count) + if ((uint)index >= (uint)Count) { throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); } - TryInsert(index, value); + return _entries[index].Value; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref int GetBucketRef(uint hashCode) + set { - int[] buckets = _buckets!; - return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; - } - - public bool Remove(TValue value) - { - int index = IndexOf(value); - if (index >= 0) - { - RemoveAt(index); - return true; - } - - return false; - } - - public void RemoveAt(int index) - { - int count = Count; - if ((uint)index >= (uint)count) + if ((uint)index >= (uint)Count) { throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); } - // Remove the entry from the bucket - RemoveEntryFromBucket(index); - - // Decrement the indices > index - Entry[] entries = _entries; - for (int i = index + 1; i < count; ++i) + TValue v = value; + int foundIndex = IndexOf(v, out uint hashCode); + if (foundIndex < 0) { - entries[i - 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: -1); - } - --_count; - entries[_count] = default; - ++_version; - } - - public void TrimExcess(int capacity) - { - if (capacity < Count) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } - - int newSize = HashHelpers.GetPrime(capacity); - if (newSize < _entries.Length) - { - Resize(newSize); + RemoveEntryFromBucket(index); + Entry entry = new Entry { HashCode = hashCode, Value = value }; + AddEntryToBucket(ref entry, index, _buckets); + _entries[index] = entry; ++_version; } - } - - public bool TryAdd(TValue value) => TryInsert(null, value) != _count - 1; - - public bool TryGetValue(TValue value, out TValue actualValue) - { - int index = IndexOf(value); - if (index >= 0) + else if (foundIndex == index) { - actualValue = _entries[index].Value; - return true; - } - - actualValue = default; - return false; - } - - public TValue this[int index] - { - get - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - return _entries[index].Value; - } - set - { - if ((uint)index >= (uint)Count) - { - throw new ArgumentOutOfRangeException(nameof(index), CollectionThrowStrings.ArgumentOutOfRange_Index); - } - - TValue v = value; - int foundIndex = IndexOf(v, out uint hashCode); - if (foundIndex < 0) - { - RemoveEntryFromBucket(index); - Entry entry = new Entry { HashCode = hashCode, Value = value }; - AddEntryToBucket(ref entry, index, _buckets); - _entries[index] = entry; - ++_version; - } - else if (foundIndex == index) - { - ref Entry entry = ref _entries[index]; - entry.Value = value; - } - else - { - throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_AddingDuplicate, v.ToString())); - } - } - } - - public bool IsReadOnly => false; - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void CopyTo(TValue[] array, int arrayIndex) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if ((uint)arrayIndex > (uint)array.Length) - { - throw new ArgumentOutOfRangeException(nameof(arrayIndex), CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); - } - - int count = Count; - if (array.Length - arrayIndex < count) - { - throw new ArgumentException(CollectionThrowStrings.Arg_ArrayPlusOffTooSmall); - } - - Entry[] entries = _entries; - for (int i = 0; i < count; ++i) - { - Entry entry = entries[i]; - array[i + arrayIndex] = entry.Value; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Entry[] Resize(int newSize) - { - int[] newBuckets = new int[newSize]; - Entry[] newEntries = new Entry[newSize]; - - int count = Count; - Array.Copy(_entries, newEntries, count); - - _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); - - for (int i = 0; i < count; ++i) - { - AddEntryToBucket(ref newEntries[i], i, newBuckets); - } - - _buckets = newBuckets; - _entries = newEntries; - return newEntries; - } - -#nullable enable - private int IndexOf(TValue value, out uint hashCode) - { - ref int bucket = ref Unsafe.NullRef(); - int i; - - IEqualityComparer? comparer = _comparer; - if (comparer == null) - { - hashCode = (uint)(value?.GetHashCode() ?? 0); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; - - if (i >= 0) - { - if (typeof(TValue).IsValueType) - { - // ValueType: Devirtualize with EqualityComparer.Default intrinsic - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && EqualityComparer.Default.Equals(entry.Value, value)) - { - break; - } - - i = entry.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - else - { - // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), - // so cache in a local rather than get EqualityComparer per loop iteration. - var defaultComparer = EqualityComparer.Default; - Entry[] entries = _entries; - int collisionCount = 0; - do - { - Entry entry = entries[i]; - if (entry.HashCode == hashCode && defaultComparer.Equals(entry.Value, value)) - { - break; - } - - i = entry.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException( - CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported - ); - } - - ++collisionCount; - } while (i >= 0); - } - } + ref Entry entry = ref _entries[index]; + entry.Value = value; } else { - hashCode = (uint)comparer.GetHashCode(value); - bucket = ref GetBucketRef(hashCode); - i = bucket - 1; - if (i >= 0) + throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_AddingDuplicate, v.ToString())); + } + } + } + + public bool IsReadOnly => false; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void CopyTo(TValue[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if ((uint)arrayIndex > (uint)array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), CollectionThrowStrings.ArgumentOutOfRange_NeedNonNegNum); + } + + int count = Count; + if (array.Length - arrayIndex < count) + { + throw new ArgumentException(CollectionThrowStrings.Arg_ArrayPlusOffTooSmall); + } + + Entry[] entries = _entries; + for (int i = 0; i < count; ++i) + { + Entry entry = entries[i]; + array[i + arrayIndex] = entry.Value; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Entry[] Resize(int newSize) + { + int[] newBuckets = new int[newSize]; + Entry[] newEntries = new Entry[newSize]; + + int count = Count; + Array.Copy(_entries, newEntries, count); + + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize); + + for (int i = 0; i < count; ++i) + { + AddEntryToBucket(ref newEntries[i], i, newBuckets); + } + + _buckets = newBuckets; + _entries = newEntries; + return newEntries; + } + +#nullable enable + private int IndexOf(TValue value, out uint hashCode) + { + ref int bucket = ref Unsafe.NullRef(); + int i; + + IEqualityComparer? comparer = _comparer; + if (comparer == null) + { + hashCode = (uint)(value?.GetHashCode() ?? 0); + bucket = ref GetBucketRef(hashCode); + i = bucket - 1; + + if (i >= 0) + { + if (typeof(TValue).IsValueType) { + // ValueType: Devirtualize with EqualityComparer.Default intrinsic Entry[] entries = _entries; int collisionCount = 0; do { Entry entry = entries[i]; - if (entry.HashCode == hashCode && comparer.Equals(entry.Value, value)) + if (entry.HashCode == hashCode && EqualityComparer.Default.Equals(entry.Value, value)) { break; } + i = entry.Next; if (collisionCount >= entries.Length) { // The chain of entries forms a loop; which means a concurrent update has happened. // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); + throw new InvalidOperationException( + CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported + ); } + + ++collisionCount; + } while (i >= 0); + } + else + { + // Object type: Shared Generic, EqualityComparer.Default won't devirtualize (https://github.com/dotnet/runtime/issues/10050), + // so cache in a local rather than get EqualityComparer per loop iteration. + var defaultComparer = EqualityComparer.Default; + Entry[] entries = _entries; + int collisionCount = 0; + do + { + Entry entry = entries[i]; + if (entry.HashCode == hashCode && defaultComparer.Equals(entry.Value, value)) + { + break; + } + + i = entry.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException( + CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported + ); + } + ++collisionCount; } while (i >= 0); } } - - return i; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int TryInsert(int? index, TValue value) + else { - int i = IndexOf(value, out uint hashCode); - return i >= 0 ? i : AddInternal(index, value, hashCode); + hashCode = (uint)comparer.GetHashCode(value); + bucket = ref GetBucketRef(hashCode); + i = bucket - 1; + if (i >= 0) + { + Entry[] entries = _entries; + int collisionCount = 0; + do + { + Entry entry = entries[i]; + if (entry.HashCode == hashCode && comparer.Equals(entry.Value, value)) + { + break; + } + i = entry.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; + } while (i >= 0); + } } - private int AddInternal(int? index, TValue value, uint hashCode) + return i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int TryInsert(int? index, TValue value) + { + int i = IndexOf(value, out uint hashCode); + return i >= 0 ? i : AddInternal(index, value, hashCode); + } + + private int AddInternal(int? index, TValue value, uint hashCode) + { + Entry[] entries = _entries; + // Check if resize is needed + int count = Count; + if (entries.Length == count || entries.Length == 1) { - Entry[] entries = _entries; - // Check if resize is needed - int count = Count; - if (entries.Length == count || entries.Length == 1) - { - entries = Resize(HashHelpers.ExpandPrime(entries.Length)); - } - - // Increment indices >= index; - int actualIndex = index ?? count; - for (int i = count - 1; i >= actualIndex; --i) - { - entries[i + 1] = entries[i]; - UpdateBucketIndex(i, incrementAmount: 1); - } - - ref Entry entry = ref entries[actualIndex]; - entry.HashCode = hashCode; - entry.Value = value; - AddEntryToBucket(ref entry, actualIndex, _buckets); - ++_count; - ++_version; - return actualIndex; + entries = Resize(HashHelpers.ExpandPrime(entries.Length)); } + + // Increment indices >= index; + int actualIndex = index ?? count; + for (int i = count - 1; i >= actualIndex; --i) + { + entries[i + 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: 1); + } + + ref Entry entry = ref entries[actualIndex]; + entry.HashCode = hashCode; + entry.Value = value; + AddEntryToBucket(ref entry, actualIndex, _buckets); + ++_count; + ++_version; + return actualIndex; + } #nullable restore - // Returns the index of the next entry in the bucket - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets) + // Returns the index of the next entry in the bucket + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets) + { + ref int b = ref buckets[(int)(entry.HashCode % (uint)buckets.Length)]; + entry.Next = b - 1; + b = entryIndex + 1; + } + + private void RemoveEntryFromBucket(int entryIndex) + { + Entry[] entries = _entries; + Entry entry = entries[entryIndex]; + ref int bucket = ref GetBucketRef(entry.HashCode); + // Bucket was pointing to removed entry. Update it to point to the next in the chain + if (bucket == entryIndex + 1) { - ref int b = ref buckets[(int)(entry.HashCode % (uint)buckets.Length)]; - entry.Next = b - 1; - b = entryIndex + 1; + bucket = entry.Next + 1; } - - private void RemoveEntryFromBucket(int entryIndex) + else { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to removed entry. Update it to point to the next in the chain - if (bucket == entryIndex + 1) + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain + int i = bucket - 1; + int collisionCount = 0; + while (true) { - bucket = entry.Next + 1; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain - int i = bucket - 1; - int collisionCount = 0; - while (true) + ref Entry e = ref entries[i]; + if (e.Next == entryIndex) { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next = entry.Next; - return; - } - i = e.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; + e.Next = entry.Next; + return; } - } - } - - private void UpdateBucketIndex(int entryIndex, int incrementAmount) - { - Entry[] entries = _entries; - Entry entry = entries[entryIndex]; - ref int bucket = ref GetBucketRef(entry.HashCode); - // Bucket was pointing to entry. Increment the index by incrementAmount. - if (bucket == entryIndex + 1) - { - bucket += incrementAmount; - } - else - { - // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. - int i = bucket - 1; - int collisionCount = 0; - while (true) + i = e.Next; + if (collisionCount >= entries.Length) { - ref Entry e = ref entries[i]; - if (e.Next == entryIndex) - { - e.Next += incrementAmount; - return; - } - i = e.Next; - if (collisionCount >= entries.Length) - { - // The chain of entries forms a loop; which means a concurrent update has happened. - // Break out of the loop and throw, rather than looping forever. - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); - } - ++collisionCount; + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); } - } - } - - public struct Enumerator : IEnumerator - { - private readonly OrderedHashSet _orderedHashSet; - private readonly int _version; - private int _index; - private TValue _current; - - public TValue Current => _current; - - object IEnumerator.Current => _current; - - internal Enumerator(OrderedHashSet orderedHashSet) - { - _orderedHashSet = orderedHashSet; - _version = orderedHashSet._version; - _index = 0; - _current = default; - } - - public void Dispose() - { - } - - public bool MoveNext() - { - if (_version != _orderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - if (_index < _orderedHashSet.Count) - { - Entry entry = _orderedHashSet._entries[_index]; - _current = entry.Value; - ++_index; - return true; - } - _current = default; - return false; - } - - void IEnumerator.Reset() - { - if (_version != _orderedHashSet._version) - { - throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); - } - - _index = 0; - _current = default; + ++collisionCount; } } } + + private void UpdateBucketIndex(int entryIndex, int incrementAmount) + { + Entry[] entries = _entries; + Entry entry = entries[entryIndex]; + ref int bucket = ref GetBucketRef(entry.HashCode); + // Bucket was pointing to entry. Increment the index by incrementAmount. + if (bucket == entryIndex + 1) + { + bucket += incrementAmount; + } + else + { + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. + int i = bucket - 1; + int collisionCount = 0; + while (true) + { + ref Entry e = ref entries[i]; + if (e.Next == entryIndex) + { + e.Next += incrementAmount; + return; + } + i = e.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; + } + } + } + + public struct Enumerator : IEnumerator + { + private readonly OrderedHashSet _orderedHashSet; + private readonly int _version; + private int _index; + private TValue _current; + + public TValue Current => _current; + + object IEnumerator.Current => _current; + + internal Enumerator(OrderedHashSet orderedHashSet) + { + _orderedHashSet = orderedHashSet; + _version = orderedHashSet._version; + _index = 0; + _current = default; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_version != _orderedHashSet._version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + + if (_index < _orderedHashSet.Count) + { + Entry entry = _orderedHashSet._entries[_index]; + _current = entry.Value; + ++_index; + return true; + } + _current = default; + return false; + } + + void IEnumerator.Reset() + { + if (_version != _orderedHashSet._version) + { + throw new InvalidOperationException(CollectionThrowStrings.InvalidOperation_EnumFailedVersion); + } + + _index = 0; + _current = default; + } + } } diff --git a/Projects/Server/Collections/PooledOrderedHashSet.cs b/Projects/Server/Collections/PooledOrderedHashSet.cs index 8853918ba..ad7427fa7 100644 --- a/Projects/Server/Collections/PooledOrderedHashSet.cs +++ b/Projects/Server/Collections/PooledOrderedHashSet.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PooledOrderedHashSet.cs * * * diff --git a/Projects/Server/Commands.cs b/Projects/Server/Commands.cs index 6c5de7deb..8a7afa2d3 100644 --- a/Projects/Server/Commands.cs +++ b/Projects/Server/Commands.cs @@ -2,240 +2,230 @@ using System; using System.Collections.Generic; using Server.Network; -namespace Server +namespace Server; + +public delegate void CommandEventHandler(CommandEventArgs e); + +public class CommandEventArgs { - public delegate void CommandEventHandler(CommandEventArgs e); - - public class CommandEventArgs + public CommandEventArgs(Mobile mobile, string command, string argString, string[] arguments) { - public CommandEventArgs(Mobile mobile, string command, string argString, string[] arguments) - { - Mobile = mobile; - Command = command; - ArgString = argString; - Arguments = arguments; - } - - public Mobile Mobile { get; } - - public string Command { get; } - - public string ArgString { get; } - - public string[] Arguments { get; } - - public int Length => Arguments.Length; - - public string GetString(int index) => index < 0 || index >= Arguments.Length ? "" : Arguments[index]; - - public int GetInt32(int index) => index < 0 || index >= Arguments.Length ? 0 : Utility.ToInt32(Arguments[index]); - - public uint GetUInt32(int index) => - index < 0 || index >= Arguments.Length ? 0 : Utility.ToUInt32(Arguments[index]); - - public bool GetBoolean(int index) => index >= 0 && index < Arguments.Length && Utility.ToBoolean(Arguments[index]); - - public double GetDouble(int index) => - index < 0 || index >= Arguments.Length ? 0.0 : Utility.ToDouble(Arguments[index]); - - public TimeSpan GetTimeSpan(int index) => - index < 0 || index >= Arguments.Length ? TimeSpan.Zero : Utility.ToTimeSpan(Arguments[index]); + Mobile = mobile; + Command = command; + ArgString = argString; + Arguments = arguments; } - public static partial class EventSink + public Mobile Mobile { get; } + + public string Command { get; } + + public string ArgString { get; } + + public string[] Arguments { get; } + + public int Length => Arguments.Length; + + public string GetString(int index) => index < 0 || index >= Arguments.Length ? "" : Arguments[index]; + + public int GetInt32(int index) => index < 0 || index >= Arguments.Length ? 0 : Utility.ToInt32(Arguments[index]); + + public uint GetUInt32(int index) => + index < 0 || index >= Arguments.Length ? 0 : Utility.ToUInt32(Arguments[index]); + + public bool GetBoolean(int index) => index >= 0 && index < Arguments.Length && Utility.ToBoolean(Arguments[index]); + + public double GetDouble(int index) => + index < 0 || index >= Arguments.Length ? 0.0 : Utility.ToDouble(Arguments[index]); + + public TimeSpan GetTimeSpan(int index) => + index < 0 || index >= Arguments.Length ? TimeSpan.Zero : Utility.ToTimeSpan(Arguments[index]); +} + +public static partial class EventSink +{ + public static event Action Command; + public static void InvokeCommand(CommandEventArgs e) => Command?.Invoke(e); +} + +public class CommandEntry : IComparable +{ + public CommandEntry(string command, CommandEventHandler handler, AccessLevel accessLevel) { - public static event Action Command; - public static void InvokeCommand(CommandEventArgs e) => Command?.Invoke(e); + Command = command; + Handler = handler; + AccessLevel = accessLevel; } - public class CommandEntry : IComparable + public string Command { get; } + + public CommandEventHandler Handler { get; } + + public AccessLevel AccessLevel { get; } + + public int CompareTo(CommandEntry e) => string.CompareOrdinal(Command, e?.Command); + + public static List GetList() { - public CommandEntry(string command, CommandEventHandler handler, AccessLevel accessLevel) + var commands = new List(CommandSystem.Entries.Values); + + commands.Sort(); + commands.Reverse(); + + for (var i = 0; i < commands.Count; ++i) { - Command = command; - Handler = handler; - AccessLevel = accessLevel; - } + var e = commands[i]; - public string Command { get; } - - public CommandEventHandler Handler { get; } - - public AccessLevel AccessLevel { get; } - - public int CompareTo(CommandEntry e) => string.CompareOrdinal(Command, e?.Command); - - public static List GetList() - { - var commands = new List(CommandSystem.Entries.Values); - - commands.Sort(); - commands.Reverse(); - - for (var i = 0; i < commands.Count; ++i) + for (var j = i + 1; j < commands.Count; ++j) { - var e = commands[i]; + var c = commands[j]; - for (var j = i + 1; j < commands.Count; ++j) + if (e.Handler.Method == c.Handler.Method) { - var c = commands[j]; + commands.RemoveAt(j); + --j; + } + } + } - if (e.Handler.Method == c.Handler.Method) + return commands; + } +} + +public record CommandInfo(AccessLevel AccessLevel, string Name, string[] Aliases, string Usage, string Description); + +public class CommandInfoSorter : IComparer +{ + public int Compare(CommandInfo a, CommandInfo b) + { + if (a == null && b == null) + { + return 0; + } + + var v = b?.AccessLevel.CompareTo(a?.AccessLevel) ?? 1; + + return v != 0 ? v : string.CompareOrdinal(a?.Name, b?.Name); + } +} + +public static class CommandSystem +{ + public static string Prefix { get; set; } = "["; + + public static Dictionary Entries { get; } = new(StringComparer.OrdinalIgnoreCase); + + public static AccessLevel BadCommandIgnoreLevel { get; set; } = AccessLevel.Player; + + public static string[] Split(string value) + { + var array = value.ToCharArray(); + var list = new List(); + + var start = 0; + + while (start < array.Length) + { + var c = array[start]; + + if (c == '"') + { + ++start; + var end = start; + + while (end < array.Length) + { + if (array[end] != '"' || array[end - 1] == '\\') { - commands.RemoveAt(j); - --j; + ++end; + } + else + { + break; } } + + list.Add(value.Substring(start, end - start)); + + start = end + 2; } - - return commands; - } - } - - public record CommandInfo(AccessLevel AccessLevel, string Name, string[] Aliases, string Usage, string Description); - - public class CommandInfoSorter : IComparer - { - public int Compare(CommandInfo a, CommandInfo b) - { - if (a == null && b == null) + else if (c != ' ') { - return 0; - } + var end = start; - var v = b?.AccessLevel.CompareTo(a?.AccessLevel) ?? 1; - - return v != 0 ? v : string.CompareOrdinal(a?.Name, b?.Name); - } - } - - public static class CommandSystem - { - public static string Prefix { get; set; } = "["; - - public static Dictionary Entries { get; } = new(StringComparer.OrdinalIgnoreCase); - - public static AccessLevel BadCommandIgnoreLevel { get; set; } = AccessLevel.Player; - - public static string[] Split(string value) - { - var array = value.ToCharArray(); - var list = new List(); - - var start = 0; - - while (start < array.Length) - { - var c = array[start]; - - if (c == '"') + while (end < array.Length) { - ++start; - var end = start; - - while (end < array.Length) + if (array[end] != ' ') { - if (array[end] != '"' || array[end - 1] == '\\') - { - ++end; - } - else - { - break; - } + ++end; } - - list.Add(value.Substring(start, end - start)); - - start = end + 2; - } - else if (c != ' ') - { - var end = start; - - while (end < array.Length) + else { - if (array[end] != ' ') - { - ++end; - } - else - { - break; - } + break; } - - list.Add(value.Substring(start, end - start)); - - start = end + 1; } - else - { - ++start; - } - } - return list.ToArray(); - } + list.Add(value.Substring(start, end - start)); - public static void Register(string command, AccessLevel access, CommandEventHandler handler) - { - Entries[command] = new CommandEntry(command, handler, access); - } - - public static bool Handle(Mobile from, string text, MessageType type = MessageType.Regular) - { - if (!text.StartsWithOrdinal(Prefix) && type != MessageType.Command) - { - return false; - } - - if (type != MessageType.Command) - { - text = text[Prefix.Length..]; - } - - var indexOf = text.IndexOfOrdinal(' '); - - string command; - string[] args; - string argString; - - if (indexOf >= 0) - { - argString = text[(indexOf + 1)..]; - - command = text[..indexOf]; - args = Split(argString); + start = end + 1; } else { - argString = ""; - command = text.ToLower(); - args = Array.Empty(); + ++start; } + } - Entries.TryGetValue(command, out var entry); + return list.ToArray(); + } - if (entry != null) + public static void Register(string command, AccessLevel access, CommandEventHandler handler) + { + Entries[command] = new CommandEntry(command, handler, access); + } + + public static bool Handle(Mobile from, string text, MessageType type = MessageType.Regular) + { + if (!text.StartsWithOrdinal(Prefix) && type != MessageType.Command) + { + return false; + } + + if (type != MessageType.Command) + { + text = text[Prefix.Length..]; + } + + var indexOf = text.IndexOfOrdinal(' '); + + string command; + string[] args; + string argString; + + if (indexOf >= 0) + { + argString = text[(indexOf + 1)..]; + + command = text[..indexOf]; + args = Split(argString); + } + else + { + argString = ""; + command = text.ToLower(); + args = Array.Empty(); + } + + Entries.TryGetValue(command, out var entry); + + if (entry != null) + { + if (from.AccessLevel >= entry.AccessLevel) { - if (from.AccessLevel >= entry.AccessLevel) + if (entry.Handler != null) { - if (entry.Handler != null) - { - var e = new CommandEventArgs(from, command, argString, args); - entry.Handler(e); - EventSink.InvokeCommand(e); - } - } - else - { - if (from.AccessLevel <= BadCommandIgnoreLevel) - { - return false; - } - - from.SendMessage("You do not have access to that command."); + var e = new CommandEventArgs(from, command, argString, args); + entry.Handler(e); + EventSink.InvokeCommand(e); } } else @@ -245,10 +235,19 @@ namespace Server return false; } - from.SendMessage("That is not a valid command."); + from.SendMessage("You do not have access to that command."); + } + } + else + { + if (from.AccessLevel <= BadCommandIgnoreLevel) + { + return false; } - return true; + from.SendMessage("That is not a valid command."); } + + return true; } } diff --git a/Projects/Server/Comparers/DescendingComparer.cs b/Projects/Server/Comparers/DescendingComparer.cs index 4a53e9f32..b269d3169 100644 --- a/Projects/Server/Comparers/DescendingComparer.cs +++ b/Projects/Server/Comparers/DescendingComparer.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: DescendingComparer.cs * * * @@ -16,9 +16,8 @@ using System; using System.Collections.Generic; -namespace Server -{ - public class DescendingComparer : IComparer where T : IComparable { - public int Compare(T x, T y) => y?.CompareTo(x) ?? 1; - } +namespace Server; + +public class DescendingComparer : IComparer where T : IComparable { + public int Compare(T x, T y) => y?.CompareTo(x) ?? 1; } diff --git a/Projects/Server/Comparers/LocationComparer.cs b/Projects/Server/Comparers/LocationComparer.cs index 93ee013e6..316848755 100644 --- a/Projects/Server/Comparers/LocationComparer.cs +++ b/Projects/Server/Comparers/LocationComparer.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: LocationComparer.cs * * * @@ -15,42 +15,41 @@ using System.Collections.Generic; -namespace Server +namespace Server; + +public class LocationComparer : IComparer { - public class LocationComparer : IComparer + private static LocationComparer m_Instance; + + public LocationComparer(IPoint3D relativeTo) => RelativeTo = relativeTo; + + public IPoint3D RelativeTo { get; set; } + + public int Compare(IPoint3D x, IPoint3D y) => GetDistance(x) - GetDistance(y); + + public static LocationComparer GetInstance(IPoint3D relativeTo) { - private static LocationComparer m_Instance; - - public LocationComparer(IPoint3D relativeTo) => RelativeTo = relativeTo; - - public IPoint3D RelativeTo { get; set; } - - public int Compare(IPoint3D x, IPoint3D y) => GetDistance(x) - GetDistance(y); - - public static LocationComparer GetInstance(IPoint3D relativeTo) + if (m_Instance == null) { - if (m_Instance == null) - { - m_Instance = new LocationComparer(relativeTo); - } - else - { - m_Instance.RelativeTo = relativeTo; - } - - return m_Instance; + m_Instance = new LocationComparer(relativeTo); + } + else + { + m_Instance.RelativeTo = relativeTo; } - private int GetDistance(IPoint3D p) - { - var x = RelativeTo.X - p.X; - var y = RelativeTo.Y - p.Y; - var z = RelativeTo.Z - p.Z; + return m_Instance; + } - x *= 11; - y *= 11; + private int GetDistance(IPoint3D p) + { + var x = RelativeTo.X - p.X; + var y = RelativeTo.Y - p.Y; + var z = RelativeTo.Z - p.Z; - return x * x + y * y + z * z; - } + x *= 11; + y *= 11; + + return x * x + y * y + z * z; } } diff --git a/Projects/Server/Configuration/ServerConfiguration.cs b/Projects/Server/Configuration/ServerConfiguration.cs index 3490b4117..e3cdaf444 100644 --- a/Projects/Server/Configuration/ServerConfiguration.cs +++ b/Projects/Server/Configuration/ServerConfiguration.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ServerConfiguration.cs * * * diff --git a/Projects/Server/Configuration/ServerSettings.cs b/Projects/Server/Configuration/ServerSettings.cs index 23eddb6de..f95cfff32 100644 --- a/Projects/Server/Configuration/ServerSettings.cs +++ b/Projects/Server/Configuration/ServerSettings.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ServerSettings.cs * * * @@ -17,23 +17,22 @@ using System.Collections.Generic; using System.Net; using System.Text.Json.Serialization; -namespace Server +namespace Server; + +public class ServerSettings { - public class ServerSettings - { - [JsonPropertyName("assemblyDirectories")] - public List AssemblyDirectories { get; set; } = new(); + [JsonPropertyName("assemblyDirectories")] + public List AssemblyDirectories { get; set; } = new(); - [JsonPropertyName("dataDirectories")] - public HashSet DataDirectories { get; set; } = new(); + [JsonPropertyName("dataDirectories")] + public HashSet DataDirectories { get; set; } = new(); - [JsonPropertyName("listeners")] - public List Listeners { get; set; } = new(); + [JsonPropertyName("listeners")] + public List Listeners { get; set; } = new(); - [JsonPropertyName("expansion")] - public Expansion? Expansion { get; set; } + [JsonPropertyName("expansion")] + public Expansion? Expansion { get; set; } - [JsonPropertyName("settings")] - public SortedDictionary Settings { get; set; } = new(); - } + [JsonPropertyName("settings")] + public SortedDictionary Settings { get; set; } = new(); } diff --git a/Projects/Server/ContextMenus/ContextMenu.cs b/Projects/Server/ContextMenus/ContextMenu.cs index c6493334d..27260b769 100644 --- a/Projects/Server/ContextMenus/ContextMenu.cs +++ b/Projects/Server/ContextMenus/ContextMenu.cs @@ -1,83 +1,82 @@ using System.Collections.Generic; -namespace Server.ContextMenus +namespace Server.ContextMenus; + +/// +/// Represents the state of an active context menu. This includes who opened the menu, the menu's focus object, and a list +/// of +/// entries that the menu is composed of. +/// +/// +public class ContextMenu { /// - /// Represents the state of an active context menu. This includes who opened the menu, the menu's focus object, and a list - /// of - /// entries that the menu is composed of. - /// + /// Instantiates a new ContextMenu instance. /// - public class ContextMenu + /// + /// The who opened this ContextMenu. + /// + /// + /// + /// The or for which this ContextMenu is on. + /// + /// + public ContextMenu(Mobile from, IEntity target) { - /// - /// Instantiates a new ContextMenu instance. - /// - /// - /// The who opened this ContextMenu. - /// - /// - /// - /// The or for which this ContextMenu is on. - /// - /// - public ContextMenu(Mobile from, IEntity target) + From = from; + Target = target; + + var list = new List(); + + if (target is Mobile mobile) { - From = from; - Target = target; - - var list = new List(); - - if (target is Mobile mobile) - { - mobile.GetContextMenuEntries(from, list); - } - else if (target is Item item) - { - item.GetContextMenuEntries(from, list); - } - - Entries = list.ToArray(); - - for (var i = 0; i < Entries.Length; ++i) - { - Entries[i].Owner = this; - } + mobile.GetContextMenuEntries(from, list); + } + else if (target is Item item) + { + item.GetContextMenuEntries(from, list); } - /// - /// Gets the who opened this ContextMenu. - /// - public Mobile From { get; } + Entries = list.ToArray(); - /// - /// Gets an object of the or for which this ContextMenu is on. - /// - public IEntity Target { get; } - - /// - /// Gets the list of entries contained in this ContextMenu. - /// - public ContextMenuEntry[] Entries { get; } - - /// - /// Returns true if this ContextMenu requires packet version 2. - /// - public bool RequiresNewPacket + for (var i = 0; i < Entries.Length; ++i) { - get - { - for (var i = 0; i < Entries.Length; ++i) - { - var number = Entries[i].Number; - if (number is < 3000000 or > 3032767) - { - return true; - } - } + Entries[i].Owner = this; + } + } - return false; + /// + /// Gets the who opened this ContextMenu. + /// + public Mobile From { get; } + + /// + /// Gets an object of the or for which this ContextMenu is on. + /// + public IEntity Target { get; } + + /// + /// Gets the list of entries contained in this ContextMenu. + /// + public ContextMenuEntry[] Entries { get; } + + /// + /// Returns true if this ContextMenu requires packet version 2. + /// + public bool RequiresNewPacket + { + get + { + for (var i = 0; i < Entries.Length; ++i) + { + var number = Entries[i].Number; + if (number is < 3000000 or > 3032767) + { + return true; + } } + + return false; } } } diff --git a/Projects/Server/ContextMenus/ContextMenuEntry.cs b/Projects/Server/ContextMenus/ContextMenuEntry.cs index dfc54958a..35155056b 100644 --- a/Projects/Server/ContextMenus/ContextMenuEntry.cs +++ b/Projects/Server/ContextMenus/ContextMenuEntry.cs @@ -1,83 +1,82 @@ using Server.Network; -namespace Server.ContextMenus +namespace Server.ContextMenus; + +/// +/// Represents a single entry of a context menu. +/// +/// +public class ContextMenuEntry { /// - /// Represents a single entry of a context menu. - /// + /// Instantiates a new ContextMenuEntry with a given localization number ( + /// ) + /// and maximum range (). /// - public class ContextMenuEntry + /// + /// The localization number containing the name of this entry. + /// + /// + /// + /// The maximum range at which this entry can be used. + /// + /// + public ContextMenuEntry(int number, int range = -1) { - /// - /// Instantiates a new ContextMenuEntry with a given localization number ( - /// ) - /// and maximum range (). - /// - /// - /// The localization number containing the name of this entry. - /// - /// - /// - /// The maximum range at which this entry can be used. - /// - /// - public ContextMenuEntry(int number, int range = -1) + if (number <= 0x7FFF) // Legacy code support { - if (number <= 0x7FFF) // Legacy code support - { - Number = 3000000 + number; - } - else - { - Number = number; - } - - Range = range; - Enabled = true; - Color = 0xFFFF; + Number = 3000000 + number; + } + else + { + Number = number; } - /// - /// Gets or sets additional flags used in client communication. - /// - public CMEFlags Flags { get; set; } + Range = range; + Enabled = true; + Color = 0xFFFF; + } - /// - /// Gets or sets the that owns this entry. - /// - public ContextMenu Owner { get; set; } + /// + /// Gets or sets additional flags used in client communication. + /// + public CMEFlags Flags { get; set; } - /// - /// Gets or sets the localization number containing the name of this entry. - /// - public int Number { get; set; } + /// + /// Gets or sets the that owns this entry. + /// + public ContextMenu Owner { get; set; } - /// - /// Gets or sets the maximum range at which this entry may be used, in tiles. A value of -1 signifies no maximum range. - /// - public int Range { get; set; } + /// + /// Gets or sets the localization number containing the name of this entry. + /// + public int Number { get; set; } - /// - /// Gets or sets the color for this entry. Format is A1-R5-G5-B5. - /// - public int Color { get; set; } + /// + /// Gets or sets the maximum range at which this entry may be used, in tiles. A value of -1 signifies no maximum range. + /// + public int Range { get; set; } - /// - /// Gets or sets whether this entry is enabled. When false, the entry will appear in a gray hue and - /// will never be invoked. - /// - public bool Enabled { get; set; } + /// + /// Gets or sets the color for this entry. Format is A1-R5-G5-B5. + /// + public int Color { get; set; } - /// - /// Gets a value indicating if non local use of this entry is permitted. - /// - public virtual bool NonLocalUse => false; + /// + /// Gets or sets whether this entry is enabled. When false, the entry will appear in a gray hue and + /// will never be invoked. + /// + public bool Enabled { get; set; } - /// - /// Overridable. Virtual event invoked when the entry is clicked. - /// - public virtual void OnClick() - { - } + /// + /// Gets a value indicating if non local use of this entry is permitted. + /// + public virtual bool NonLocalUse => false; + + /// + /// Overridable. Virtual event invoked when the entry is clicked. + /// + public virtual void OnClick() + { } } diff --git a/Projects/Server/ContextMenus/OpenBackpackEntry.cs b/Projects/Server/ContextMenus/OpenBackpackEntry.cs index f32c518f2..8928191f1 100644 --- a/Projects/Server/ContextMenus/OpenBackpackEntry.cs +++ b/Projects/Server/ContextMenus/OpenBackpackEntry.cs @@ -1,14 +1,13 @@ -namespace Server.ContextMenus +namespace Server.ContextMenus; + +public class OpenBackpackEntry : ContextMenuEntry { - public class OpenBackpackEntry : ContextMenuEntry + private readonly Mobile m_Mobile; + + public OpenBackpackEntry(Mobile m) : base(6145) => m_Mobile = m; + + public override void OnClick() { - private readonly Mobile m_Mobile; - - public OpenBackpackEntry(Mobile m) : base(6145) => m_Mobile = m; - - public override void OnClick() - { - m_Mobile.Use(m_Mobile.Backpack); - } + m_Mobile.Use(m_Mobile.Backpack); } } diff --git a/Projects/Server/ContextMenus/PaperdollEntry.cs b/Projects/Server/ContextMenus/PaperdollEntry.cs index 4ef4fea6f..1660d58e4 100644 --- a/Projects/Server/ContextMenus/PaperdollEntry.cs +++ b/Projects/Server/ContextMenus/PaperdollEntry.cs @@ -1,17 +1,16 @@ -namespace Server.ContextMenus +namespace Server.ContextMenus; + +public class PaperdollEntry : ContextMenuEntry { - public class PaperdollEntry : ContextMenuEntry + private readonly Mobile m_Mobile; + + public PaperdollEntry(Mobile m) : base(6123, 18) => m_Mobile = m; + + public override void OnClick() { - private readonly Mobile m_Mobile; - - public PaperdollEntry(Mobile m) : base(6123, 18) => m_Mobile = m; - - public override void OnClick() + if (m_Mobile.CanPaperdollBeOpenedBy(Owner.From)) { - if (m_Mobile.CanPaperdollBeOpenedBy(Owner.From)) - { - m_Mobile.DisplayPaperdollTo(Owner.From); - } + m_Mobile.DisplayPaperdollTo(Owner.From); } } } diff --git a/Projects/Server/Diagnostics/BaseProfile.cs b/Projects/Server/Diagnostics/BaseProfile.cs index e28116e69..32e20ba55 100644 --- a/Projects/Server/Diagnostics/BaseProfile.cs +++ b/Projects/Server/Diagnostics/BaseProfile.cs @@ -3,78 +3,77 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -namespace Server.Diagnostics +namespace Server.Diagnostics; + +public abstract class BaseProfile { - public abstract class BaseProfile + private readonly Stopwatch _stopwatch; + + protected BaseProfile(string name) { - private readonly Stopwatch _stopwatch; + Name = name; - protected BaseProfile(string 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) { - Name = name; - - _stopwatch = new Stopwatch(); + prof.WriteTo(op); + op.WriteLine(); } + } - 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 + public virtual void Start() + { + if (_stopwatch.IsRunning) { - 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) + _stopwatch.Start(); + } + + public virtual void Finish() + { + var elapsed = _stopwatch.Elapsed; + + TotalTime += elapsed; + + if (elapsed > PeakTime) { - op.Write( - "{0,-100} {1,12:N0} {2,12:F5} {3,-12:F5} {4,12:F5}", - Name, - Count, - AverageTime.TotalSeconds, - PeakTime.TotalSeconds, - TotalTime.TotalSeconds - ); + 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/GumpProfile.cs b/Projects/Server/Diagnostics/GumpProfile.cs index f5c58fbf1..5946c7e93 100644 --- a/Projects/Server/Diagnostics/GumpProfile.cs +++ b/Projects/Server/Diagnostics/GumpProfile.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -namespace Server.Diagnostics +namespace Server.Diagnostics; + +public class GumpProfile : BaseProfile { - public class GumpProfile : BaseProfile + private static readonly Dictionary _profiles = new(); + + public GumpProfile(Type type) : base(type.FullName) { - 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; } - public static IEnumerable Profiles => _profiles.Values; - - public static GumpProfile Acquire(Type type) + if (!_profiles.TryGetValue(type, out var prof)) { - if (!Core.Profiling) - { - return null; - } - - if (!_profiles.TryGetValue(type, out var prof)) - { - _profiles.Add(type, prof = new GumpProfile(type)); - } - - return prof; + _profiles.Add(type, prof = new GumpProfile(type)); } + + return prof; } } diff --git a/Projects/Server/Diagnostics/PacketProfile.cs b/Projects/Server/Diagnostics/PacketProfile.cs index 80e13969a..34e4ce86e 100644 --- a/Projects/Server/Diagnostics/PacketProfile.cs +++ b/Projects/Server/Diagnostics/PacketProfile.cs @@ -4,89 +4,88 @@ using System.IO; using System.Runtime.CompilerServices; using System.Threading; -namespace Server.Diagnostics +namespace Server.Diagnostics; + +public abstract class BasePacketProfile : BaseProfile { - public abstract class BasePacketProfile : BaseProfile + protected BasePacketProfile(string name) : base(name) { - 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 + public long TotalLength { get; private set; } + + public double AverageLength => (double)TotalLength / Math.Max(Count, 1); + + public void Finish(long length) { - private static readonly Dictionary _profiles = new(); + Finish(); - private long _created; - - public PacketSendProfile(int packetId) : base($"0x{packetId:X2}") - { - } - - public static IEnumerable Profiles => _profiles.Values; - - [MethodImpl(MethodImplOptions.Synchronized)] - 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); - } + TotalLength += length; } - public class PacketReceiveProfile : BasePacketProfile + public override void WriteTo(TextWriter op) { - private static readonly Dictionary - _profiles = new(); + base.WriteTo(op); - public PacketReceiveProfile(int packetId) : base($"0x{packetId:X2}") - { - } - - public static IEnumerable Profiles => _profiles.Values; - - [MethodImpl(MethodImplOptions.Synchronized)] - public static PacketReceiveProfile Acquire(int packetId) - { - if (!_profiles.TryGetValue(packetId, out var prof)) - { - _profiles.Add(packetId, prof = new PacketReceiveProfile(packetId)); - } - - return prof; - } + 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; + + [MethodImpl(MethodImplOptions.Synchronized)] + 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; + + [MethodImpl(MethodImplOptions.Synchronized)] + 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 index 27c85875f..32971c341 100644 --- a/Projects/Server/Diagnostics/TargetProfile.cs +++ b/Projects/Server/Diagnostics/TargetProfile.cs @@ -1,32 +1,31 @@ using System; using System.Collections.Generic; -namespace Server.Diagnostics +namespace Server.Diagnostics; + +public class TargetProfile : BaseProfile { - public class TargetProfile : BaseProfile + private static readonly Dictionary _profiles = new(); + + public TargetProfile(Type type) + : base(type.FullName) { - 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; } - public static IEnumerable Profiles => _profiles.Values; - - public static TargetProfile Acquire(Type type) + if (!_profiles.TryGetValue(type, out var prof)) { - if (!Core.Profiling) - { - return null; - } - - if (!_profiles.TryGetValue(type, out var prof)) - { - _profiles.Add(type, prof = new TargetProfile(type)); - } - - return prof; + _profiles.Add(type, prof = new TargetProfile(type)); } + + return prof; } } diff --git a/Projects/Server/Diagnostics/TimerProfile.cs b/Projects/Server/Diagnostics/TimerProfile.cs index bb843daeb..c1ba30d91 100644 --- a/Projects/Server/Diagnostics/TimerProfile.cs +++ b/Projects/Server/Diagnostics/TimerProfile.cs @@ -1,45 +1,44 @@ using System.Collections.Generic; using System.IO; -namespace Server.Diagnostics +namespace Server.Diagnostics; + +public class TimerProfile : BaseProfile { - public class TimerProfile : BaseProfile + private static readonly Dictionary _profiles = new(); + + public TimerProfile(string name) + : base(name) { - 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; } - 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 (!_profiles.TryGetValue(name, out var prof)) { - if (!Core.Profiling) - { - return null; - } - - if (!_profiles.TryGetValue(name, out var prof)) - { - _profiles.Add(name, prof = new TimerProfile(name)); - } - - return prof; + _profiles.Add(name, prof = new TimerProfile(name)); } - public override void WriteTo(TextWriter op) - { - base.WriteTo(op); + return prof; + } - op.Write("\t{0,12:N0} {1,12:N0} {2,-12:N0}", Created, Started, Stopped); - } + 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/Effects.cs b/Projects/Server/Effects.cs index b5d2476ba..22a133285 100644 --- a/Projects/Server/Effects.cs +++ b/Projects/Server/Effects.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Effects.cs * * * @@ -16,262 +16,304 @@ using System; using Server.Network; -namespace Server +namespace Server; + +public enum EffectType { - public enum EffectType + Moving, + Lightning, + FixedXYZ, + FixedFrom +} + +public enum ScreenEffectType +{ + FadeOut = 0x00, + FadeIn = 0x01, + LightFlash = 0x02, + FadeInOut = 0x03, + DarkFlash = 0x04 +} + +public enum EffectLayer +{ + Head = 0, + RightHand = 1, + LeftHand = 2, + Waist = 3, + LeftFoot = 4, + RightFoot = 5, + CenterFeet = 7 +} + +public enum ParticleSupportType +{ + Full, + Detect, + None +} + +public static class Effects +{ + public static ParticleSupportType ParticleSupportType { get; set; } = ParticleSupportType.Detect; + + public static bool SendParticlesTo(NetState state) => + ParticleSupportType == ParticleSupportType.Full || + ParticleSupportType == ParticleSupportType.Detect && state.IsUOTDClient; + + public static void PlaySound(IEntity e, int soundID) => PlaySound(e.Location, e.Map, soundID); + + public static void PlaySound(Point3D p, Map map, int soundID) { - Moving, - Lightning, - FixedXYZ, - FixedFrom - } - - public enum ScreenEffectType - { - FadeOut = 0x00, - FadeIn = 0x01, - LightFlash = 0x02, - FadeInOut = 0x03, - DarkFlash = 0x04 - } - - public enum EffectLayer - { - Head = 0, - RightHand = 1, - LeftHand = 2, - Waist = 3, - LeftFoot = 4, - RightFoot = 5, - CenterFeet = 7 - } - - public enum ParticleSupportType - { - Full, - Detect, - None - } - - public static class Effects - { - public static ParticleSupportType ParticleSupportType { get; set; } = ParticleSupportType.Detect; - - public static bool SendParticlesTo(NetState state) => - ParticleSupportType == ParticleSupportType.Full || - ParticleSupportType == ParticleSupportType.Detect && state.IsUOTDClient; - - public static void PlaySound(IEntity e, int soundID) => PlaySound(e.Location, e.Map, soundID); - - public static void PlaySound(Point3D p, Map map, int soundID) + if (soundID <= -1) { - if (soundID <= -1) - { - return; - } - - if (map != null) - { - Span buffer = stackalloc byte[OutgoingEffectPackets.SoundPacketLength].InitializePacket(); - - var eable = map.GetClientsInRange(new Point3D(p)); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - OutgoingEffectPackets.CreateSoundEffect(buffer, soundID, p); - state.Send(buffer); - } - - eable.Free(); - } + return; } - public static void SendBoltEffect(IEntity e, bool sound = true, int hue = 0) + if (map != null) { - var map = e.Map; + Span buffer = stackalloc byte[OutgoingEffectPackets.SoundPacketLength].InitializePacket(); - if (map == null) - { - return; - } - - e.ProcessDelta(); - - Span preEffect = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); - Span boltEffect = stackalloc byte[OutgoingEffectPackets.BoltEffectLength].InitializePacket(); - Span soundEffect = sound ? stackalloc byte[OutgoingEffectPackets.SoundPacketLength].InitializePacket() : null; - - var eable = map.GetClientsInRange(e.Location); - - foreach (var state in eable) - { - if (state.Mobile.CanSee(e)) - { - if (SendParticlesTo(state)) - { - OutgoingEffectPackets.CreateTargetParticleEffect( - preEffect, - e, 0, 10, 5, 0, 0, 5031, 3, 0 - ); - state.Send(preEffect); - } - - OutgoingEffectPackets.CreateBoltEffect(boltEffect, e, hue); - state.Send(boltEffect); - - if (sound) - { - OutgoingEffectPackets.CreateSoundEffect(soundEffect, 0x29, e); - state.Send(soundEffect); - } - } - } - - eable.Free(); - } - - public static void SendLocationEffect( - IEntity e, int itemID, int duration, int speed = 10, int hue = 0, int renderMode = 0 - ) => SendLocationEffect(e.Location, e.Map, itemID, duration, speed, hue, renderMode); - - public static void SendLocationEffect( - Point3D p, Map map, int itemID, int duration, int speed = 10, int hue = 0, int renderMode = 0 - ) - { - Span effect = stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket(); - OutgoingEffectPackets.CreateLocationHuedEffect( - effect, - p, itemID, speed, duration, hue, renderMode - ); - - SendPacket(p, map, effect); - } - - public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect) - { - SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, 0); - } - - public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect, int unknown) - { - SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, unknown); - } - - public static void SendLocationParticles( - IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, int unknown - ) - { - var map = e.Map; - - if (map == null) - { - return; - } - - Span particles = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); - - Span regular = itemID != 0 ? stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket() : null; - - var eable = map.GetClientsInRange(e.Location); + var eable = map.GetClientsInRange(new Point3D(p)); foreach (var state in eable) { state.Mobile.ProcessDelta(); - - if (SendParticlesTo(state)) - { - OutgoingEffectPackets.CreateLocationParticleEffect( - particles, - e, itemID, speed, duration, hue, renderMode, effect, unknown - ); - state.Send(particles); - } - else if (itemID != 0) - { - OutgoingEffectPackets.CreateLocationHuedEffect( - regular, - e.Location, itemID, speed, duration, hue, renderMode - ); - state.Send(regular); - } + OutgoingEffectPackets.CreateSoundEffect(buffer, soundID, p); + state.Send(buffer); } eable.Free(); } + } - public static void SendTargetEffect(IEntity target, int itemID, int speed, int duration, int hue = 0, int renderMode = 0) + public static void SendBoltEffect(IEntity e, bool sound = true, int hue = 0) + { + var map = e.Map; + + if (map == null) { - (target as Mobile)?.ProcessDelta(); - - Span effect = stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket(); - OutgoingEffectPackets.CreateTargetHuedEffect( - effect, - target, itemID, speed, duration, hue, renderMode - ); - - SendPacket(target.Location, target.Map, effect); + return; } - public static void SendTargetParticles( - IEntity target, int itemID, int speed, int duration, int effect, - EffectLayer layer - ) + e.ProcessDelta(); + + Span preEffect = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); + Span boltEffect = stackalloc byte[OutgoingEffectPackets.BoltEffectLength].InitializePacket(); + Span soundEffect = sound ? stackalloc byte[OutgoingEffectPackets.SoundPacketLength].InitializePacket() : null; + + var eable = map.GetClientsInRange(e.Location); + + foreach (var state in eable) { - SendTargetParticles(target, itemID, speed, duration, 0, 0, effect, layer); - } - - public static void SendTargetParticles( - IEntity target, int itemID, int speed, int duration, int hue, int renderMode, - int effect, EffectLayer layer, int unknown = 0 - ) - { - (target as Mobile)?.ProcessDelta(); - - var map = target.Map; - - if (map == null) + if (state.Mobile.CanSee(e)) { - return; - } - - Span particles = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); - Span regular = itemID != 0 ? stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket() : null; - - var eable = map.GetClientsInRange(target.Location); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - if (SendParticlesTo(state)) { OutgoingEffectPackets.CreateTargetParticleEffect( - particles, - target, itemID, speed, duration, hue, renderMode, effect, (int)layer, unknown + preEffect, + e, 0, 10, 5, 0, 0, 5031, 3, 0 ); - state.Send(particles); + state.Send(preEffect); } - else if (itemID != 0) + + OutgoingEffectPackets.CreateBoltEffect(boltEffect, e, hue); + state.Send(boltEffect); + + if (sound) { - OutgoingEffectPackets.CreateTargetHuedEffect(regular, target, itemID, speed, duration, hue, renderMode); - state.Send(regular); + OutgoingEffectPackets.CreateSoundEffect(soundEffect, 0x29, e); + state.Send(soundEffect); } } - - eable.Free(); } - public static void SendMovingEffect( - Map map, int itemID, Point3D from, Point3D to, int speed, int duration, - bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 - ) => SendMovingEffect( + eable.Free(); + } + + public static void SendLocationEffect( + IEntity e, int itemID, int duration, int speed = 10, int hue = 0, int renderMode = 0 + ) => SendLocationEffect(e.Location, e.Map, itemID, duration, speed, hue, renderMode); + + public static void SendLocationEffect( + Point3D p, Map map, int itemID, int duration, int speed = 10, int hue = 0, int renderMode = 0 + ) + { + Span effect = stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket(); + OutgoingEffectPackets.CreateLocationHuedEffect( + effect, + p, itemID, speed, duration, hue, renderMode + ); + + SendPacket(p, map, effect); + } + + public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect) + { + SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, 0); + } + + public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect, int unknown) + { + SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, unknown); + } + + public static void SendLocationParticles( + IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, int unknown + ) + { + var map = e.Map; + + if (map == null) + { + return; + } + + Span particles = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); + + Span regular = itemID != 0 ? stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket() : null; + + var eable = map.GetClientsInRange(e.Location); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + + if (SendParticlesTo(state)) + { + OutgoingEffectPackets.CreateLocationParticleEffect( + particles, + e, itemID, speed, duration, hue, renderMode, effect, unknown + ); + state.Send(particles); + } + else if (itemID != 0) + { + OutgoingEffectPackets.CreateLocationHuedEffect( + regular, + e.Location, itemID, speed, duration, hue, renderMode + ); + state.Send(regular); + } + } + + eable.Free(); + } + + public static void SendTargetEffect(IEntity target, int itemID, int speed, int duration, int hue = 0, int renderMode = 0) + { + (target as Mobile)?.ProcessDelta(); + + Span effect = stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket(); + OutgoingEffectPackets.CreateTargetHuedEffect( + effect, + target, itemID, speed, duration, hue, renderMode + ); + + SendPacket(target.Location, target.Map, effect); + } + + public static void SendTargetParticles( + IEntity target, int itemID, int speed, int duration, int effect, + EffectLayer layer + ) + { + SendTargetParticles(target, itemID, speed, duration, 0, 0, effect, layer); + } + + public static void SendTargetParticles( + IEntity target, int itemID, int speed, int duration, int hue, int renderMode, + int effect, EffectLayer layer, int unknown = 0 + ) + { + (target as Mobile)?.ProcessDelta(); + + var map = target.Map; + + if (map == null) + { + return; + } + + Span particles = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); + Span regular = itemID != 0 ? stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket() : null; + + var eable = map.GetClientsInRange(target.Location); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + + if (SendParticlesTo(state)) + { + OutgoingEffectPackets.CreateTargetParticleEffect( + particles, + target, itemID, speed, duration, hue, renderMode, effect, (int)layer, unknown + ); + state.Send(particles); + } + else if (itemID != 0) + { + OutgoingEffectPackets.CreateTargetHuedEffect(regular, target, itemID, speed, duration, hue, renderMode); + state.Send(regular); + } + } + + eable.Free(); + } + + public static void SendMovingEffect( + Map map, int itemID, Point3D from, Point3D to, int speed, int duration, + bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 + ) => SendMovingEffect( + Serial.Zero, + Serial.Zero, + from, + map, + itemID, + from, + to, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode + ); + + public static void SendMovingEffect( + Point3D origin, Map map, int itemID, Point3D from, Point3D to, int speed, int duration, + bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 + ) => SendMovingEffect( + Serial.Zero, + Serial.Zero, + origin, + map, + itemID, + from, + to, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode + ); + + public static void SendMovingEffect( + IEntity from, Point3D to, int itemID, + int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 + ) + { + (from as Mobile)?.ProcessDelta(); + + SendMovingEffect( + from.Serial, Serial.Zero, - Serial.Zero, - from, - map, + from.Location, + from.Map, itemID, - from, + from.Location, to, speed, duration, @@ -280,18 +322,49 @@ namespace Server hue, renderMode ); + } - public static void SendMovingEffect( - Point3D origin, Map map, int itemID, Point3D from, Point3D to, int speed, int duration, - bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 - ) => SendMovingEffect( - Serial.Zero, - Serial.Zero, + public static void SendMovingEffect( + IEntity from, IEntity to, int itemID, + int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 + ) + { + (from as Mobile)?.ProcessDelta(); + (to as Mobile)?.ProcessDelta(); + + SendMovingEffect( + from, + to, + from.Location, + from.Map, + itemID, + from.Location, + to.Location, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode + ); + } + + public static void SendMovingEffect( + IEntity from, IEntity to, Point3D origin, Map map, int itemID, Point3D fromLocation, Point3D toLocation, + int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 + ) + { + (from as Mobile)?.ProcessDelta(); + (to as Mobile)?.ProcessDelta(); + + SendMovingEffect( + from.Serial, + to.Serial, origin, map, itemID, - from, - to, + fromLocation, + toLocation, speed, duration, fixedDirection, @@ -299,205 +372,131 @@ namespace Server hue, renderMode ); + } - public static void SendMovingEffect( - IEntity from, Point3D to, int itemID, - int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 - ) + public static void SendMovingEffect( + Serial from, Serial to, Point3D origin, Map map, int itemID, Point3D fromLocation, Point3D toLocation, + int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 + ) + { + Span effect = stackalloc byte[OutgoingEffectPackets.HuedEffectLength]; + OutgoingEffectPackets.CreateMovingHuedEffect( + effect, + from, to, itemID, fromLocation, toLocation, speed, duration, fixedDirection, + explodes, hue, renderMode + ); + + SendPacket(origin, map, effect); + } + + public static void SendMovingParticles( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound, int unknown = 0 + ) + { + SendMovingParticles( + from, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + 0, + 0, + effect, + explodeEffect, + explodeSound, + unknown + ); + } + + public static void SendMovingParticles( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, + int unknown + ) + { + SendMovingParticles( + from, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + (EffectLayer)255, + unknown + ); + } + + public static void SendMovingParticles( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, + EffectLayer layer, int unknown + ) + { + (from as Mobile)?.ProcessDelta(); + (to as Mobile)?.ProcessDelta(); + + var map = from.Map; + + if (map == null) { - (from as Mobile)?.ProcessDelta(); - - SendMovingEffect( - from.Serial, - Serial.Zero, - from.Location, - from.Map, - itemID, - from.Location, - to, - speed, - duration, - fixedDirection, - explodes, - hue, - renderMode - ); + return; } - public static void SendMovingEffect( - IEntity from, IEntity to, int itemID, - int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 - ) + Span particles = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); + Span regular = itemID != 0 ? stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket() : null; + + var eable = map.GetClientsInRange(from.Location); + + foreach (var state in eable) { - (from as Mobile)?.ProcessDelta(); - (to as Mobile)?.ProcessDelta(); + state.Mobile.ProcessDelta(); - SendMovingEffect( - from, - to, - from.Location, - from.Map, - itemID, - from.Location, - to.Location, - speed, - duration, - fixedDirection, - explodes, - hue, - renderMode - ); - } - - public static void SendMovingEffect( - IEntity from, IEntity to, Point3D origin, Map map, int itemID, Point3D fromLocation, Point3D toLocation, - int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 - ) - { - (from as Mobile)?.ProcessDelta(); - (to as Mobile)?.ProcessDelta(); - - SendMovingEffect( - from.Serial, - to.Serial, - origin, - map, - itemID, - fromLocation, - toLocation, - speed, - duration, - fixedDirection, - explodes, - hue, - renderMode - ); - } - - public static void SendMovingEffect( - Serial from, Serial to, Point3D origin, Map map, int itemID, Point3D fromLocation, Point3D toLocation, - int speed, int duration, bool fixedDirection = false, bool explodes = false, int hue = 0, int renderMode = 0 - ) - { - Span effect = stackalloc byte[OutgoingEffectPackets.HuedEffectLength]; - OutgoingEffectPackets.CreateMovingHuedEffect( - effect, - from, to, itemID, fromLocation, toLocation, speed, duration, fixedDirection, - explodes, hue, renderMode - ); - - SendPacket(origin, map, effect); - } - - public static void SendMovingParticles( - IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound, int unknown = 0 - ) - { - SendMovingParticles( - from, - to, - itemID, - speed, - duration, - fixedDirection, - explodes, - 0, - 0, - effect, - explodeEffect, - explodeSound, - unknown - ); - } - - public static void SendMovingParticles( - IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, - int unknown - ) - { - SendMovingParticles( - from, - to, - itemID, - speed, - duration, - fixedDirection, - explodes, - hue, - renderMode, - effect, - explodeEffect, - explodeSound, - (EffectLayer)255, - unknown - ); - } - - public static void SendMovingParticles( - IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, - EffectLayer layer, int unknown - ) - { - (from as Mobile)?.ProcessDelta(); - (to as Mobile)?.ProcessDelta(); - - var map = from.Map; - - if (map == null) + if (SendParticlesTo(state)) { - return; + OutgoingEffectPackets.CreateMovingParticleEffect( + particles, + from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, + explodeEffect, explodeSound, layer, unknown + ); + state.Send(particles); } - - Span particles = stackalloc byte[OutgoingEffectPackets.ParticleEffectLength].InitializePacket(); - Span regular = itemID != 0 ? stackalloc byte[OutgoingEffectPackets.HuedEffectLength].InitializePacket() : null; - - var eable = map.GetClientsInRange(from.Location); - - foreach (var state in eable) + else if (itemID > 1) { - state.Mobile.ProcessDelta(); - - if (SendParticlesTo(state)) - { - OutgoingEffectPackets.CreateMovingParticleEffect( - particles, - from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, - explodeEffect, explodeSound, layer, unknown - ); - state.Send(particles); - } - else if (itemID > 1) - { - OutgoingEffectPackets.CreateMovingHuedEffect( - regular, - from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode - ); - state.Send(regular); - } + OutgoingEffectPackets.CreateMovingHuedEffect( + regular, + from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode + ); + state.Send(regular); } - - eable.Free(); } - public static void SendPacket(Point3D origin, Map map, Span effectBuffer) + eable.Free(); + } + + public static void SendPacket(Point3D origin, Map map, Span effectBuffer) + { + if (map == null) { - if (map == null) - { - return; - } - - var eable = map.GetClientsInRange(new Point3D(origin)); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - state.Send(effectBuffer); - } - - eable.Free(); + return; } + + var eable = map.GetClientsInRange(new Point3D(origin)); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + state.Send(effectBuffer); + } + + eable.Free(); } } diff --git a/Projects/Server/EventLoopTasks.cs b/Projects/Server/EventLoopTasks.cs index 12f5ac664..9c72578b0 100644 --- a/Projects/Server/EventLoopTasks.cs +++ b/Projects/Server/EventLoopTasks.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EventLoopTasks.cs * * * @@ -17,59 +17,58 @@ using System; using System.Collections.Concurrent; using System.Threading; -namespace Server +namespace Server; + +public sealed class EventLoopContext : SynchronizationContext { - public sealed class EventLoopContext : SynchronizationContext + private readonly ConcurrentQueue _queue; + private readonly Thread _mainThread; + + public EventLoopContext() { - private readonly ConcurrentQueue _queue; - private readonly Thread _mainThread; + _queue = new ConcurrentQueue(); + _mainThread = Thread.CurrentThread; + } - public EventLoopContext() + public override SynchronizationContext CreateCopy() => new EventLoopContext(); + + public void Post(Action d) => _queue.Enqueue(d); + + public override void Post(SendOrPostCallback d, object state) => _queue.Enqueue(() => d(state)); + + public override void Send(SendOrPostCallback d, object state) + { + if (Thread.CurrentThread == _mainThread) { - _queue = new ConcurrentQueue(); - _mainThread = Thread.CurrentThread; + d(state); + return; } - public override SynchronizationContext CreateCopy() => new EventLoopContext(); + AutoResetEvent evt = new AutoResetEvent(false); - public void Post(Action d) => _queue.Enqueue(d); - - public override void Post(SendOrPostCallback d, object state) => _queue.Enqueue(() => d(state)); - - public override void Send(SendOrPostCallback d, object state) + _queue.Enqueue(() => { - if (Thread.CurrentThread == _mainThread) - { - d(state); - return; - } + d(state); + evt.Set(); + }); - AutoResetEvent evt = new AutoResetEvent(false); + evt.WaitOne(); + } - _queue.Enqueue(() => - { - d(state); - evt.Set(); - }); - - evt.WaitOne(); + public void ExecuteTasks() + { + if (Thread.CurrentThread != _mainThread) + { + throw new Exception("Called EventLoop.ExecuteTasks on incorrect thread!"); } - public void ExecuteTasks() + var count = _queue.Count; + + for (int i = 0; i < count; i++) { - if (Thread.CurrentThread != _mainThread) + if (_queue.TryDequeue(out var a)) { - throw new Exception("Called EventLoop.ExecuteTasks on incorrect thread!"); - } - - var count = _queue.Count; - - for (int i = 0; i < count; i++) - { - if (_queue.TryDequeue(out var a)) - { - a(); - } + a(); } } } diff --git a/Projects/Server/Events/AccountLoginEvent.cs b/Projects/Server/Events/AccountLoginEvent.cs index cfe5636a3..e5354fe15 100644 --- a/Projects/Server/Events/AccountLoginEvent.cs +++ b/Projects/Server/Events/AccountLoginEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: AccountLoginEvent.cs * * * @@ -17,33 +17,32 @@ using System; using System.Runtime.CompilerServices; using Server.Network; -namespace Server +namespace Server; + +public class AccountLoginEventArgs { - public class AccountLoginEventArgs + public AccountLoginEventArgs(NetState state, string username, string password) { - public AccountLoginEventArgs(NetState state, string username, string password) - { - State = state; - Username = username; - Password = password; - } - - public NetState State { get; } - - public string Username { get; } - - public string Password { get; } - - public bool Accepted { get; set; } - - public ALRReason RejectReason { get; set; } + State = state; + Username = username; + Password = password; } - public static partial class EventSink - { - public static event Action AccountLogin; + public NetState State { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeAccountLogin(AccountLoginEventArgs e) => AccountLogin?.Invoke(e); - } + public string Username { get; } + + public string Password { get; } + + public bool Accepted { get; set; } + + public ALRReason RejectReason { get; set; } +} + +public static partial class EventSink +{ + public static event Action AccountLogin; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeAccountLogin(AccountLoginEventArgs e) => AccountLogin?.Invoke(e); } diff --git a/Projects/Server/Events/AggressiveActionEvent.cs b/Projects/Server/Events/AggressiveActionEvent.cs index 663ddd8bf..f30cd0399 100644 --- a/Projects/Server/Events/AggressiveActionEvent.cs +++ b/Projects/Server/Events/AggressiveActionEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: AggressiveActionEvent.cs * * * @@ -17,56 +17,55 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public class AggressiveActionEventArgs : EventArgs { - public class AggressiveActionEventArgs : EventArgs + private static readonly Queue m_Pool = new(); + + private AggressiveActionEventArgs(Mobile aggressed, Mobile aggressor, bool criminal) { - private static readonly Queue m_Pool = new(); - - private AggressiveActionEventArgs(Mobile aggressed, Mobile aggressor, bool criminal) - { - Aggressed = aggressed; - Aggressor = aggressor; - Criminal = criminal; - } - - public Mobile Aggressed { get; private set; } - - public Mobile Aggressor { get; private set; } - - public bool Criminal { get; private set; } - - public static AggressiveActionEventArgs Create(Mobile aggressed, Mobile aggressor, bool criminal) - { - AggressiveActionEventArgs args; - - if (m_Pool.Count > 0) - { - args = m_Pool.Dequeue(); - - args.Aggressed = aggressed; - args.Aggressor = aggressor; - args.Criminal = criminal; - } - else - { - args = new AggressiveActionEventArgs(aggressed, aggressor, criminal); - } - - return args; - } - - public void Free() - { - m_Pool.Enqueue(this); - } + Aggressed = aggressed; + Aggressor = aggressor; + Criminal = criminal; } - public static partial class EventSink - { - public static event Action AggressiveAction; + public Mobile Aggressed { get; private set; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeAggressiveAction(AggressiveActionEventArgs e) => AggressiveAction?.Invoke(e); + public Mobile Aggressor { get; private set; } + + public bool Criminal { get; private set; } + + public static AggressiveActionEventArgs Create(Mobile aggressed, Mobile aggressor, bool criminal) + { + AggressiveActionEventArgs args; + + if (m_Pool.Count > 0) + { + args = m_Pool.Dequeue(); + + args.Aggressed = aggressed; + args.Aggressor = aggressor; + args.Criminal = criminal; + } + else + { + args = new AggressiveActionEventArgs(aggressed, aggressor, criminal); + } + + return args; + } + + public void Free() + { + m_Pool.Enqueue(this); } } + +public static partial class EventSink +{ + public static event Action AggressiveAction; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeAggressiveAction(AggressiveActionEventArgs e) => AggressiveAction?.Invoke(e); +} diff --git a/Projects/Server/Events/CharacterCreatedEvent.cs b/Projects/Server/Events/CharacterCreatedEvent.cs index dc089daae..ab78df4d9 100644 --- a/Projects/Server/Events/CharacterCreatedEvent.cs +++ b/Projects/Server/Events/CharacterCreatedEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CharacterCreatedEvent.cs * * * @@ -18,77 +18,76 @@ using System.Runtime.CompilerServices; using Server.Accounting; using Server.Network; -namespace Server +namespace Server; + +public class CharacterCreatedEventArgs { - 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 + ) { - 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; } + 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 readonly record struct SkillNameValue(SkillName Name, int Value); - public readonly record struct StatNameValue(StatType Name, int Value); + public NetState State { get; } - public static partial class EventSink - { - public static event Action CharacterCreated; + public IAccount Account { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeCharacterCreated(CharacterCreatedEventArgs e) => CharacterCreated?.Invoke(e); - } + 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 29f8fda70..37d9c7c25 100644 --- a/Projects/Server/Events/EventSink.cs +++ b/Projects/Server/Events/EventSink.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EventSink.cs * * * @@ -17,145 +17,144 @@ using System; using System.Collections.Generic; using Server.Network; -namespace Server +namespace Server; + +public static partial class EventSink { - public static partial class EventSink + public static event Action OpenDoorMacroUsed; + public static void InvokeOpenDoorMacroUsed(Mobile m) => OpenDoorMacroUsed?.Invoke(m); + + public static event Action Login; + public static void InvokeLogin(Mobile m) => Login?.Invoke(m); + + public static event Action HungerChanged; + public static void InvokeHungerChanged(Mobile mobile, int oldValue) => HungerChanged?.Invoke(mobile, oldValue); + + public static event Action Shutdown; + public static void InvokeShutdown() => Shutdown?.Invoke(); + + public static event Action HelpRequest; + public static void InvokeHelpRequest(Mobile m) => HelpRequest?.Invoke(m); + + public static event Action DisarmRequest; + public static void InvokeDisarmRequest(Mobile m) => DisarmRequest?.Invoke(m); + + public static event Action StunRequest; + public static void InvokeStunRequest(Mobile m) => StunRequest?.Invoke(m); + + public static event Action OpenSpellbookRequest; + public static void InvokeOpenSpellbookRequest(Mobile m, int type) => OpenSpellbookRequest?.Invoke(m, type); + + public static event Action CastSpellRequest; + + public static void InvokeCastSpellRequest(Mobile m, int spellID, Item book) => + CastSpellRequest?.Invoke(m, spellID, book); + + public static event Action BandageTargetRequest; + + public static void InvokeBandageTargetRequest(Mobile m, Item bandage, Mobile target) => + BandageTargetRequest?.Invoke(m, bandage, target); + + public static event Action AnimateRequest; + public static void InvokeAnimateRequest(Mobile m, string action) => AnimateRequest?.Invoke(m, action); + + public static event Action Logout; + public static void InvokeLogout(Mobile m) => Logout?.Invoke(m); + + public static event Action Connected; + public static void InvokeConnected(Mobile m) => Connected?.Invoke(m); + + public static event Action BeforeDisconnected; + public static void InvokeBeforeDisconnected(Mobile m) => BeforeDisconnected?.Invoke(m); + + public static event Action Disconnected; + public static void InvokeDisconnected(Mobile m) => Disconnected?.Invoke(m); + + public static event Action RenameRequest; + + public static void InvokeRenameRequest(Mobile from, Mobile target, string name) => + RenameRequest?.Invoke(from, target, name); + + public static event Action PlayerDeath; + public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m); + + public static event Action VirtueGumpRequest; + + public static void InvokeVirtueGumpRequest(Mobile beholder, Mobile beheld) => + VirtueGumpRequest?.Invoke(beholder, beheld); + + public static event Action VirtueItemRequest; + + public static void InvokeVirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) => + VirtueItemRequest?.Invoke(beholder, beheld, gumpID); + + 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) => + PaperdollRequest?.Invoke(beholder, beheld); + + public static event Action ProfileRequest; + public static void InvokeProfileRequest(Mobile beholder, Mobile beheld) => ProfileRequest?.Invoke(beholder, beheld); + + public static event Action ChangeProfileRequest; + + public static void InvokeChangeProfileRequest(Mobile beholder, Mobile beheld, string text) => + ChangeProfileRequest?.Invoke(beholder, beheld, text); + + public static event Action DeleteRequest; + public static void InvokeDeleteRequest(NetState state, int index) => DeleteRequest?.Invoke(state, index); + + public static event Action ServerStarted; + public static void InvokeServerStarted() => ServerStarted?.Invoke(); + + public static event Action GuildGumpRequest; + public static void InvokeGuildGumpRequest(Mobile m) => GuildGumpRequest?.Invoke(m); + + public static event Action QuestGumpRequest; + public static void InvokeQuestGumpRequest(Mobile m) => QuestGumpRequest?.Invoke(m); + + public static event Action ClientVersionReceived; + + public static void InvokeClientVersionReceived(NetState state, ClientVersion cv) => + ClientVersionReceived?.Invoke(state, cv); + + public static event Action> EquipMacro; + + public static void InvokeEquipMacro(Mobile m, List list) { - public static event Action OpenDoorMacroUsed; - public static void InvokeOpenDoorMacroUsed(Mobile m) => OpenDoorMacroUsed?.Invoke(m); - - public static event Action Login; - public static void InvokeLogin(Mobile m) => Login?.Invoke(m); - - public static event Action HungerChanged; - public static void InvokeHungerChanged(Mobile mobile, int oldValue) => HungerChanged?.Invoke(mobile, oldValue); - - public static event Action Shutdown; - public static void InvokeShutdown() => Shutdown?.Invoke(); - - public static event Action HelpRequest; - public static void InvokeHelpRequest(Mobile m) => HelpRequest?.Invoke(m); - - public static event Action DisarmRequest; - public static void InvokeDisarmRequest(Mobile m) => DisarmRequest?.Invoke(m); - - public static event Action StunRequest; - public static void InvokeStunRequest(Mobile m) => StunRequest?.Invoke(m); - - public static event Action OpenSpellbookRequest; - public static void InvokeOpenSpellbookRequest(Mobile m, int type) => OpenSpellbookRequest?.Invoke(m, type); - - public static event Action CastSpellRequest; - - public static void InvokeCastSpellRequest(Mobile m, int spellID, Item book) => - CastSpellRequest?.Invoke(m, spellID, book); - - public static event Action BandageTargetRequest; - - public static void InvokeBandageTargetRequest(Mobile m, Item bandage, Mobile target) => - BandageTargetRequest?.Invoke(m, bandage, target); - - public static event Action AnimateRequest; - public static void InvokeAnimateRequest(Mobile m, string action) => AnimateRequest?.Invoke(m, action); - - public static event Action Logout; - public static void InvokeLogout(Mobile m) => Logout?.Invoke(m); - - public static event Action Connected; - public static void InvokeConnected(Mobile m) => Connected?.Invoke(m); - - public static event Action BeforeDisconnected; - public static void InvokeBeforeDisconnected(Mobile m) => BeforeDisconnected?.Invoke(m); - - public static event Action Disconnected; - public static void InvokeDisconnected(Mobile m) => Disconnected?.Invoke(m); - - public static event Action RenameRequest; - - public static void InvokeRenameRequest(Mobile from, Mobile target, string name) => - RenameRequest?.Invoke(from, target, name); - - public static event Action PlayerDeath; - public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m); - - public static event Action VirtueGumpRequest; - - public static void InvokeVirtueGumpRequest(Mobile beholder, Mobile beheld) => - VirtueGumpRequest?.Invoke(beholder, beheld); - - public static event Action VirtueItemRequest; - - public static void InvokeVirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) => - VirtueItemRequest?.Invoke(beholder, beheld, gumpID); - - 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) => - PaperdollRequest?.Invoke(beholder, beheld); - - public static event Action ProfileRequest; - public static void InvokeProfileRequest(Mobile beholder, Mobile beheld) => ProfileRequest?.Invoke(beholder, beheld); - - public static event Action ChangeProfileRequest; - - public static void InvokeChangeProfileRequest(Mobile beholder, Mobile beheld, string text) => - ChangeProfileRequest?.Invoke(beholder, beheld, text); - - public static event Action DeleteRequest; - public static void InvokeDeleteRequest(NetState state, int index) => DeleteRequest?.Invoke(state, index); - - public static event Action ServerStarted; - public static void InvokeServerStarted() => ServerStarted?.Invoke(); - - public static event Action GuildGumpRequest; - public static void InvokeGuildGumpRequest(Mobile m) => GuildGumpRequest?.Invoke(m); - - public static event Action QuestGumpRequest; - public static void InvokeQuestGumpRequest(Mobile m) => QuestGumpRequest?.Invoke(m); - - public static event Action ClientVersionReceived; - - public static void InvokeClientVersionReceived(NetState state, ClientVersion cv) => - ClientVersionReceived?.Invoke(state, cv); - - public static event Action> EquipMacro; - - public static void InvokeEquipMacro(Mobile m, List list) + if (list?.Count > 0) { - if (list?.Count > 0) - { - EquipMacro?.Invoke(m, list); - } + EquipMacro?.Invoke(m, list); } - - public static event Action> UnequipMacro; - - public static void InvokeUnequipMacro(Mobile m, List layers) - { - if (layers?.Count > 0) - { - UnequipMacro?.Invoke(m, layers); - } - } - - public static event Action TargetedSpell; - - public static void InvokeTargetedSpell(Mobile m, IEntity target, int spellId) => - TargetedSpell?.Invoke(m, target, spellId); - - public static event Action TargetedSkillUse; - - public static void InvokeTargetedSkillUse(Mobile m, IEntity target, int skillId) => - TargetedSkillUse?.Invoke(m, target, skillId); - - public static event Action TargetByResourceMacro; - - public static void InvokeTargetByResourceMacro(Mobile m, Item item, short resourceType) => - TargetByResourceMacro?.Invoke(m, item, resourceType); } + + public static event Action> UnequipMacro; + + public static void InvokeUnequipMacro(Mobile m, List layers) + { + if (layers?.Count > 0) + { + UnequipMacro?.Invoke(m, layers); + } + } + + public static event Action TargetedSpell; + + public static void InvokeTargetedSpell(Mobile m, IEntity target, int spellId) => + TargetedSpell?.Invoke(m, target, spellId); + + public static event Action TargetedSkillUse; + + public static void InvokeTargetedSkillUse(Mobile m, IEntity target, int skillId) => + TargetedSkillUse?.Invoke(m, target, skillId); + + 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/FastwalkEvent.cs b/Projects/Server/Events/FastwalkEvent.cs index 4ef3bd5d8..f252510c8 100644 --- a/Projects/Server/Events/FastwalkEvent.cs +++ b/Projects/Server/Events/FastwalkEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: FastwalkEvent.cs * * * @@ -17,22 +17,21 @@ using System; using System.Runtime.CompilerServices; using Server.Network; -namespace Server +namespace Server; + +public class FastWalkEventArgs { - public class FastWalkEventArgs - { - public FastWalkEventArgs(NetState state) => NetState = state; + public FastWalkEventArgs(NetState state) => NetState = state; - public NetState NetState { get; } + public NetState NetState { get; } - public bool Blocked { get; set; } = true; - } - - public static partial class EventSink - { - public static event Action FastWalk; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeFastWalk(FastWalkEventArgs e) => FastWalk?.Invoke(e); - } + public bool Blocked { get; set; } = true; +} + +public static partial class EventSink +{ + public static event Action FastWalk; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeFastWalk(FastWalkEventArgs e) => FastWalk?.Invoke(e); } diff --git a/Projects/Server/Events/GameLoginEvent.cs b/Projects/Server/Events/GameLoginEvent.cs index 03a67c0f4..366bc62fb 100644 --- a/Projects/Server/Events/GameLoginEvent.cs +++ b/Projects/Server/Events/GameLoginEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GameLoginEvent.cs * * * @@ -17,33 +17,32 @@ using System; using System.Runtime.CompilerServices; using Server.Network; -namespace Server +namespace Server; + +public class GameLoginEventArgs { - public class GameLoginEventArgs + public GameLoginEventArgs(NetState state, string un, string pw) { - public GameLoginEventArgs(NetState state, string un, string pw) - { - State = state; - Username = un; - Password = pw; - } - - public NetState State { get; } - - public string Username { get; } - - public string Password { get; } - - public bool Accepted { get; set; } - - public CityInfo[] CityInfo { get; set; } + State = state; + Username = un; + Password = pw; } - public static partial class EventSink - { - public static event Action GameLogin; + public NetState State { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeGameLogin(GameLoginEventArgs e) => GameLogin?.Invoke(e); - } + public string Username { get; } + + public string Password { get; } + + public bool Accepted { get; set; } + + public CityInfo[] CityInfo { get; set; } +} + +public static partial class EventSink +{ + public static event Action GameLogin; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeGameLogin(GameLoginEventArgs e) => GameLogin?.Invoke(e); } diff --git a/Projects/Server/Events/MovementEvent.cs b/Projects/Server/Events/MovementEvent.cs index b3125f80a..ef52f4a41 100644 --- a/Projects/Server/Events/MovementEvent.cs +++ b/Projects/Server/Events/MovementEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MovementEvent.cs * * * @@ -17,55 +17,54 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public class MovementEventArgs : EventArgs { - public class MovementEventArgs : EventArgs + private static readonly Queue m_Pool = new(); + + public MovementEventArgs(Mobile mobile, Direction dir) { - private static readonly Queue m_Pool = new(); - - public MovementEventArgs(Mobile mobile, Direction dir) - { - Mobile = mobile; - Direction = dir; - } - - public Mobile Mobile { get; private set; } - - public Direction Direction { get; private set; } - - public bool Blocked { get; set; } - - public static MovementEventArgs Create(Mobile mobile, Direction dir) - { - MovementEventArgs args; - - if (m_Pool.Count > 0) - { - args = m_Pool.Dequeue(); - - args.Mobile = mobile; - args.Direction = dir; - args.Blocked = false; - } - else - { - args = new MovementEventArgs(mobile, dir); - } - - return args; - } - - public void Free() - { - m_Pool.Enqueue(this); - } + Mobile = mobile; + Direction = dir; } - public static partial class EventSink - { - public static event Action Movement; + public Mobile Mobile { get; private set; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeMovement(MovementEventArgs e) => Movement?.Invoke(e); + public Direction Direction { get; private set; } + + public bool Blocked { get; set; } + + public static MovementEventArgs Create(Mobile mobile, Direction dir) + { + MovementEventArgs args; + + if (m_Pool.Count > 0) + { + args = m_Pool.Dequeue(); + + args.Mobile = mobile; + args.Direction = dir; + args.Blocked = false; + } + else + { + args = new MovementEventArgs(mobile, dir); + } + + return args; + } + + public void Free() + { + m_Pool.Enqueue(this); } } + +public static partial class EventSink +{ + public static event Action Movement; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeMovement(MovementEventArgs e) => Movement?.Invoke(e); +} diff --git a/Projects/Server/Events/ServerCrashedEvent.cs b/Projects/Server/Events/ServerCrashedEvent.cs index ee4445c58..d6989a82f 100644 --- a/Projects/Server/Events/ServerCrashedEvent.cs +++ b/Projects/Server/Events/ServerCrashedEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ServerCrashedEvent.cs * * * @@ -16,22 +16,21 @@ using System; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public class ServerCrashedEventArgs { - public class ServerCrashedEventArgs - { - public ServerCrashedEventArgs(Exception e) => Exception = e; + public ServerCrashedEventArgs(Exception e) => Exception = e; - public Exception Exception { get; } + public Exception Exception { get; } - public bool Close { get; set; } - } - - public static partial class EventSink - { - public static event Action ServerCrashed; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeServerCrashed(ServerCrashedEventArgs e) => ServerCrashed?.Invoke(e); - } + public bool Close { get; set; } +} + +public static partial class EventSink +{ + public static event Action ServerCrashed; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeServerCrashed(ServerCrashedEventArgs e) => ServerCrashed?.Invoke(e); } diff --git a/Projects/Server/Events/ServerListEvent.cs b/Projects/Server/Events/ServerListEvent.cs index c646a65a2..383815eb5 100644 --- a/Projects/Server/Events/ServerListEvent.cs +++ b/Projects/Server/Events/ServerListEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ServerListEvent.cs * * * @@ -20,41 +20,40 @@ using System.Runtime.CompilerServices; using Server.Accounting; using Server.Network; -namespace Server +namespace Server; + +public class ServerListEventArgs { - public class ServerListEventArgs + public ServerListEventArgs(NetState state, IAccount account) { - 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)); - } + State = state; + Account = account; + Servers = new List(); } - public static partial class EventSink - { - public static event Action ServerList; + public NetState State { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeServerList(ServerListEventArgs e) => ServerList?.Invoke(e); + 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/SocketConnectionEvent.cs b/Projects/Server/Events/SocketConnectionEvent.cs index c39756a99..940e16537 100644 --- a/Projects/Server/Events/SocketConnectionEvent.cs +++ b/Projects/Server/Events/SocketConnectionEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SocketConnectionEvent.cs * * * @@ -17,26 +17,25 @@ using System; using System.Net.Sockets; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public class SocketConnectEventArgs { - public class SocketConnectEventArgs + public SocketConnectEventArgs(Socket c) { - public SocketConnectEventArgs(Socket c) - { - Connection = c; - AllowConnection = true; - } - - public Socket Connection { get; } - - public bool AllowConnection { get; set; } + Connection = c; + AllowConnection = true; } - public static partial class EventSink - { - public static event Action SocketConnect; + public Socket Connection { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeSocketConnect(SocketConnectEventArgs e) => SocketConnect?.Invoke(e); - } + public bool AllowConnection { get; set; } +} + +public static partial class EventSink +{ + public static event Action SocketConnect; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeSocketConnect(SocketConnectEventArgs e) => SocketConnect?.Invoke(e); } diff --git a/Projects/Server/Events/SpeechEvent.cs b/Projects/Server/Events/SpeechEvent.cs index 0c17dff18..43833244f 100644 --- a/Projects/Server/Events/SpeechEvent.cs +++ b/Projects/Server/Events/SpeechEvent.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SpeechEvent.cs * * * @@ -17,52 +17,51 @@ using System; using System.Runtime.CompilerServices; using Server.Network; -namespace Server +namespace Server; + +public class SpeechEventArgs { - public class SpeechEventArgs + public SpeechEventArgs(Mobile mobile, string speech, MessageType type, int hue, int[] keywords) { - public SpeechEventArgs(Mobile mobile, string speech, MessageType type, int hue, int[] keywords) - { - Mobile = mobile; - Speech = speech; - Type = type; - Hue = hue; - Keywords = keywords; - } - - public Mobile Mobile { get; } - - public string Speech { get; set; } - - public MessageType Type { get; } - - public int Hue { get; } - - public int[] Keywords { get; } - - public bool Handled { get; set; } - - public bool Blocked { get; set; } - - public bool HasKeyword(int keyword) - { - for (var i = 0; i < Keywords.Length; ++i) - { - if (Keywords[i] == keyword) - { - return true; - } - } - - return false; - } + Mobile = mobile; + Speech = speech; + Type = type; + Hue = hue; + Keywords = keywords; } - public static partial class EventSink - { - public static event Action Speech; + public Mobile Mobile { get; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeSpeech(SpeechEventArgs e) => Speech?.Invoke(e); + public string Speech { get; set; } + + public MessageType Type { get; } + + public int Hue { get; } + + public int[] Keywords { get; } + + public bool Handled { get; set; } + + public bool Blocked { get; set; } + + public bool HasKeyword(int keyword) + { + for (var i = 0; i < Keywords.Length; ++i) + { + if (Keywords[i] == keyword) + { + return true; + } + } + + return false; } } + +public static partial class EventSink +{ + public static event Action Speech; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeSpeech(SpeechEventArgs e) => Speech?.Invoke(e); +} diff --git a/Projects/Server/Exceptions/ExceptionExtensions.cs b/Projects/Server/Exceptions/ExceptionExtensions.cs index 48eafddd1..7fd2fc119 100644 --- a/Projects/Server/Exceptions/ExceptionExtensions.cs +++ b/Projects/Server/Exceptions/ExceptionExtensions.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ExceptionExtensions.cs * * * @@ -18,25 +18,24 @@ using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; -namespace Server.Exceptions +namespace Server.Exceptions; + +public static class ExceptionExtensions { - public static class ExceptionExtensions + public static Exception SetStackTrace(this Exception target, StackTrace stack) => _setStackTrace(target, stack); + + private static readonly Func _setStackTrace = _createStackTraceMethod(); + + private static Func _createStackTraceMethod() { - public static Exception SetStackTrace(this Exception target, StackTrace stack) => _setStackTrace(target, stack); - - private static readonly Func _setStackTrace = _createStackTraceMethod(); - - private static Func _createStackTraceMethod() - { - ParameterExpression target = Expression.Parameter(typeof(Exception)); - ParameterExpression stack = Expression.Parameter(typeof(StackTrace)); - Type traceFormatType = typeof(StackTrace).GetNestedType("TraceFormat", BindingFlags.NonPublic); - MethodInfo toString = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { traceFormatType }, null); - object normalTraceFormat = Enum.GetValues(traceFormatType!).GetValue(0); - MethodCallExpression stackTraceString = Expression.Call(stack, toString!, Expression.Constant(normalTraceFormat, traceFormatType)); - FieldInfo stackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance); - BinaryExpression assign = Expression.Assign(Expression.Field(target, stackTraceStringField!), stackTraceString); - return Expression.Lambda>(Expression.Block(assign, target), target, stack).Compile(); - } + ParameterExpression target = Expression.Parameter(typeof(Exception)); + ParameterExpression stack = Expression.Parameter(typeof(StackTrace)); + Type traceFormatType = typeof(StackTrace).GetNestedType("TraceFormat", BindingFlags.NonPublic); + MethodInfo toString = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { traceFormatType }, null); + object normalTraceFormat = Enum.GetValues(traceFormatType!).GetValue(0); + MethodCallExpression stackTraceString = Expression.Call(stack, toString!, Expression.Constant(normalTraceFormat, traceFormatType)); + FieldInfo stackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance); + BinaryExpression assign = Expression.Assign(Expression.Field(target, stackTraceStringField!), stackTraceString); + return Expression.Lambda>(Expression.Block(assign, target), target, stack).Compile(); } } diff --git a/Projects/Server/Exceptions/InvalidThreadException.cs b/Projects/Server/Exceptions/InvalidThreadException.cs index de199ceb6..88d92a02a 100644 --- a/Projects/Server/Exceptions/InvalidThreadException.cs +++ b/Projects/Server/Exceptions/InvalidThreadException.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: InvalidThreadException.cs * * * @@ -15,12 +15,11 @@ using System; -namespace Server.Exceptions +namespace Server.Exceptions; + +public class InvalidThreadException : Exception { - public class InvalidThreadException : Exception + public InvalidThreadException(string methodName) : base($"{methodName} executed on an invalid thread.") { - public InvalidThreadException(string methodName) : base($"{methodName} executed on an invalid thread.") - { - } } } diff --git a/Projects/Server/Exceptions/MaxConnectionsException.cs b/Projects/Server/Exceptions/MaxConnectionsException.cs index f0b35954e..746ebcec8 100644 --- a/Projects/Server/Exceptions/MaxConnectionsException.cs +++ b/Projects/Server/Exceptions/MaxConnectionsException.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MaxConnectionsException.cs * * * @@ -15,12 +15,11 @@ using System; -namespace Server.Exceptions +namespace Server.Exceptions; + +public class MaxConnectionsException : Exception { - public class MaxConnectionsException : Exception + public MaxConnectionsException() : base("Maximum connections exceeded") { - public MaxConnectionsException() : base("Maximum connections exceeded") - { - } } } diff --git a/Projects/Server/ExpansionInfo.cs b/Projects/Server/ExpansionInfo.cs index 3bd7fcf81..671c2e716 100644 --- a/Projects/Server/ExpansionInfo.cs +++ b/Projects/Server/ExpansionInfo.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ExpansionInfo.cs * * * diff --git a/Projects/Server/Geometry/Point2D.cs b/Projects/Server/Geometry/Point2D.cs index efee210dd..63bee4c40 100644 --- a/Projects/Server/Geometry/Point2D.cs +++ b/Projects/Server/Geometry/Point2D.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Point2D.cs * * * @@ -15,102 +15,101 @@ using System; -namespace Server +namespace Server; + +[Parsable] +public struct Point2D + : IPoint2D, IComparable, IComparable, IEquatable, IEquatable, + IEquatable { - [Parsable] - public struct Point2D - : IPoint2D, IComparable, IComparable, IEquatable, IEquatable, - IEquatable + internal int m_X; + internal int m_Y; + + public static readonly Point2D Zero = new(0, 0); + + [CommandProperty(AccessLevel.Counselor)] + public int X { - internal int m_X; - internal int m_Y; + get => m_X; + set => m_X = value; + } - public static readonly Point2D Zero = new(0, 0); + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Y; + set => m_Y = value; + } - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_X; - set => m_X = value; - } + public Point2D(int x, int y) + { + m_X = x; + m_Y = y; + } - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Y; - set => m_Y = value; - } + public Point2D(Point2D p) : this(p.X, p.Y) + { + } - public Point2D(int x, int y) - { - m_X = x; - m_Y = y; - } + public override string ToString() => $"({m_X}, {m_Y})"; - public Point2D(Point2D p) : this(p.X, p.Y) - { - } + public static Point2D Parse(string value) + { + var start = value.IndexOfOrdinal('('); + var end = value.IndexOf(',', start + 1); - public override string ToString() => $"({m_X}, {m_Y})"; + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var x); - public static Point2D Parse(string value) - { - var start = value.IndexOfOrdinal('('); - var end = value.IndexOf(',', start + 1); + start = end; + end = value.IndexOf(')', start + 1); - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var x); + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var y); - start = end; - end = value.IndexOf(')', start + 1); + return new Point2D(x, y); + } - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var y); + public bool Equals(Point2D other) => m_X == other.m_X && m_Y == other.m_Y; - return new Point2D(x, y); - } + public bool Equals(IPoint2D other) => + m_X == other?.X && m_Y == other.Y; - public bool Equals(Point2D other) => m_X == other.m_X && m_Y == other.m_Y; + public override bool Equals(object obj) => obj is Point2D other && Equals(other); - public bool Equals(IPoint2D other) => - m_X == other?.X && m_Y == other.Y; + public override int GetHashCode() => HashCode.Combine(m_X, m_Y); - public override bool Equals(object obj) => obj is Point2D other && Equals(other); + public static bool operator ==(Point2D l, Point2D r) => l.m_X == r.m_X && l.m_Y == r.m_Y; - public override int GetHashCode() => HashCode.Combine(m_X, m_Y); + public static bool operator !=(Point2D l, Point2D r) => l.m_X != r.m_X || l.m_Y != r.m_Y; - public static bool operator ==(Point2D l, Point2D r) => l.m_X == r.m_X && l.m_Y == r.m_Y; + public static bool operator ==(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y; - public static bool operator !=(Point2D l, Point2D r) => l.m_X != r.m_X || l.m_Y != r.m_Y; + public static bool operator !=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y); - public static bool operator ==(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y; + public static bool operator >(Point2D l, Point2D r) => l.m_X > r.m_X && l.m_Y > r.m_Y; - public static bool operator !=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y); + public static bool operator >(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y; - public static bool operator >(Point2D l, Point2D r) => l.m_X > r.m_X && l.m_Y > r.m_Y; + public static bool operator <(Point2D l, Point2D r) => l.m_X < r.m_X && l.m_Y < r.m_Y; - public static bool operator >(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y; + public static bool operator <(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y; - public static bool operator <(Point2D l, Point2D r) => l.m_X < r.m_X && l.m_Y < r.m_Y; + public static bool operator >=(Point2D l, Point2D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y; - public static bool operator <(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y; + public static bool operator >=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y; - public static bool operator >=(Point2D l, Point2D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y; + public static bool operator <=(Point2D l, Point2D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y; - public static bool operator >=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y; + public static bool operator <=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y; - public static bool operator <=(Point2D l, Point2D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y; + public int CompareTo(Point2D other) + { + var xComparison = m_X.CompareTo(other.m_X); + return xComparison != 0 ? xComparison : m_Y.CompareTo(other.m_Y); + } - public static bool operator <=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y; - - public int CompareTo(Point2D other) - { - var xComparison = m_X.CompareTo(other.m_X); - return xComparison != 0 ? xComparison : m_Y.CompareTo(other.m_Y); - } - - public int CompareTo(IPoint2D other) - { - var xComparison = m_X.CompareTo(other.X); - return xComparison != 0 ? xComparison : m_Y.CompareTo(other.Y); - } + public int CompareTo(IPoint2D other) + { + var xComparison = m_X.CompareTo(other.X); + return xComparison != 0 ? xComparison : m_Y.CompareTo(other.Y); } } diff --git a/Projects/Server/Geometry/Point3D.cs b/Projects/Server/Geometry/Point3D.cs index 96ce2f94e..e08ca3861 100644 --- a/Projects/Server/Geometry/Point3D.cs +++ b/Projects/Server/Geometry/Point3D.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Point3D.cs * * * @@ -16,153 +16,152 @@ using System; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +[Parsable] +public struct Point3D + : IPoint3D, IComparable, IComparable, IEquatable, IEquatable, + IEquatable { - [Parsable] - public struct Point3D - : IPoint3D, IComparable, IComparable, IEquatable, IEquatable, - IEquatable + internal int m_X; + internal int m_Y; + internal int m_Z; + + public static readonly Point3D Zero = new(0, 0, 0); + + [CommandProperty(AccessLevel.Counselor)] + public int X { - internal int m_X; - internal int m_Y; - internal int m_Z; + get => m_X; + set => m_X = value; + } - public static readonly Point3D Zero = new(0, 0, 0); + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Y; + set => m_Y = value; + } - [CommandProperty(AccessLevel.Counselor)] - public int X + [CommandProperty(AccessLevel.Counselor)] + public int Z + { + get => m_Z; + set => m_Z = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Point3D(IPoint3D p) : this(p.X, p.Y, p.Z) + { + } + + public Point3D(Point3D p) : this(p.X, p.Y, p.Z) + { + } + + public Point3D(Point2D p, int z) : this(p.X, p.Y, z) + { + } + + public Point3D(int x, int y, int z) + { + m_X = x; + m_Y = y; + m_Z = z; + } + + public override string ToString() => $"({m_X}, {m_Y}, {m_Z})"; + + public bool Equals(Point3D other) => m_X == other.m_X && m_Y == other.m_Y && m_Z == other.m_Z; + + public bool Equals(IPoint3D other) => + m_X == other?.X && m_Y == other.Y && m_Z == other.Z; + + public override bool Equals(object obj) => obj is Point3D other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(m_X, m_Y, m_Z); + + public static Point3D Parse(string value) + { + var start = value.IndexOfOrdinal('('); + var end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var x); + + start = end; + end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var y); + + start = end; + end = value.IndexOf(')', start + 1); + + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var z); + + return new Point3D(x, y, z); + } + + public static bool operator ==(Point3D l, Point3D r) => l.m_X == r.m_X && l.m_Y == r.m_Y && l.m_Z == r.m_Z; + + public static bool operator ==(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y && l.m_Z == r.Z; + + public static bool operator !=(Point3D l, Point3D r) => l.m_X != r.m_X || l.m_Y != r.m_Y || l.m_Z != r.m_Z; + + public static bool operator !=(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y || l.m_Z != r.Z); + + public static bool operator >(Point3D l, Point3D r) => l.m_X > r.m_X && l.m_Y > r.m_Y && l.m_Z > r.m_Z; + + public static bool operator >(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y && l.m_Z > r.Z; + + public static bool operator <(Point3D l, Point3D r) => l.m_X < r.m_X && l.m_Y < r.m_Y && l.m_Z > r.m_Z; + + public static bool operator <(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y && l.m_Z > r.Z; + + public static bool operator >=(Point3D l, Point3D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y && l.m_Z > r.m_Z; + + public static bool operator >=(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y && l.m_Z > r.Z; + + public static bool operator <=(Point3D l, Point3D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y && l.m_Z > r.m_Z; + + public static bool operator <=(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y && l.m_Z > r.Z; + + public int CompareTo(Point3D other) + { + var xComparison = m_X.CompareTo(other.m_X); + if (xComparison != 0) { - get => m_X; - set => m_X = value; + return xComparison; } - [CommandProperty(AccessLevel.Counselor)] - public int Y + var yComparison = m_Y.CompareTo(other.m_Y); + if (yComparison != 0) { - get => m_Y; - set => m_Y = value; + return yComparison; } - [CommandProperty(AccessLevel.Counselor)] - public int Z + return m_Z.CompareTo(other.m_Z); + } + + public int CompareTo(IPoint3D other) + { + var xComparison = m_X.CompareTo(other.X); + if (xComparison != 0) { - get => m_Z; - set => m_Z = value; + return xComparison; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Point3D(IPoint3D p) : this(p.X, p.Y, p.Z) + var yComparison = m_Y.CompareTo(other.Y); + if (yComparison != 0) { + return yComparison; } - public Point3D(Point3D p) : this(p.X, p.Y, p.Z) - { - } - - public Point3D(Point2D p, int z) : this(p.X, p.Y, z) - { - } - - public Point3D(int x, int y, int z) - { - m_X = x; - m_Y = y; - m_Z = z; - } - - public override string ToString() => $"({m_X}, {m_Y}, {m_Z})"; - - public bool Equals(Point3D other) => m_X == other.m_X && m_Y == other.m_Y && m_Z == other.m_Z; - - public bool Equals(IPoint3D other) => - m_X == other?.X && m_Y == other.Y && m_Z == other.Z; - - public override bool Equals(object obj) => obj is Point3D other && Equals(other); - - public override int GetHashCode() => HashCode.Combine(m_X, m_Y, m_Z); - - public static Point3D Parse(string value) - { - var start = value.IndexOfOrdinal('('); - var end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var x); - - start = end; - end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var y); - - start = end; - end = value.IndexOf(')', start + 1); - - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var z); - - return new Point3D(x, y, z); - } - - public static bool operator ==(Point3D l, Point3D r) => l.m_X == r.m_X && l.m_Y == r.m_Y && l.m_Z == r.m_Z; - - public static bool operator ==(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y && l.m_Z == r.Z; - - public static bool operator !=(Point3D l, Point3D r) => l.m_X != r.m_X || l.m_Y != r.m_Y || l.m_Z != r.m_Z; - - public static bool operator !=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y || l.m_Z != r.Z); - - public static bool operator >(Point3D l, Point3D r) => l.m_X > r.m_X && l.m_Y > r.m_Y && l.m_Z > r.m_Z; - - public static bool operator >(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y && l.m_Z > r.Z; - - public static bool operator <(Point3D l, Point3D r) => l.m_X < r.m_X && l.m_Y < r.m_Y && l.m_Z > r.m_Z; - - public static bool operator <(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y && l.m_Z > r.Z; - - public static bool operator >=(Point3D l, Point3D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y && l.m_Z > r.m_Z; - - public static bool operator >=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y && l.m_Z > r.Z; - - public static bool operator <=(Point3D l, Point3D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y && l.m_Z > r.m_Z; - - public static bool operator <=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y && l.m_Z > r.Z; - - public int CompareTo(Point3D other) - { - var xComparison = m_X.CompareTo(other.m_X); - if (xComparison != 0) - { - return xComparison; - } - - var yComparison = m_Y.CompareTo(other.m_Y); - if (yComparison != 0) - { - return yComparison; - } - - return m_Z.CompareTo(other.m_Z); - } - - public int CompareTo(IPoint3D other) - { - var xComparison = m_X.CompareTo(other.X); - if (xComparison != 0) - { - return xComparison; - } - - var yComparison = m_Y.CompareTo(other.Y); - if (yComparison != 0) - { - return yComparison; - } - - return m_Z.CompareTo(other.Z); - } + return m_Z.CompareTo(other.Z); } } diff --git a/Projects/Server/Geometry/Point3DList.cs b/Projects/Server/Geometry/Point3DList.cs index 6db81bcc8..244a20def 100644 --- a/Projects/Server/Geometry/Point3DList.cs +++ b/Projects/Server/Geometry/Point3DList.cs @@ -1,84 +1,83 @@ using System; -namespace Server +namespace Server; + +public class Point3DList { - public class Point3DList + private static readonly Point3D[] m_EmptyList = Array.Empty(); + private Point3D[] m_List; + + public Point3DList() { - private static readonly Point3D[] m_EmptyList = Array.Empty(); - private Point3D[] m_List; + m_List = new Point3D[8]; + Count = 0; + } - public Point3DList() + public int Count { get; private set; } + + public Point3D Last => m_List[Count - 1]; + + public Point3D this[int index] => m_List[index]; + + public void Clear() + { + Count = 0; + } + + public void Add(int x, int y, int z) + { + if (Count + 1 > m_List.Length) { - m_List = new Point3D[8]; - Count = 0; - } + var old = m_List; + m_List = new Point3D[old.Length * 2]; - public int Count { get; private set; } - - public Point3D Last => m_List[Count - 1]; - - public Point3D this[int index] => m_List[index]; - - public void Clear() - { - Count = 0; - } - - public void Add(int x, int y, int z) - { - if (Count + 1 > m_List.Length) + for (var i = 0; i < old.Length; ++i) { - var old = m_List; - m_List = new Point3D[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - { - m_List[i] = old[i]; - } + m_List[i] = old[i]; } - - m_List[Count].m_X = x; - m_List[Count].m_Y = y; - m_List[Count].m_Z = z; - ++Count; } - public void Add(Point3D p) + m_List[Count].m_X = x; + m_List[Count].m_Y = y; + m_List[Count].m_Z = z; + ++Count; + } + + public void Add(Point3D p) + { + if (Count + 1 > m_List.Length) { - if (Count + 1 > m_List.Length) + var old = m_List; + m_List = new Point3D[old.Length * 2]; + + for (var i = 0; i < old.Length; ++i) { - var old = m_List; - m_List = new Point3D[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - { - m_List[i] = old[i]; - } + m_List[i] = old[i]; } - - m_List[Count].m_X = p.m_X; - m_List[Count].m_Y = p.m_Y; - m_List[Count].m_Z = p.m_Z; - ++Count; } - public Point3D[] ToArray() + m_List[Count].m_X = p.m_X; + m_List[Count].m_Y = p.m_Y; + m_List[Count].m_Z = p.m_Z; + ++Count; + } + + public Point3D[] ToArray() + { + if (Count == 0) { - if (Count == 0) - { - return m_EmptyList; - } - - var list = new Point3D[Count]; - - for (var i = 0; i < Count; ++i) - { - list[i] = m_List[i]; - } - - Count = 0; - - return list; + return m_EmptyList; } + + var list = new Point3D[Count]; + + for (var i = 0; i < Count; ++i) + { + list[i] = m_List[i]; + } + + Count = 0; + + return list; } } diff --git a/Projects/Server/Geometry/Rectangle2D.cs b/Projects/Server/Geometry/Rectangle2D.cs index ad3550dd6..8079c210f 100644 --- a/Projects/Server/Geometry/Rectangle2D.cs +++ b/Projects/Server/Geometry/Rectangle2D.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Rectangle2D.cs * * * diff --git a/Projects/Server/Geometry/Rectangle3D.cs b/Projects/Server/Geometry/Rectangle3D.cs index 48ee66b75..9ea17ea96 100644 --- a/Projects/Server/Geometry/Rectangle3D.cs +++ b/Projects/Server/Geometry/Rectangle3D.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Rectangle3D.cs * * * @@ -13,130 +13,129 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server +namespace Server; + +[NoSort] +[PropertyObject] +public struct Rectangle3D { - [NoSort] - [PropertyObject] - public struct Rectangle3D + private Point3D m_Start; + private Point3D m_End; + + public Rectangle3D(Point3D start, Point3D end) { - private Point3D m_Start; - private Point3D m_End; - - public Rectangle3D(Point3D start, Point3D end) - { - m_Start = start; - m_End = end; - } - - public Rectangle3D(int x, int y, int z, int width, int height, int depth) - { - m_Start = new Point3D(x, y, z); - m_End = new Point3D(x + width, y + height, z + depth); - } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D Start - { - get => m_Start; - set => m_Start = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D End - { - get => m_End; - set => m_End = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_Start.m_X; - set => m_Start.m_X = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Start.m_Y; - set => m_Start.m_Y = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Z - { - get => m_Start.m_Z; - set => m_Start.m_Z = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Width => m_End.X - m_Start.X; - - [CommandProperty(AccessLevel.Counselor)] - public int Height => m_End.Y - m_Start.Y; - - [CommandProperty(AccessLevel.Counselor)] - public int Depth => m_End.Z - m_Start.Z; - - public void MakeHold(Rectangle3D r) - { - if (r.m_Start.m_X < m_Start.m_X) - { - m_Start.m_X = r.m_Start.m_X; - } - - if (r.m_Start.m_Y < m_Start.m_Y) - { - m_Start.m_Y = r.m_Start.m_Y; - } - - if (r.m_Start.m_Z < m_Start.m_Z) - { - m_Start.m_Z = r.m_Start.m_Z; - } - - if (r.m_End.m_X > m_End.m_X) - { - m_End.m_X = r.m_End.m_X; - } - - if (r.m_End.m_Y > m_End.m_Y) - { - m_End.m_Y = r.m_End.m_Y; - } - - if (r.m_End.m_Z < m_End.m_Z) - { - m_End.m_Z = r.m_End.m_Z; - } - } - - public bool Contains(Point3D p) => - p.m_X >= m_Start.m_X - && p.m_X < m_End.m_X - && p.m_Y >= m_Start.m_Y - && p.m_Y < m_End.m_Y - && p.m_Z >= m_Start.m_Z - && p.m_Z < m_End.m_Z; - - public bool Contains(Point2D p) => - p.m_X >= m_Start.m_X - && p.m_X < m_End.m_X - && p.m_Y >= m_Start.m_Y - && p.m_Y < m_End.m_Y; - - public bool Contains(IPoint2D p) => - p.X >= m_Start.m_X - && p.X < m_End.m_X - && p.Y >= m_Start.m_Y - && p.Y < m_End.m_Y; - - public bool Contains(IPoint3D p) => - p.X >= m_Start.m_X - && p.X < m_End.m_X - && p.Y >= m_Start.m_Y - && p.Y < m_End.m_Y - && p.Z >= m_Start.m_Z - && p.Z < m_End.m_Z; + m_Start = start; + m_End = end; } + + public Rectangle3D(int x, int y, int z, int width, int height, int depth) + { + m_Start = new Point3D(x, y, z); + m_End = new Point3D(x + width, y + height, z + depth); + } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Start + { + get => m_Start; + set => m_Start = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D End + { + get => m_End; + set => m_End = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int X + { + get => m_Start.m_X; + set => m_Start.m_X = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Start.m_Y; + set => m_Start.m_Y = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Z + { + get => m_Start.m_Z; + set => m_Start.m_Z = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Width => m_End.X - m_Start.X; + + [CommandProperty(AccessLevel.Counselor)] + public int Height => m_End.Y - m_Start.Y; + + [CommandProperty(AccessLevel.Counselor)] + public int Depth => m_End.Z - m_Start.Z; + + public void MakeHold(Rectangle3D r) + { + if (r.m_Start.m_X < m_Start.m_X) + { + m_Start.m_X = r.m_Start.m_X; + } + + if (r.m_Start.m_Y < m_Start.m_Y) + { + m_Start.m_Y = r.m_Start.m_Y; + } + + if (r.m_Start.m_Z < m_Start.m_Z) + { + m_Start.m_Z = r.m_Start.m_Z; + } + + if (r.m_End.m_X > m_End.m_X) + { + m_End.m_X = r.m_End.m_X; + } + + if (r.m_End.m_Y > m_End.m_Y) + { + m_End.m_Y = r.m_End.m_Y; + } + + if (r.m_End.m_Z < m_End.m_Z) + { + m_End.m_Z = r.m_End.m_Z; + } + } + + public bool Contains(Point3D p) => + p.m_X >= m_Start.m_X + && p.m_X < m_End.m_X + && p.m_Y >= m_Start.m_Y + && p.m_Y < m_End.m_Y + && p.m_Z >= m_Start.m_Z + && p.m_Z < m_End.m_Z; + + public bool Contains(Point2D p) => + p.m_X >= m_Start.m_X + && p.m_X < m_End.m_X + && p.m_Y >= m_Start.m_Y + && p.m_Y < m_End.m_Y; + + public bool Contains(IPoint2D p) => + p.X >= m_Start.m_X + && p.X < m_End.m_X + && p.Y >= m_Start.m_Y + && p.Y < m_End.m_Y; + + public bool Contains(IPoint3D p) => + p.X >= m_Start.m_X + && p.X < m_End.m_X + && p.Y >= m_Start.m_Y + && p.Y < m_End.m_Y + && p.Z >= m_Start.m_Z + && p.Z < m_End.m_Z; } diff --git a/Projects/Server/Geometry/WorldLocation.cs b/Projects/Server/Geometry/WorldLocation.cs index f2e28eaac..9b6a5cd7c 100644 --- a/Projects/Server/Geometry/WorldLocation.cs +++ b/Projects/Server/Geometry/WorldLocation.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: WorldLocation.cs * * * @@ -16,153 +16,152 @@ using System; using System.Collections.Generic; -namespace Server +namespace Server; + +[Parsable] +public struct WorldLocation + : IPoint3D, IComparable, IEquatable, IEquatable, IEquatable { - [Parsable] - public struct WorldLocation - : IPoint3D, IComparable, IEquatable, IEquatable, IEquatable + internal Point3D m_Loc; + internal Map m_Map; + + public static readonly WorldLocation Zero = new(0, 0, 0, Map.Internal); + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Location { - internal Point3D m_Loc; - internal Map m_Map; + get => m_Loc; + set => m_Loc = value; + } - public static readonly WorldLocation Zero = new(0, 0, 0, Map.Internal); + [CommandProperty(AccessLevel.Counselor)] + public int X + { + get => m_Loc.m_X; + set => m_Loc.m_X = value; + } - [CommandProperty(AccessLevel.Counselor)] - public Point3D Location - { - get => m_Loc; - set => m_Loc = value; - } + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Loc.m_Y; + set => m_Loc.m_Y = value; + } - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_Loc.m_X; - set => m_Loc.m_X = value; - } + [CommandProperty(AccessLevel.Counselor)] + public int Z + { + get => m_Loc.m_Z; + set => m_Loc.m_Z = value; + } - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Loc.m_Y; - set => m_Loc.m_Y = value; - } + [CommandProperty(AccessLevel.Counselor)] + public Map Map + { + get => m_Map; + set => m_Map = value; + } - [CommandProperty(AccessLevel.Counselor)] - public int Z - { - get => m_Loc.m_Z; - set => m_Loc.m_Z = value; - } + public WorldLocation(IEntity e) : this(e.Location.X, e.Location.Y, e.Location.Z, e.Map) + { + } - [CommandProperty(AccessLevel.Counselor)] - public Map Map - { - get => m_Map; - set => m_Map = value; - } + public WorldLocation(Point2D p, Map map) : this(p.X, p.Y, 0, map) + { + } - public WorldLocation(IEntity e) : this(e.Location.X, e.Location.Y, e.Location.Z, e.Map) - { - } + public WorldLocation(int x, int y, Map map) : this(x, y, 0, map) + { + } - public WorldLocation(Point2D p, Map map) : this(p.X, p.Y, 0, map) - { - } + public WorldLocation(Point3D p, Map map) : this(p.X, p.Y, p.Z, map) + { + } - public WorldLocation(int x, int y, Map map) : this(x, y, 0, map) - { - } + public WorldLocation(int x, int y, int z, Map map) + { + m_Loc.m_X = x; + m_Loc.m_Y = y; + m_Loc.m_Z = z; + m_Map = map; + } - public WorldLocation(Point3D p, Map map) : this(p.X, p.Y, p.Z, map) - { - } + public override string ToString() => + $"({m_Loc.m_X}, {m_Loc.m_Y}, {m_Loc.m_Z}, {m_Map?.ToString() ?? "(-null-)"})"; - public WorldLocation(int x, int y, int z, Map map) - { - m_Loc.m_X = x; - m_Loc.m_Y = y; - m_Loc.m_Z = z; - m_Map = map; - } + public bool Equals(WorldLocation other) => + m_Loc.Equals(other.m_Loc) && m_Map.MapID == other.m_Map.MapID; - public override string ToString() => - $"({m_Loc.m_X}, {m_Loc.m_Y}, {m_Loc.m_Z}, {m_Map?.ToString() ?? "(-null-)"})"; + public bool Equals(IEntity other) => + !ReferenceEquals(other, null) && m_Loc == other.Location && + m_Map.MapID == other.Map.MapID; - public bool Equals(WorldLocation other) => - m_Loc.Equals(other.m_Loc) && m_Map.MapID == other.m_Map.MapID; + public override bool Equals(object obj) => + obj is WorldLocation other && Equals(other); - public bool Equals(IEntity other) => - !ReferenceEquals(other, null) && m_Loc == other.Location && - m_Map.MapID == other.Map.MapID; + public override int GetHashCode() => HashCode.Combine(m_Loc, m_Map); - public override bool Equals(object obj) => - obj is WorldLocation other && Equals(other); + public int CompareTo(WorldLocation other) + { + var locComparison = m_Loc.CompareTo(other.m_Loc); + return locComparison != 0 ? locComparison : Comparer.Default.Compare(m_Map, other.m_Map); + } - public override int GetHashCode() => HashCode.Combine(m_Loc, m_Map); + public static implicit operator Point3D(WorldLocation worldLocation) => worldLocation.Location; - public int CompareTo(WorldLocation other) - { - var locComparison = m_Loc.CompareTo(other.m_Loc); - return locComparison != 0 ? locComparison : Comparer.Default.Compare(m_Map, other.m_Map); - } + public static bool operator ==(WorldLocation l, WorldLocation r) => + l.m_Loc == r.m_Loc && l.m_Map == r.m_Map; - public static implicit operator Point3D(WorldLocation worldLocation) => worldLocation.Location; + public static bool operator ==(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc == r.Location && l.m_Map == r.Map; - public static bool operator ==(WorldLocation l, WorldLocation r) => - l.m_Loc == r.m_Loc && l.m_Map == r.m_Map; + public static bool operator !=(WorldLocation l, WorldLocation r) => l.m_Loc != r.m_Loc && l.m_Map != r.m_Map; - public static bool operator ==(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc == r.Location && l.m_Map == r.Map; + public static bool operator !=(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc != r.Location && l.m_Map != r.Map; - public static bool operator !=(WorldLocation l, WorldLocation r) => l.m_Loc != r.m_Loc && l.m_Map != r.m_Map; + public static bool operator >(WorldLocation l, WorldLocation r) => l.m_Loc > r.m_Loc && l.m_Map == r.m_Map; - public static bool operator !=(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc != r.Location && l.m_Map != r.Map; + public static bool operator >(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc > r.Location && l.m_Map == r.Map; - public static bool operator >(WorldLocation l, WorldLocation r) => l.m_Loc > r.m_Loc && l.m_Map == r.m_Map; + public static bool operator <(WorldLocation l, WorldLocation r) => l.m_Loc < r.m_Loc && l.m_Map == r.m_Map; - public static bool operator >(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc > r.Location && l.m_Map == r.Map; + public static bool operator <(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc < r.Location && l.m_Map == r.Map; - public static bool operator <(WorldLocation l, WorldLocation r) => l.m_Loc < r.m_Loc && l.m_Map == r.m_Map; + public static bool operator >=(WorldLocation l, WorldLocation r) => l.m_Loc >= r.m_Loc && l.m_Map == r.m_Map; - public static bool operator <(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc < r.Location && l.m_Map == r.Map; + public static bool operator >=(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc >= r.Location && l.m_Map == r.Map; - public static bool operator >=(WorldLocation l, WorldLocation r) => l.m_Loc >= r.m_Loc && l.m_Map == r.m_Map; + public static bool operator <=(WorldLocation l, WorldLocation r) => l.m_Loc <= r.m_Loc && l.m_Map == r.m_Map; - public static bool operator >=(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc >= r.Location && l.m_Map == r.Map; + public static bool operator <=(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc <= r.Location && l.m_Map == r.Map; - public static bool operator <=(WorldLocation l, WorldLocation r) => l.m_Loc <= r.m_Loc && l.m_Map == r.m_Map; + public static WorldLocation Parse(string value) + { + var start = value.IndexOfOrdinal('('); + var end = value.IndexOf(',', start + 1); - public static bool operator <=(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc <= r.Location && l.m_Map == r.Map; + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var x); - public static WorldLocation Parse(string value) - { - var start = value.IndexOfOrdinal('('); - var end = value.IndexOf(',', start + 1); + start = end; + end = value.IndexOf(',', start + 1); - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var x); + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var y); - start = end; - end = value.IndexOf(',', start + 1); + start = end; + end = value.IndexOf(',', start + 1); - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var y); + Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var z); - start = end; - end = value.IndexOf(',', start + 1); + start = end; + end = value.IndexOf(')', start + 1); - Utility.ToInt32(value.AsSpan(start + 1, end - (start + 1)).Trim(), out var z); + var map = Map.Parse(value.AsSpan(start + 1, end - (start + 1)).Trim()); - start = end; - end = value.IndexOf(')', start + 1); - - var map = Map.Parse(value.AsSpan(start + 1, end - (start + 1)).Trim()); - - return new WorldLocation(x, y, z, map); - } + return new WorldLocation(x, y, z, map); } } diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs index 27fb2015b..b006864d4 100644 --- a/Projects/Server/Guild.cs +++ b/Projects/Server/Guild.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Guild.cs * * * @@ -16,125 +16,124 @@ using System; using System.Collections.Generic; -namespace Server.Guilds +namespace Server.Guilds; + +public enum GuildType { - public enum GuildType - { - Regular, - Chaos, - Order - } - - public abstract class BaseGuild : ISerializable - { - protected BaseGuild() - { - Serial = World.NewGuild; - World.AddGuild(this); - - SetTypeRef(GetType()); - } - - protected BaseGuild(Serial serial) - { - Serial = serial; - SetTypeRef(GetType()); - } - - public void SetTypeRef(Type type) - { - TypeRef = World.GuildTypes.IndexOf(type); - - if (TypeRef == -1) - { - World.GuildTypes.Add(type); - TypeRef = World.GuildTypes.Count - 1; - } - } - - public abstract string Abbreviation { get; set; } - public abstract string Name { get; set; } - public abstract GuildType Type { get; set; } - public abstract bool Disbanded { get; } - public abstract void Delete(); - - public bool Deleted => Disbanded; - - [CommandProperty(AccessLevel.Counselor)] - public Serial Serial { get; } - - [CommandProperty(AccessLevel.GameMaster, readOnly: true)] - public DateTime Created { get; set; } = Core.Now; - - [CommandProperty(AccessLevel.GameMaster)] - DateTime ISerializable.LastSerialized { get; set; } = Core.Now; - - long ISerializable.SavePosition { get; set; } = -1; - - BufferWriter ISerializable.SaveBuffer { get; set; } - - public int TypeRef { get; private set; } - - public abstract void BeforeSerialize(); - public abstract void Serialize(IGenericWriter writer); - - public abstract void Deserialize(IGenericReader reader); - public abstract void OnDelete(Mobile mob); - - public static BaseGuild FindByName(string name) - { - foreach (var g in World.Guilds.Values) - { - if (g.Name == name) - { - return g; - } - } - - return null; - } - - public static BaseGuild FindByAbbrev(string abbr) - { - foreach (var g in World.Guilds.Values) - { - if (g.Abbreviation == abbr) - { - return g; - } - } - - return null; - } - - public static HashSet Search(string find) - { - var words = find.ToLower().Split(' '); - var results = new HashSet(); - - foreach (var g in World.Guilds.Values) - { - var name = g.Name; - - bool all = true; - foreach (var t in words) - { - if (name.InsensitiveIndexOf(t) == -1) - { - all = false; - break; - } - } - - if (all) - { - results.Add(g); - } - } - - return results; - } - - public override string ToString() => $"{Serial} \"{Name} [{Abbreviation}]\""; - } + Regular, + Chaos, + Order +} + +public abstract class BaseGuild : ISerializable +{ + protected BaseGuild() + { + Serial = World.NewGuild; + World.AddGuild(this); + + SetTypeRef(GetType()); + } + + protected BaseGuild(Serial serial) + { + Serial = serial; + SetTypeRef(GetType()); + } + + public void SetTypeRef(Type type) + { + TypeRef = World.GuildTypes.IndexOf(type); + + if (TypeRef == -1) + { + World.GuildTypes.Add(type); + TypeRef = World.GuildTypes.Count - 1; + } + } + + public abstract string Abbreviation { get; set; } + public abstract string Name { get; set; } + public abstract GuildType Type { get; set; } + public abstract bool Disbanded { get; } + public abstract void Delete(); + + public bool Deleted => Disbanded; + + [CommandProperty(AccessLevel.Counselor)] + public Serial Serial { get; } + + [CommandProperty(AccessLevel.GameMaster, readOnly: true)] + public DateTime Created { get; set; } = Core.Now; + + [CommandProperty(AccessLevel.GameMaster)] + DateTime ISerializable.LastSerialized { get; set; } = Core.Now; + + long ISerializable.SavePosition { get; set; } = -1; + + BufferWriter ISerializable.SaveBuffer { get; set; } + + public int TypeRef { get; private set; } + + public abstract void BeforeSerialize(); + public abstract void Serialize(IGenericWriter writer); + + public abstract void Deserialize(IGenericReader reader); + public abstract void OnDelete(Mobile mob); + + public static BaseGuild FindByName(string name) + { + foreach (var g in World.Guilds.Values) + { + if (g.Name == name) + { + return g; + } + } + + return null; + } + + public static BaseGuild FindByAbbrev(string abbr) + { + foreach (var g in World.Guilds.Values) + { + if (g.Abbreviation == abbr) + { + return g; + } + } + + return null; + } + + public static HashSet Search(string find) + { + var words = find.ToLower().Split(' '); + var results = new HashSet(); + + foreach (var g in World.Guilds.Values) + { + var name = g.Name; + + bool all = true; + foreach (var t in words) + { + if (name.InsensitiveIndexOf(t) == -1) + { + all = false; + break; + } + } + + if (all) + { + results.Add(g); + } + } + + return results; + } + + public override string ToString() => $"{Serial} \"{Name} [{Abbreviation}]\""; } diff --git a/Projects/Server/Gumps/GumpECHandleInput.cs b/Projects/Server/Gumps/GumpECHandleInput.cs index 88a6a45e6..504320154 100644 --- a/Projects/Server/Gumps/GumpECHandleInput.cs +++ b/Projects/Server/Gumps/GumpECHandleInput.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GumpECHandleInput.cs * * * diff --git a/Projects/Server/Gumps/GumpMasterGump.cs b/Projects/Server/Gumps/GumpMasterGump.cs index 45b3f3e53..3be5e1e36 100644 --- a/Projects/Server/Gumps/GumpMasterGump.cs +++ b/Projects/Server/Gumps/GumpMasterGump.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GumpMasterGump.cs * * * diff --git a/Projects/Server/Gumps/GumpSpriteImage.cs b/Projects/Server/Gumps/GumpSpriteImage.cs index 01d9a29bb..90610080e 100644 --- a/Projects/Server/Gumps/GumpSpriteImage.cs +++ b/Projects/Server/Gumps/GumpSpriteImage.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GumpSpriteImage.cs * * * diff --git a/Projects/Server/Gumps/GumpTooltip.cs b/Projects/Server/Gumps/GumpTooltip.cs index 3bf15b93b..25a565bd4 100644 --- a/Projects/Server/Gumps/GumpTooltip.cs +++ b/Projects/Server/Gumps/GumpTooltip.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GumpTooltip.cs * * * diff --git a/Projects/Server/HuePicker.cs b/Projects/Server/HuePicker.cs index ae67eb676..4ff0dc62c 100644 --- a/Projects/Server/HuePicker.cs +++ b/Projects/Server/HuePicker.cs @@ -1,33 +1,32 @@ using Server.Network; -namespace Server.HuePickers +namespace Server.HuePickers; + +public class HuePicker { - public class HuePicker + private static Serial _nextSerial = (Serial)1; + + public HuePicker(int itemID) { - private static Serial _nextSerial = (Serial)1; - - public HuePicker(int itemID) + do { - do - { - Serial = _nextSerial++; - } while (Serial == 0); + Serial = _nextSerial++; + } while (Serial == 0); - ItemID = itemID; - } + ItemID = itemID; + } - public Serial Serial { get; } + public Serial Serial { get; } - public int ItemID { get; } + public int ItemID { get; } - public virtual void OnResponse(int hue) - { - } + public virtual void OnResponse(int hue) + { + } - public void SendTo(NetState state) - { - state.SendDisplayHuePicker(Serial, ItemID); - state.AddHuePicker(this); - } + public void SendTo(NetState state) + { + state.SendDisplayHuePicker(Serial, ItemID); + state.AddHuePicker(this); } } diff --git a/Projects/Server/IAccount.cs b/Projects/Server/IAccount.cs index 04575082f..2e24f5e5b 100644 --- a/Projects/Server/IAccount.cs +++ b/Projects/Server/IAccount.cs @@ -1,113 +1,112 @@ using System; -namespace Server.Accounting +namespace Server.Accounting; + +public static class AccountGold { - public static class AccountGold + public static bool Enabled { get; private set; } + + /// + /// This amount specifies the value at which point Gold turns to Platinum. + /// By default, when 1,000,000,000 Gold is accumulated, it will transform + /// into 1 Platinum. + /// !!! WARNING !!! + /// The client is designed to perceive the currency threshold at 1,000,000,000 + /// if you change this, it may cause unexpected results when using secure trading. + /// + public static int CurrencyThreshold = 1000000000; + + /// + /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency + /// when they are added to a bank box container. + /// + public static bool ConvertOnBank { get; private set; } + + /// + /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency + /// when they are added to a secure trade container. + /// + public static bool ConvertOnTrade { get; private set; } + + public static void Configure() { - public static bool Enabled { get; private set; } - - /// - /// This amount specifies the value at which point Gold turns to Platinum. - /// By default, when 1,000,000,000 Gold is accumulated, it will transform - /// into 1 Platinum. - /// !!! WARNING !!! - /// The client is designed to perceive the currency threshold at 1,000,000,000 - /// if you change this, it may cause unexpected results when using secure trading. - /// - public static int CurrencyThreshold = 1000000000; - - /// - /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency - /// when they are added to a bank box container. - /// - public static bool ConvertOnBank { get; private set; } - - /// - /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency - /// when they are added to a secure trade container. - /// - public static bool ConvertOnTrade { get; private set; } - - public static void Configure() - { - Enabled = ServerConfiguration.GetSetting("accountGold.enable", Core.TOL); - ConvertOnBank = ServerConfiguration.GetSetting("accountGold.convertOnBank", true); - ConvertOnTrade = ServerConfiguration.GetSetting("accountGold.convertOnTrade", false); - } - } - - public interface IGoldAccount - { - /// - /// This amount represents the current amount of Gold owned by the player. - /// The value does not include the value of Platinum and ranges from - /// 0 to 999,999,999 by default. - /// - [CommandProperty(AccessLevel.Administrator)] - int TotalGold { get; } - - /// - /// This amount represents the current amount of Platinum owned by the player. - /// The value does not include the value of Gold and ranges from - /// 0 to 2,147,483,647 by default. - /// One Platinum represents the value of CurrencyThreshold in Gold. - /// - [CommandProperty(AccessLevel.Administrator)] - int TotalPlat { get; } - - /// - /// Attempts to deposit the given amount of Gold into this account. - /// If the given amount is greater than the CurrencyThreshold, - /// Platinum will be deposited to offset the difference. - /// - /// Amount to deposit. - /// True if successful, false if amount given is less than or equal to zero. - bool DepositGold(int amount); - - /// - /// Attempts to deposit the given amount of Platinum into this account. - /// - /// Amount to deposit. - /// True if successful, false if amount given is less than or equal to zero. - bool DepositPlat(int amount); - - /// - /// Attempts to withdraw the given amount of Gold from this account. - /// If the given amount is greater than the CurrencyThreshold, - /// Platinum will be withdrawn to offset the difference. - /// - /// Amount to withdraw. - /// True if successful, false if balance was too low. - bool WithdrawGold(int amount); - - /// - /// Attempts to withdraw the given amount of Platinum from this account. - /// - /// Amount to withdraw. - /// True if successful, false if balance was too low. - bool WithdrawPlat(int amount); - - /// - /// Returns total gold inclusive of platinum, capped to Int32. - /// This is strictly for backwards compatibility - /// - /// Total gold, capped at Int32.MaxValue - long GetTotalGold(); - } - - public interface IAccount : IGoldAccount, IComparable - { - string Username { get; set; } - string Email { get; set; } - AccessLevel AccessLevel { get; set; } - - int Length { get; } - int Limit { get; } - int Count { get; } - Mobile this[int index] { get; set; } - - void Delete(); - void SetPassword(string password); - bool CheckPassword(string password); + Enabled = ServerConfiguration.GetSetting("accountGold.enable", Core.TOL); + ConvertOnBank = ServerConfiguration.GetSetting("accountGold.convertOnBank", true); + ConvertOnTrade = ServerConfiguration.GetSetting("accountGold.convertOnTrade", false); } } + +public interface IGoldAccount +{ + /// + /// This amount represents the current amount of Gold owned by the player. + /// The value does not include the value of Platinum and ranges from + /// 0 to 999,999,999 by default. + /// + [CommandProperty(AccessLevel.Administrator)] + int TotalGold { get; } + + /// + /// This amount represents the current amount of Platinum owned by the player. + /// The value does not include the value of Gold and ranges from + /// 0 to 2,147,483,647 by default. + /// One Platinum represents the value of CurrencyThreshold in Gold. + /// + [CommandProperty(AccessLevel.Administrator)] + int TotalPlat { get; } + + /// + /// Attempts to deposit the given amount of Gold into this account. + /// If the given amount is greater than the CurrencyThreshold, + /// Platinum will be deposited to offset the difference. + /// + /// Amount to deposit. + /// True if successful, false if amount given is less than or equal to zero. + bool DepositGold(int amount); + + /// + /// Attempts to deposit the given amount of Platinum into this account. + /// + /// Amount to deposit. + /// True if successful, false if amount given is less than or equal to zero. + bool DepositPlat(int amount); + + /// + /// Attempts to withdraw the given amount of Gold from this account. + /// If the given amount is greater than the CurrencyThreshold, + /// Platinum will be withdrawn to offset the difference. + /// + /// Amount to withdraw. + /// True if successful, false if balance was too low. + bool WithdrawGold(int amount); + + /// + /// Attempts to withdraw the given amount of Platinum from this account. + /// + /// Amount to withdraw. + /// True if successful, false if balance was too low. + bool WithdrawPlat(int amount); + + /// + /// Returns total gold inclusive of platinum, capped to Int32. + /// This is strictly for backwards compatibility + /// + /// Total gold, capped at Int32.MaxValue + long GetTotalGold(); +} + +public interface IAccount : IGoldAccount, IComparable +{ + string Username { get; set; } + string Email { get; set; } + AccessLevel AccessLevel { get; set; } + + int Length { get; } + int Limit { get; } + int Count { get; } + Mobile this[int index] { get; set; } + + void Delete(); + void SetPassword(string password); + bool CheckPassword(string password); +} diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs index f5b36b0d9..0d1614332 100644 --- a/Projects/Server/IEntity.cs +++ b/Projects/Server/IEntity.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IEntity.cs * * * @@ -15,110 +15,109 @@ using System; -namespace Server +namespace Server; + +public interface IEntity : IPoint3D, ISerializable { - public interface IEntity : IPoint3D, ISerializable + Point3D Location { get; } + Map Map { get; } + void MoveToWorld(Point3D location, Map map); + + void ProcessDelta(); + + bool InRange(Point2D p, int range); + + bool InRange(Point3D p, int range); + + void RemoveItem(Item item); +} + +public class Entity : IEntity +{ + public Entity(Serial serial, Point3D loc, Map map) : this(serial) { - Point3D Location { get; } - Map Map { get; } - void MoveToWorld(Point3D location, Map map); - - void ProcessDelta(); - - bool InRange(Point2D p, int range); - - bool InRange(Point3D p, int range); - - void RemoveItem(Item item); + Location = loc; + Map = map; + Deleted = false; } - public class Entity : IEntity + public Entity(Serial serial) => Serial = serial; + + public void SetTypeRef(Type type) { - public Entity(Serial serial, Point3D loc, Map map) : this(serial) - { - Location = loc; - Map = map; - Deleted = false; - } + } - public Entity(Serial serial) => Serial = serial; + DateTime ISerializable.Created { get; set; } = Core.Now; - public void SetTypeRef(Type type) - { - } + DateTime ISerializable.LastSerialized { get; set; } = DateTime.MaxValue; - DateTime ISerializable.Created { get; set; } = Core.Now; + long ISerializable.SavePosition { get; set; } = -1; - DateTime ISerializable.LastSerialized { get; set; } = DateTime.MaxValue; + BufferWriter ISerializable.SaveBuffer { get; set; } - long ISerializable.SavePosition { get; set; } = -1; + public int TypeRef => -1; - BufferWriter ISerializable.SaveBuffer { get; set; } + public Serial Serial { get; } - public int TypeRef => -1; + public Point3D Location { get; private set; } - public Serial Serial { get; } + public int X => Location.X; - public Point3D Location { get; private set; } + public int Y => Location.Y; - public int X => Location.X; + public int Z => Location.Z; - public int Y => Location.Y; + public Map Map { get; private set; } - public int Z => Location.Z; + public void MoveToWorld(Point3D newLocation, Map map) + { + Location = newLocation; + Map = map; + } - public Map Map { get; private set; } + public bool Deleted { get; } - public void MoveToWorld(Point3D newLocation, Map map) - { - Location = newLocation; - Map = map; - } + public void Delete() + { + } - public bool Deleted { get; } + public void ProcessDelta() + { + } - public void Delete() - { - } + public void RemoveItem(Item item) + { + } - public void ProcessDelta() - { - } + public bool InRange(Point2D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; - public void RemoveItem(Item item) - { - } + public bool InRange(Point3D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; - public bool InRange(Point2D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; + public bool InRange(IPoint2D p, int range) => + p.X >= Location.m_X - range + && p.X <= Location.m_X + range + && p.Y >= Location.m_Y - range + && p.Y <= Location.m_Y + range; - public bool InRange(Point3D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; + public void BeforeSerialize() + { + } - public bool InRange(IPoint2D p, int range) => - p.X >= Location.m_X - range - && p.X <= Location.m_X + range - && p.Y >= Location.m_Y - range - && p.Y <= Location.m_Y + range; + public void Deserialize(IGenericReader reader) + { + // Should not actually be saved + Timer.StartTimer(Delete); + } - public void BeforeSerialize() - { - } - - public void Deserialize(IGenericReader reader) - { - // Should not actually be saved - Timer.StartTimer(Delete); - } - - public void Serialize(IGenericWriter writer) - { - } + public void Serialize(IGenericWriter writer) + { } } diff --git a/Projects/Server/Interfaces.cs b/Projects/Server/Interfaces.cs index 3e46e8bb2..9bd96f661 100644 --- a/Projects/Server/Interfaces.cs +++ b/Projects/Server/Interfaces.cs @@ -1,75 +1,74 @@ using System; -namespace Server +namespace Server; + +public interface IPoint2D { - public interface IPoint2D - { - int X { get; } - int Y { get; } - } - - public interface IPoint3D : IPoint2D - { - int Z { get; } - } - - public interface ICarvable - { - void Carve(Mobile from, Item item); - } - - public interface IWeapon - { - int MaxRange { get; } - void OnBeforeSwing(Mobile attacker, Mobile defender); - TimeSpan OnSwing(Mobile attacker, Mobile defender, double damageBonus = 1.0); - void GetStatusDamage(Mobile from, out int min, out int max); - } - - public interface IHued - { - int HuedItemID { get; } - } - - public interface ISpell - { - bool IsCasting { get; } - void OnCasterHurt(); - void OnCasterKilled(); - void OnConnectionChanged(); - bool OnCasterMoving(Direction d); - bool OnCasterEquipping(Item item); - bool OnCasterUsingObject(IEntity entity); - bool OnCastInTown(Region r); - void FinishSequence(); - } - - public interface IParty - { - void OnStamChanged(Mobile m); - void OnManaChanged(Mobile m); - void OnStatsQuery(Mobile beholder, Mobile beheld); - } - - // TODO: Add SpawnMap and change Spawner.Map to use it - public interface ISpawner : IEntity - { - Guid Guid { get; } - bool UnlinkOnTaming { get; } - Point3D HomeLocation { get; } - int HomeRange { get; } - Region Region { get; } - bool ReturnOnDeactivate { get; } - - void Remove(ISpawnable spawn); - Point3D GetSpawnPosition(ISpawnable spawned, Map map); - void Respawn(); - } - - public interface ISpawnable : IEntity - { - ISpawner Spawner { get; set; } - void OnBeforeSpawn(Point3D location, Map map); - void OnAfterSpawn(); - } + int X { get; } + int Y { get; } +} + +public interface IPoint3D : IPoint2D +{ + int Z { get; } +} + +public interface ICarvable +{ + void Carve(Mobile from, Item item); +} + +public interface IWeapon +{ + int MaxRange { get; } + void OnBeforeSwing(Mobile attacker, Mobile defender); + TimeSpan OnSwing(Mobile attacker, Mobile defender, double damageBonus = 1.0); + void GetStatusDamage(Mobile from, out int min, out int max); +} + +public interface IHued +{ + int HuedItemID { get; } +} + +public interface ISpell +{ + bool IsCasting { get; } + void OnCasterHurt(); + void OnCasterKilled(); + void OnConnectionChanged(); + bool OnCasterMoving(Direction d); + bool OnCasterEquipping(Item item); + bool OnCasterUsingObject(IEntity entity); + bool OnCastInTown(Region r); + void FinishSequence(); +} + +public interface IParty +{ + void OnStamChanged(Mobile m); + void OnManaChanged(Mobile m); + void OnStatsQuery(Mobile beholder, Mobile beheld); +} + +// TODO: Add SpawnMap and change Spawner.Map to use it +public interface ISpawner : IEntity +{ + Guid Guid { get; } + bool UnlinkOnTaming { get; } + Point3D HomeLocation { get; } + int HomeRange { get; } + Region Region { get; } + bool ReturnOnDeactivate { get; } + + void Remove(ISpawnable spawn); + Point3D GetSpawnPosition(ISpawnable spawned, Map map); + void Respawn(); +} + +public interface ISpawnable : IEntity +{ + ISpawner Spawner { get; set; } + void OnBeforeSpawn(Point3D location, Map map); + void OnAfterSpawn(); } diff --git a/Projects/Server/Items/BaseMulti.cs b/Projects/Server/Items/BaseMulti.cs index 5deb97814..a6d8de065 100644 --- a/Projects/Server/Items/BaseMulti.cs +++ b/Projects/Server/Items/BaseMulti.cs @@ -1,125 +1,124 @@ using System; using System.Runtime.CompilerServices; -namespace Server.Items +namespace Server.Items; + +public abstract class BaseMulti : Item { - public abstract class BaseMulti : Item + public BaseMulti(int itemID) : base(itemID) => Movable = false; + + public BaseMulti(Serial serial) : base(serial) { - public BaseMulti(int itemID) : base(itemID) => Movable = false; + } - public BaseMulti(Serial serial) : base(serial) + [CommandProperty(AccessLevel.GameMaster)] + public override int ItemID + { + get => base.ItemID; + set { - } - - [CommandProperty(AccessLevel.GameMaster)] - public override int ItemID - { - get => base.ItemID; - set + if (base.ItemID != value) { - if (base.ItemID != value) - { - var facet = Parent == null ? Map : null; + var facet = Parent == null ? Map : null; - facet?.OnLeave(this); + facet?.OnLeave(this); - base.ItemID = value; + base.ItemID = value; - facet?.OnEnter(this); - } + facet?.OnEnter(this); } } + } - public override int LabelNumber - { - get - { - var mcl = Components; - - if (mcl.List.Length > 0) - { - int id = mcl.List[0].ItemId; - - if (id < 0x4000) - { - return 1020000 + id; - } - - return 1078872 + id; - } - - return base.LabelNumber; - } - } - - public virtual bool AllowsRelativeDrop => false; - - public virtual MultiComponentList Components => MultiData.GetComponents(ItemID); - - [Obsolete("Replace with calls to OnLeave and OnEnter surrounding component invalidation.", true)] - public virtual void RefreshComponents() - { - if (Parent == null) - { - var facet = Map; - - if (facet != null) - { - facet.OnLeave(this); - facet.OnEnter(this); - } - } - } - - public override int GetMaxUpdateRange() => 22; - - public override int GetUpdateRange(Mobile m) => 22; - - public virtual bool Contains(Point2D p) => Contains(p.m_X, p.m_Y); - - public virtual bool Contains(Point3D p) => Contains(p.m_X, p.m_Y); - - public virtual bool Contains(IPoint3D p) => Contains(p.X, p.Y); - - public virtual bool Contains(int x, int y) + public override int LabelNumber + { + get { var mcl = Components; - x -= X + mcl.Min.m_X; - y -= Y + mcl.Min.m_Y; - - return x >= 0 - && x < mcl.Width - && y >= 0 - && y < mcl.Height - && mcl.Tiles[x][y].Length > 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Mobile m) => m.Map == Map && Contains(m.X, m.Y); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(Item item) => item.Map == Map && Contains(item.X, item.Y); - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(1); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - if (version == 0) + if (mcl.List.Length > 0) { - if (ItemID >= 0x4000) + int id = mcl.List[0].ItemId; + + if (id < 0x4000) { - ItemID -= 0x4000; + return 1020000 + id; } + + return 1078872 + id; + } + + return base.LabelNumber; + } + } + + public virtual bool AllowsRelativeDrop => false; + + public virtual MultiComponentList Components => MultiData.GetComponents(ItemID); + + [Obsolete("Replace with calls to OnLeave and OnEnter surrounding component invalidation.", true)] + public virtual void RefreshComponents() + { + if (Parent == null) + { + var facet = Map; + + if (facet != null) + { + facet.OnLeave(this); + facet.OnEnter(this); + } + } + } + + public override int GetMaxUpdateRange() => 22; + + public override int GetUpdateRange(Mobile m) => 22; + + public virtual bool Contains(Point2D p) => Contains(p.m_X, p.m_Y); + + public virtual bool Contains(Point3D p) => Contains(p.m_X, p.m_Y); + + public virtual bool Contains(IPoint3D p) => Contains(p.X, p.Y); + + public virtual bool Contains(int x, int y) + { + var mcl = Components; + + x -= X + mcl.Min.m_X; + y -= Y + mcl.Min.m_Y; + + return x >= 0 + && x < mcl.Width + && y >= 0 + && y < mcl.Height + && mcl.Tiles[x][y].Length > 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Mobile m) => m.Map == Map && Contains(m.X, m.Y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Item item) => item.Map == Map && Contains(item.X, item.Y); + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(1); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + if (version == 0) + { + if (ItemID >= 0x4000) + { + ItemID -= 0x4000; } } } diff --git a/Projects/Server/Items/Container.cs b/Projects/Server/Items/Container.cs index 1d06752cc..e1eeb8cab 100644 --- a/Projects/Server/Items/Container.cs +++ b/Projects/Server/Items/Container.cs @@ -5,809 +5,921 @@ using Server.Collections; using Server.Logging; using Server.Network; -namespace Server.Items +namespace Server.Items; + +public delegate void OnItemConsumed(Item item, int amount); + +public delegate int CheckItemGroup(Item a, Item b); + +public delegate void ContainerSnoopHandler(Container cont, Mobile from); + +public class Container : Item { - public delegate void OnItemConsumed(Item item, int amount); + private static readonly List m_FindItemsList = new(); - public delegate int CheckItemGroup(Item a, Item b); + private ContainerData m_ContainerData; - public delegate void ContainerSnoopHandler(Container cont, Mobile from); + private int m_DropSound; + private int m_GumpID; - public class Container : Item + internal List m_Items; + private int m_MaxItems; + private int m_TotalGold; + + private int m_TotalItems; + private int m_TotalWeight; + + public Container(int itemID) : base(itemID) { - private static readonly List m_FindItemsList = new(); + m_GumpID = -1; + m_DropSound = -1; + m_MaxItems = -1; - private ContainerData m_ContainerData; + UpdateContainerData(); + } - private int m_DropSound; - private int m_GumpID; + public Container(Serial serial) : base(serial) + { + } - internal List m_Items; - private int m_MaxItems; - private int m_TotalGold; + public static ContainerSnoopHandler SnoopHandler { get; set; } - private int m_TotalItems; - private int m_TotalWeight; - - public Container(int itemID) : base(itemID) + public ContainerData ContainerData + { + get { - m_GumpID = -1; - m_DropSound = -1; - m_MaxItems = -1; - - UpdateContainerData(); - } - - public Container(Serial serial) : base(serial) - { - } - - public static ContainerSnoopHandler SnoopHandler { get; set; } - - public ContainerData ContainerData - { - get + if (m_ContainerData == null) { - if (m_ContainerData == null) + UpdateContainerData(); + } + + return m_ContainerData; + } + set => m_ContainerData = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public override int ItemID + { + get => base.ItemID; + set + { + var oldID = ItemID; + + base.ItemID = value; + + if (ItemID != oldID) + { + UpdateContainerData(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int GumpID + { + get => m_GumpID == -1 ? DefaultGumpID : m_GumpID; + set => m_GumpID = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int DropSound + { + get => m_DropSound == -1 ? DefaultDropSound : m_DropSound; + set => m_DropSound = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int MaxItems + { + get => m_MaxItems == -1 ? DefaultMaxItems : m_MaxItems; + set + { + m_MaxItems = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int MaxWeight + { + get + { + if (Parent is Container container && container.MaxWeight == 0) + { + return 0; + } + + return DefaultMaxWeight; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool LiftOverride { get; set; } + + public virtual Rectangle2D Bounds => ContainerData.Bounds; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int DefaultGumpID => ContainerData.GumpID; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int DefaultDropSound => ContainerData.DropSound; + + public virtual int DefaultMaxItems => GlobalMaxItems; + public virtual int DefaultMaxWeight => GlobalMaxWeight; + + public virtual bool IsDecoContainer => !Movable && !IsLockedDown && !IsSecure && Parent == null && !LiftOverride; + + public static int GlobalMaxItems { get; set; } = 125; + + public static int GlobalMaxWeight { get; set; } = 400; + + public virtual bool DisplaysContent => true; + + public List Openers { get; set; } + + public virtual bool IsPublicContainer => false; + + public virtual void UpdateContainerData() + { + ContainerData = ContainerData.GetData(ItemID); + } + + public virtual int GetDroppedSound(Item item) + { + var dropSound = item.GetDropSound(); + + return dropSound != -1 ? dropSound : DropSound; + } + + public override void OnSnoop(Mobile from) + { + SnoopHandler?.Invoke(this, from); + } + + public override bool CheckLift(Mobile from, Item item, ref LRReason reject) + { + if (from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) + { + reject = LRReason.CannotLift; + return false; + } + + return base.CheckLift(from, item, ref reject); + } + + public override bool CheckItemUse(Mobile from, Item item) + { + if (item != this && from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) + { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that. + return false; + } + + return base.CheckItemUse(from, item); + } + + public bool CheckHold(Mobile m, Item item, bool message) => CheckHold(m, item, message, true, 0, 0); + + public bool CheckHold(Mobile m, Item item, bool message, bool checkItems) => + CheckHold(m, item, message, checkItems, 0, 0); + + public virtual bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) + { + if (m == null || m.AccessLevel < AccessLevel.GameMaster) + { + if (IsDecoContainer) + { + if (message) { - UpdateContainerData(); + SendCantStoreMessage(m, item); } - return m_ContainerData; - } - set => m_ContainerData = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public override int ItemID - { - get => base.ItemID; - set - { - var oldID = ItemID; - - base.ItemID = value; - - if (ItemID != oldID) - { - UpdateContainerData(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int GumpID - { - get => m_GumpID == -1 ? DefaultGumpID : m_GumpID; - set => m_GumpID = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int DropSound - { - get => m_DropSound == -1 ? DefaultDropSound : m_DropSound; - set => m_DropSound = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int MaxItems - { - get => m_MaxItems == -1 ? DefaultMaxItems : m_MaxItems; - set - { - m_MaxItems = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int MaxWeight - { - get - { - if (Parent is Container container && container.MaxWeight == 0) - { - return 0; - } - - return DefaultMaxWeight; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool LiftOverride { get; set; } - - public virtual Rectangle2D Bounds => ContainerData.Bounds; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int DefaultGumpID => ContainerData.GumpID; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int DefaultDropSound => ContainerData.DropSound; - - public virtual int DefaultMaxItems => GlobalMaxItems; - public virtual int DefaultMaxWeight => GlobalMaxWeight; - - public virtual bool IsDecoContainer => !Movable && !IsLockedDown && !IsSecure && Parent == null && !LiftOverride; - - public static int GlobalMaxItems { get; set; } = 125; - - public static int GlobalMaxWeight { get; set; } = 400; - - public virtual bool DisplaysContent => true; - - public List Openers { get; set; } - - public virtual bool IsPublicContainer => false; - - public virtual void UpdateContainerData() - { - ContainerData = ContainerData.GetData(ItemID); - } - - public virtual int GetDroppedSound(Item item) - { - var dropSound = item.GetDropSound(); - - return dropSound != -1 ? dropSound : DropSound; - } - - public override void OnSnoop(Mobile from) - { - SnoopHandler?.Invoke(this, from); - } - - public override bool CheckLift(Mobile from, Item item, ref LRReason reject) - { - if (from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) - { - reject = LRReason.CannotLift; return false; } - return base.CheckLift(from, item, ref reject); - } + var maxItems = MaxItems; - public override bool CheckItemUse(Mobile from, Item item) - { - if (item != this && from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) + if (checkItems && maxItems != 0 && + TotalItems + plusItems + item.TotalItems + (item.IsVirtualItem ? 0 : 1) > maxItems) { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that. + if (message) + { + SendFullItemsMessage(m, item); + } + return false; } - return base.CheckItemUse(from, item); + if (MaxWeight != 0 && TotalWeight + plusWeight + item.TotalWeight + item.PileWeight > MaxWeight) + { + if (message) + { + SendFullWeightMessage(m, item); + } + + return false; + } } - public bool CheckHold(Mobile m, Item item, bool message) => CheckHold(m, item, message, true, 0, 0); + var parent = Parent; - public bool CheckHold(Mobile m, Item item, bool message, bool checkItems) => - CheckHold(m, item, message, checkItems, 0, 0); - - public virtual bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) + while (parent != null) { - if (m == null || m.AccessLevel < AccessLevel.GameMaster) + if (parent is Container container) { - if (IsDecoContainer) + return container.CheckHold(m, item, message, checkItems, plusItems, plusWeight); + } + + if (parent is not Item parentItem) + { + break; + } + + parent = parentItem.Parent; + } + + return true; + } + + public virtual void SendFullItemsMessage(Mobile to, Item item) + { + to.SendMessage("That container cannot hold more items."); + } + + public virtual void SendFullWeightMessage(Mobile to, Item item) + { + to.SendMessage("That container cannot hold more weight."); + } + + public virtual void SendCantStoreMessage(Mobile to, Item item) + { + to.SendLocalizedMessage(500176); // That is not your container, you can't store things here. + } + + public virtual bool OnDragDropInto(Mobile from, Item item, Point3D p) + { + if (!CheckHold(from, item, true, true)) + { + return false; + } + + item.Location = new Point3D(p.m_X, p.m_Y, 0); + AddItem(item); + + from.SendSound(GetDroppedSound(item), GetWorldLocation()); + + return true; + } + + private static bool InTypeList(Item item, Type[] types) + { + var t = item.GetType(); + + for (var i = 0; i < types.Length; ++i) + { + if (types[i].IsAssignableFrom(t)) + { + return true; + } + } + + return false; + } + + private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) + { + if (setIf) + { + flags |= toSet; + } + } + + private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(2); // version + + var flags = SaveFlag.None; + + SetSaveFlag(ref flags, SaveFlag.MaxItems, m_MaxItems != -1); + SetSaveFlag(ref flags, SaveFlag.GumpID, m_GumpID != -1); + SetSaveFlag(ref flags, SaveFlag.DropSound, m_DropSound != -1); + SetSaveFlag(ref flags, SaveFlag.LiftOverride, LiftOverride); + + writer.Write((byte)flags); + + if (GetSaveFlag(flags, SaveFlag.MaxItems)) + { + writer.WriteEncodedInt(m_MaxItems); + } + + if (GetSaveFlag(flags, SaveFlag.GumpID)) + { + writer.WriteEncodedInt(m_GumpID); + } + + if (GetSaveFlag(flags, SaveFlag.DropSound)) + { + writer.WriteEncodedInt(m_DropSound); + } + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + switch (version) + { + case 2: { - if (message) + var flags = (SaveFlag)reader.ReadByte(); + + if (GetSaveFlag(flags, SaveFlag.MaxItems)) { - SendCantStoreMessage(m, item); + m_MaxItems = reader.ReadEncodedInt(); + } + else + { + m_MaxItems = -1; } - return false; + if (GetSaveFlag(flags, SaveFlag.GumpID)) + { + m_GumpID = reader.ReadEncodedInt(); + } + else + { + m_GumpID = -1; + } + + if (GetSaveFlag(flags, SaveFlag.DropSound)) + { + m_DropSound = reader.ReadEncodedInt(); + } + else + { + m_DropSound = -1; + } + + LiftOverride = GetSaveFlag(flags, SaveFlag.LiftOverride); + + break; } - - var maxItems = MaxItems; - - if (checkItems && maxItems != 0 && - TotalItems + plusItems + item.TotalItems + (item.IsVirtualItem ? 0 : 1) > maxItems) + case 1: { - if (message) - { - SendFullItemsMessage(m, item); - } - - return false; + m_MaxItems = reader.ReadInt(); + goto case 0; } - - if (MaxWeight != 0 && TotalWeight + plusWeight + item.TotalWeight + item.PileWeight > MaxWeight) + case 0: { - if (message) + if (version < 1) { - SendFullWeightMessage(m, item); + m_MaxItems = GlobalMaxItems; } - return false; + m_GumpID = reader.ReadInt(); + m_DropSound = reader.ReadInt(); + + if (m_GumpID == DefaultGumpID) + { + m_GumpID = -1; + } + + if (m_DropSound == DefaultDropSound) + { + m_DropSound = -1; + } + + if (m_MaxItems == DefaultMaxItems) + { + m_MaxItems = -1; + } + + // m_Bounds = new Rectangle2D( reader.ReadPoint2D(), reader.ReadPoint2D() ); + reader.ReadPoint2D(); + reader.ReadPoint2D(); + + break; + } + } + + UpdateContainerData(); + } + + public override int GetTotal(TotalType type) + { + return type switch + { + TotalType.Gold => m_TotalGold, + TotalType.Items => m_TotalItems, + TotalType.Weight => m_TotalWeight, + _ => base.GetTotal(type) + }; + } + + public override void UpdateTotal(Item sender, TotalType type, int delta) + { + if (sender != this && delta != 0 && !sender.IsVirtualItem) + { + switch (type) + { + case TotalType.Gold: + m_TotalGold += delta; + break; + + case TotalType.Items: + m_TotalItems += delta; + InvalidateProperties(); + break; + + case TotalType.Weight: + m_TotalWeight += delta; + InvalidateProperties(); + break; + } + } + + base.UpdateTotal(sender, type, delta); + } + + public override void UpdateTotals() + { + m_TotalGold = 0; + m_TotalItems = 0; + m_TotalWeight = 0; + + var items = m_Items; + + if (items == null) + { + return; + } + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + + item.UpdateTotals(); + + if (item.IsVirtualItem) + { + continue; + } + + m_TotalGold += item.TotalGold; + m_TotalItems += item.TotalItems + 1; + m_TotalWeight += item.TotalWeight + item.PileWeight; + } + } + + public virtual bool OnStackAttempt(Mobile from, Item stack, Item dropped) => + CheckHold(from, dropped, true, false) && stack.StackWith(from, dropped); + + public override bool OnDragDrop(Mobile from, Item dropped) + { + if (TryDropItem(from, dropped, true)) + { + from.SendSound(GetDroppedSound(dropped), GetWorldLocation()); + + return true; + } + + return false; + } + + public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage) => + TryDropItem(from, dropped, sendFullMessage, false); + + public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage, bool playSound) + { + var list = Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (item is not Container && CheckHold(from, dropped, false, false) && + item.StackWith(from, dropped, playSound)) + { + return true; + } + } + + if (CheckHold(from, dropped, sendFullMessage, true)) + { + DropItem(dropped); + return true; + } + + return false; + } + + public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params Item[] droppedItems) + { + var dropItems = new List(); + var stackItems = new List(); + + var extraItems = 0; + var extraWeight = 0; + + for (var i = 0; i < droppedItems.Length; i++) + { + var dropped = droppedItems[i]; + + var list = Items; + + var stacked = false; + + for (var j = 0; j < list.Count; ++j) + { + var item = list[j]; + + if (item is not Container && CheckHold(from, dropped, false, false, 0, extraWeight) && + item.CanStackWith(dropped)) + { + stackItems.Add(new ItemStackEntry(item, dropped)); + extraWeight += (int)Math.Ceiling(item.Weight * (item.Amount + dropped.Amount)) - + item.PileWeight; // extra weight delta, do not need TotalWeight as we do not have hybrid stackable container types + stacked = true; + break; } } - var parent = Parent; - - while (parent != null) + if (!stacked && CheckHold(from, dropped, false, true, extraItems, extraWeight)) { - if (parent is Container container) + dropItems.Add(dropped); + extraItems++; + extraWeight += dropped.TotalWeight + dropped.PileWeight; + } + } + + if (dropItems.Count + stackItems.Count == droppedItems.Length) // All good + { + for (var i = 0; i < dropItems.Count; i++) + { + DropItem(dropItems[i]); + } + + for (var i = 0; i < stackItems.Count; i++) + { + stackItems[i].m_StackItem.StackWith(from, stackItems[i].m_DropItem, false); + } + + return true; + } + + return false; + } + + public virtual void Destroy() + { + var loc = GetWorldLocation(); + var map = Map; + + for (var i = Items.Count - 1; i >= 0; --i) + { + if (i < Items.Count) + { + Items[i].SetLastMoved(); + Items[i].MoveToWorld(loc, map); + } + } + + Delete(); + } + + public virtual void DropItem(Item dropped) + { + if (dropped == null) + { + return; + } + + AddItem(dropped); + + var bounds = dropped.GetGraphicBounds(); + var ourBounds = Bounds; + + int x, y; + + if (bounds.Width >= ourBounds.Width) + { + x = (ourBounds.Width - bounds.Width) / 2; + } + else + { + x = Utility.Random(ourBounds.Width - bounds.Width); + } + + if (bounds.Height >= ourBounds.Height) + { + y = (ourBounds.Height - bounds.Height) / 2; + } + else + { + y = Utility.Random(ourBounds.Height - bounds.Height); + } + + x += ourBounds.X; + x -= bounds.X; + + y += ourBounds.Y; + y -= bounds.Y; + + dropped.Location = new Point3D(x, y, 0); + } + + public override void OnDoubleClickSecureTrade(Mobile from) + { + if (from.InRange(GetWorldLocation(), 2)) + { + DisplayTo(from); + + var trade = GetSecureTradeCont()?.Trade; + + if (trade != null) + { + if (trade.From.Mobile == from) { - return container.CheckHold(m, item, message, checkItems, plusItems, plusWeight); + DisplayTo(trade.To.Mobile); + } + else if (trade.To.Mobile == from) + { + DisplayTo(trade.From.Mobile); + } + } + } + else + { + from.SendLocalizedMessage(500446); // That is too far away. + } + } + + public virtual bool CheckContentDisplay(Mobile from) => + DisplaysContent && RootParent == null || + RootParent is Item || RootParent == from || + from.AccessLevel > AccessLevel.Player; + + public override void OnSingleClick(Mobile from) + { + base.OnSingleClick(from); + + if (CheckContentDisplay(from)) + { + LabelTo(from, "({0} item{2}, {1} stones)", TotalItems, TotalWeight, TotalItems != 1 ? "s" : string.Empty); + } + + // LabelTo( from, 1050044, String.Format( "{0}\t{1}", TotalItems.ToString(), TotalWeight.ToString() ) ); + } + + public override void OnDelete() + { + base.OnDelete(); + + Openers = null; + } + + public virtual void DisplayTo(Mobile to) + { + ProcessOpeners(to); + + var ns = to.NetState; + + if (ns != null) + { + ns.SendDisplayContainer(Serial, GumpID); + + SendContentTo(ns); + + if (ObjectPropertyList.Enabled) + { + for (var i = 0; i < Items.Count; ++i) + { + ns.SendOPLInfo(Items[i]); + } + } + } + } + + public void ProcessOpeners(Mobile opener) + { + if (IsPublicContainer) + { + return; + } + + var contains = false; + + if (Openers != null) + { + var worldLoc = GetWorldLocation(); + var map = Map; + + for (var i = 0; i < Openers.Count; ++i) + { + var mob = Openers[i]; + + if (mob == opener) + { + contains = true; + } + else + { + var range = GetUpdateRange(mob); + + if (mob.Map != map || !mob.InRange(worldLoc, range)) + { + Openers.RemoveAt(i--); + } + } + } + } + + if (!contains) + { + Openers ??= new List(); + + Openers.Add(opener); + } + else if (Openers?.Count == 0) + { + Openers = null; + } + } + + public virtual void SendContentTo(NetState state) => state.SendContainerContent(state.Mobile, this); + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + if (DisplaysContent) // CheckContentDisplay( from )) + { + if (Core.ML) + { + if (ParentsContain()) // Root Parent is the Mobile. Parent could be another container. + { + list.Add( + 1073841, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones + $"{TotalItems}\t{MaxItems}\t{TotalWeight}" + ); + } + else + { + list.Add( + 1072241, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~/~4_MAXWEIGHT~ stones + $"{TotalItems}\t{MaxItems}\t{TotalWeight}\t{MaxWeight}" + ); } - if (parent is not Item parentItem) + // TODO: Where do the other clilocs come into play? 1073839 & 1073840? + } + else + { + // ~1_COUNT~ items, ~2_WEIGHT~ stones + list.Add(1050044, $"{TotalItems}\t{TotalWeight}"); + } + } + } + + public override void OnDoubleClick(Mobile from) + { + if (from.AccessLevel > AccessLevel.Player || from.InRange(GetWorldLocation(), 2)) + { + DisplayTo(from); + } + else + { + from.SendLocalizedMessage(500446); // That is too far away. + } + } + + public bool ConsumeTotalGrouped(Type type, int amount, bool recurse, OnItemConsumed callback, CheckItemGroup grouper) + { + if (grouper == null) + { + throw new ArgumentNullException(nameof(grouper)); + } + + var typedItems = FindItemsByType(type, recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + { + group.Add(b); + } + else { break; } - parent = parentItem.Parent; + ++idx; } - return true; + groups.Add(group); } - public virtual void SendFullItemsMessage(Mobile to, Item item) - { - to.SendMessage("That container cannot hold more items."); - } + var items = new Item[groups.Count][]; + var totals = new int[groups.Count]; - public virtual void SendFullWeightMessage(Mobile to, Item item) - { - to.SendMessage("That container cannot hold more weight."); - } + var hasEnough = false; - public virtual void SendCantStoreMessage(Mobile to, Item item) + for (var i = 0; i < groups.Count; ++i) { - to.SendLocalizedMessage(500176); // That is not your container, you can't store things here. - } + items[i] = groups[i].ToArray(); - public virtual bool OnDragDropInto(Mobile from, Item item, Point3D p) - { - if (!CheckHold(from, item, true, true)) + for (var j = 0; j < items[i].Length; ++j) { - return false; + totals[i] += items[i][j].Amount; } - item.Location = new Point3D(p.m_X, p.m_Y, 0); - AddItem(item); - - from.SendSound(GetDroppedSound(item), GetWorldLocation()); - - return true; + if (totals[i] >= amount) + { + hasEnough = true; + } } - private static bool InTypeList(Item item, Type[] types) + if (!hasEnough) { - var t = item.GetType(); - - for (var i = 0; i < types.Length; ++i) - { - if (types[i].IsAssignableFrom(t)) - { - return true; - } - } - return false; } - private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) + for (var i = 0; i < items.Length; ++i) { - if (setIf) + if (totals[i] >= amount) { - flags |= toSet; - } - } + var need = amount; - private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; + for (var j = 0; j < items[i].Length; ++j) + { + var item = items[i][j]; - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); + var theirAmount = item.Amount; - writer.Write(2); // version - - var flags = SaveFlag.None; - - SetSaveFlag(ref flags, SaveFlag.MaxItems, m_MaxItems != -1); - SetSaveFlag(ref flags, SaveFlag.GumpID, m_GumpID != -1); - SetSaveFlag(ref flags, SaveFlag.DropSound, m_DropSound != -1); - SetSaveFlag(ref flags, SaveFlag.LiftOverride, LiftOverride); - - writer.Write((byte)flags); - - if (GetSaveFlag(flags, SaveFlag.MaxItems)) - { - writer.WriteEncodedInt(m_MaxItems); - } - - if (GetSaveFlag(flags, SaveFlag.GumpID)) - { - writer.WriteEncodedInt(m_GumpID); - } - - if (GetSaveFlag(flags, SaveFlag.DropSound)) - { - writer.WriteEncodedInt(m_DropSound); - } - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 2: + if (theirAmount < need) { - var flags = (SaveFlag)reader.ReadByte(); + callback?.Invoke(item, theirAmount); - if (GetSaveFlag(flags, SaveFlag.MaxItems)) - { - m_MaxItems = reader.ReadEncodedInt(); - } - else - { - m_MaxItems = -1; - } - - if (GetSaveFlag(flags, SaveFlag.GumpID)) - { - m_GumpID = reader.ReadEncodedInt(); - } - else - { - m_GumpID = -1; - } - - if (GetSaveFlag(flags, SaveFlag.DropSound)) - { - m_DropSound = reader.ReadEncodedInt(); - } - else - { - m_DropSound = -1; - } - - LiftOverride = GetSaveFlag(flags, SaveFlag.LiftOverride); - - break; - } - case 1: - { - m_MaxItems = reader.ReadInt(); - goto case 0; - } - case 0: - { - if (version < 1) - { - m_MaxItems = GlobalMaxItems; - } - - m_GumpID = reader.ReadInt(); - m_DropSound = reader.ReadInt(); - - if (m_GumpID == DefaultGumpID) - { - m_GumpID = -1; - } - - if (m_DropSound == DefaultDropSound) - { - m_DropSound = -1; - } - - if (m_MaxItems == DefaultMaxItems) - { - m_MaxItems = -1; - } - - // m_Bounds = new Rectangle2D( reader.ReadPoint2D(), reader.ReadPoint2D() ); - reader.ReadPoint2D(); - reader.ReadPoint2D(); - - break; - } - } - - UpdateContainerData(); - } - - public override int GetTotal(TotalType type) - { - return type switch - { - TotalType.Gold => m_TotalGold, - TotalType.Items => m_TotalItems, - TotalType.Weight => m_TotalWeight, - _ => base.GetTotal(type) - }; - } - - public override void UpdateTotal(Item sender, TotalType type, int delta) - { - if (sender != this && delta != 0 && !sender.IsVirtualItem) - { - switch (type) - { - case TotalType.Gold: - m_TotalGold += delta; - break; - - case TotalType.Items: - m_TotalItems += delta; - InvalidateProperties(); - break; - - case TotalType.Weight: - m_TotalWeight += delta; - InvalidateProperties(); - break; - } - } - - base.UpdateTotal(sender, type, delta); - } - - public override void UpdateTotals() - { - m_TotalGold = 0; - m_TotalItems = 0; - m_TotalWeight = 0; - - var items = m_Items; - - if (items == null) - { - return; - } - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - item.UpdateTotals(); - - if (item.IsVirtualItem) - { - continue; - } - - m_TotalGold += item.TotalGold; - m_TotalItems += item.TotalItems + 1; - m_TotalWeight += item.TotalWeight + item.PileWeight; - } - } - - public virtual bool OnStackAttempt(Mobile from, Item stack, Item dropped) => - CheckHold(from, dropped, true, false) && stack.StackWith(from, dropped); - - public override bool OnDragDrop(Mobile from, Item dropped) - { - if (TryDropItem(from, dropped, true)) - { - from.SendSound(GetDroppedSound(dropped), GetWorldLocation()); - - return true; - } - - return false; - } - - public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage) => - TryDropItem(from, dropped, sendFullMessage, false); - - public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage, bool playSound) - { - var list = Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (item is not Container && CheckHold(from, dropped, false, false) && - item.StackWith(from, dropped, playSound)) - { - return true; - } - } - - if (CheckHold(from, dropped, sendFullMessage, true)) - { - DropItem(dropped); - return true; - } - - return false; - } - - public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params Item[] droppedItems) - { - var dropItems = new List(); - var stackItems = new List(); - - var extraItems = 0; - var extraWeight = 0; - - for (var i = 0; i < droppedItems.Length; i++) - { - var dropped = droppedItems[i]; - - var list = Items; - - var stacked = false; - - for (var j = 0; j < list.Count; ++j) - { - var item = list[j]; - - if (item is not Container && CheckHold(from, dropped, false, false, 0, extraWeight) && - item.CanStackWith(dropped)) - { - stackItems.Add(new ItemStackEntry(item, dropped)); - extraWeight += (int)Math.Ceiling(item.Weight * (item.Amount + dropped.Amount)) - - item.PileWeight; // extra weight delta, do not need TotalWeight as we do not have hybrid stackable container types - stacked = true; - break; - } - } - - if (!stacked && CheckHold(from, dropped, false, true, extraItems, extraWeight)) - { - dropItems.Add(dropped); - extraItems++; - extraWeight += dropped.TotalWeight + dropped.PileWeight; - } - } - - if (dropItems.Count + stackItems.Count == droppedItems.Length) // All good - { - for (var i = 0; i < dropItems.Count; i++) - { - DropItem(dropItems[i]); - } - - for (var i = 0; i < stackItems.Count; i++) - { - stackItems[i].m_StackItem.StackWith(from, stackItems[i].m_DropItem, false); - } - - return true; - } - - return false; - } - - public virtual void Destroy() - { - var loc = GetWorldLocation(); - var map = Map; - - for (var i = Items.Count - 1; i >= 0; --i) - { - if (i < Items.Count) - { - Items[i].SetLastMoved(); - Items[i].MoveToWorld(loc, map); - } - } - - Delete(); - } - - public virtual void DropItem(Item dropped) - { - if (dropped == null) - { - return; - } - - AddItem(dropped); - - var bounds = dropped.GetGraphicBounds(); - var ourBounds = Bounds; - - int x, y; - - if (bounds.Width >= ourBounds.Width) - { - x = (ourBounds.Width - bounds.Width) / 2; - } - else - { - x = Utility.Random(ourBounds.Width - bounds.Width); - } - - if (bounds.Height >= ourBounds.Height) - { - y = (ourBounds.Height - bounds.Height) / 2; - } - else - { - y = Utility.Random(ourBounds.Height - bounds.Height); - } - - x += ourBounds.X; - x -= bounds.X; - - y += ourBounds.Y; - y -= bounds.Y; - - dropped.Location = new Point3D(x, y, 0); - } - - public override void OnDoubleClickSecureTrade(Mobile from) - { - if (from.InRange(GetWorldLocation(), 2)) - { - DisplayTo(from); - - var trade = GetSecureTradeCont()?.Trade; - - if (trade != null) - { - if (trade.From.Mobile == from) - { - DisplayTo(trade.To.Mobile); - } - else if (trade.To.Mobile == from) - { - DisplayTo(trade.From.Mobile); - } - } - } - else - { - from.SendLocalizedMessage(500446); // That is too far away. - } - } - - public virtual bool CheckContentDisplay(Mobile from) => - DisplaysContent && RootParent == null || - RootParent is Item || RootParent == from || - from.AccessLevel > AccessLevel.Player; - - public override void OnSingleClick(Mobile from) - { - base.OnSingleClick(from); - - if (CheckContentDisplay(from)) - { - LabelTo(from, "({0} item{2}, {1} stones)", TotalItems, TotalWeight, TotalItems != 1 ? "s" : string.Empty); - } - - // LabelTo( from, 1050044, String.Format( "{0}\t{1}", TotalItems.ToString(), TotalWeight.ToString() ) ); - } - - public override void OnDelete() - { - base.OnDelete(); - - Openers = null; - } - - public virtual void DisplayTo(Mobile to) - { - ProcessOpeners(to); - - var ns = to.NetState; - - if (ns != null) - { - ns.SendDisplayContainer(Serial, GumpID); - - SendContentTo(ns); - - if (ObjectPropertyList.Enabled) - { - for (var i = 0; i < Items.Count; ++i) - { - ns.SendOPLInfo(Items[i]); - } - } - } - } - - public void ProcessOpeners(Mobile opener) - { - if (IsPublicContainer) - { - return; - } - - var contains = false; - - if (Openers != null) - { - var worldLoc = GetWorldLocation(); - var map = Map; - - for (var i = 0; i < Openers.Count; ++i) - { - var mob = Openers[i]; - - if (mob == opener) - { - contains = true; + item.Consume(theirAmount); + need -= theirAmount; } else { - var range = GetUpdateRange(mob); + callback?.Invoke(item, need); - if (mob.Map != map || !mob.InRange(worldLoc, range)) - { - Openers.RemoveAt(i--); - } + item.Consume(need); + break; } } - } - if (!contains) - { - Openers ??= new List(); - - Openers.Add(opener); - } - else if (Openers?.Count == 0) - { - Openers = null; + break; } } - public virtual void SendContentTo(NetState state) => state.SendContainerContent(state.Mobile, this); + return true; + } - public override void GetProperties(IPropertyList list) + public int ConsumeTotalGrouped( + Type[] types, int[] amounts, bool recurse, OnItemConsumed callback, + CheckItemGroup grouper + ) + { + if (types.Length != amounts.Length) { - base.GetProperties(list); - - if (DisplaysContent) // CheckContentDisplay( from )) - { - if (Core.ML) - { - if (ParentsContain()) // Root Parent is the Mobile. Parent could be another container. - { - list.Add( - 1073841, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones - $"{TotalItems}\t{MaxItems}\t{TotalWeight}" - ); - } - else - { - list.Add( - 1072241, // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~/~4_MAXWEIGHT~ stones - $"{TotalItems}\t{MaxItems}\t{TotalWeight}\t{MaxWeight}" - ); - } - - // TODO: Where do the other clilocs come into play? 1073839 & 1073840? - } - else - { - // ~1_COUNT~ items, ~2_WEIGHT~ stones - list.Add(1050044, $"{TotalItems}\t{TotalWeight}"); - } - } + throw new ArgumentException("length of types and amounts must match"); } - public override void OnDoubleClick(Mobile from) + if (grouper == null) { - if (from.AccessLevel > AccessLevel.Player || from.InRange(GetWorldLocation(), 2)) - { - DisplayTo(from); - } - else - { - from.SendLocalizedMessage(500446); // That is too far away. - } + throw new ArgumentNullException(nameof(grouper)); } - public bool ConsumeTotalGrouped(Type type, int amount, bool recurse, OnItemConsumed callback, CheckItemGroup grouper) - { - if (grouper == null) - { - throw new ArgumentNullException(nameof(grouper)); - } + var items = new Item[types.Length][][]; + var totals = new int[types.Length][]; - var typedItems = FindItemsByType(type, recurse); + for (var i = 0; i < types.Length; ++i) + { + var typedItems = FindItemsByType(types[i], recurse); var groups = new List>(); var idx = 0; @@ -839,21 +951,21 @@ namespace Server.Items groups.Add(group); } - var items = new Item[groups.Count][]; - var totals = new int[groups.Count]; + items[i] = new Item[groups.Count][]; + totals[i] = new int[groups.Count]; var hasEnough = false; - for (var i = 0; i < groups.Count; ++i) + for (var j = 0; j < groups.Count; ++j) { - items[i] = groups[i].ToArray(); + items[i][j] = groups[j].ToArray(); - for (var j = 0; j < items[i].Length; ++j) + for (var k = 0; k < items[i][j].Length; ++k) { - totals[i] += items[i][j].Amount; + totals[i][j] += items[i][j][k].Amount; } - if (totals[i] >= amount) + if (totals[i][j] >= amounts[i]) { hasEnough = true; } @@ -861,18 +973,21 @@ namespace Server.Items if (!hasEnough) { - return false; + return i; } + } - for (var i = 0; i < items.Length; ++i) + for (var i = 0; i < items.Length; ++i) + { + for (var j = 0; j < items[i].Length; ++j) { - if (totals[i] >= amount) + if (totals[i][j] >= amounts[i]) { - var need = amount; + var need = amounts[i]; - for (var j = 0; j < items[i].Length; ++j) + for (var k = 0; k < items[i][j].Length; ++k) { - var item = items[i][j]; + var item = items[i][j][k]; var theirAmount = item.Amount; @@ -895,463 +1010,32 @@ namespace Server.Items break; } } - - return true; } - public int ConsumeTotalGrouped( - Type[] types, int[] amounts, bool recurse, OnItemConsumed callback, - CheckItemGroup grouper - ) + return -1; + } + + public int ConsumeTotalGrouped( + Type[][] types, int[] amounts, bool recurse, OnItemConsumed callback, + CheckItemGroup grouper + ) + { + if (types.Length != amounts.Length) { - if (types.Length != amounts.Length) - { - throw new ArgumentException("length of types and amounts must match"); - } - - if (grouper == null) - { - throw new ArgumentNullException(nameof(grouper)); - } - - var items = new Item[types.Length][][]; - var totals = new int[types.Length][]; - - for (var i = 0; i < types.Length; ++i) - { - var typedItems = FindItemsByType(types[i], recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - { - group.Add(b); - } - else - { - break; - } - - ++idx; - } - - groups.Add(group); - } - - items[i] = new Item[groups.Count][]; - totals[i] = new int[groups.Count]; - - var hasEnough = false; - - for (var j = 0; j < groups.Count; ++j) - { - items[i][j] = groups[j].ToArray(); - - for (var k = 0; k < items[i][j].Length; ++k) - { - totals[i][j] += items[i][j][k].Amount; - } - - if (totals[i][j] >= amounts[i]) - { - hasEnough = true; - } - } - - if (!hasEnough) - { - return i; - } - } - - for (var i = 0; i < items.Length; ++i) - { - for (var j = 0; j < items[i].Length; ++j) - { - if (totals[i][j] >= amounts[i]) - { - var need = amounts[i]; - - for (var k = 0; k < items[i][j].Length; ++k) - { - var item = items[i][j][k]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - - break; - } - } - } - - return -1; + throw new ArgumentException("length of types and amounts must match"); } - public int ConsumeTotalGrouped( - Type[][] types, int[] amounts, bool recurse, OnItemConsumed callback, - CheckItemGroup grouper - ) + if (grouper == null) { - if (types.Length != amounts.Length) - { - throw new ArgumentException("length of types and amounts must match"); - } - - if (grouper == null) - { - throw new ArgumentNullException(nameof(grouper)); - } - - var items = new Item[types.Length][][]; - var totals = new int[types.Length][]; - - for (var i = 0; i < types.Length; ++i) - { - var typedItems = FindItemsByType(types[i], recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - { - group.Add(b); - } - else - { - break; - } - - ++idx; - } - - groups.Add(group); - } - - items[i] = new Item[groups.Count][]; - totals[i] = new int[groups.Count]; - - var hasEnough = false; - - for (var j = 0; j < groups.Count; ++j) - { - items[i][j] = groups[j].ToArray(); - - for (var k = 0; k < items[i][j].Length; ++k) - { - totals[i][j] += items[i][j][k].Amount; - } - - if (totals[i][j] >= amounts[i]) - { - hasEnough = true; - } - } - - if (!hasEnough) - { - return i; - } - } - - for (var i = 0; i < items.Length; ++i) - { - for (var j = 0; j < items[i].Length; ++j) - { - if (totals[i][j] >= amounts[i]) - { - var need = amounts[i]; - - for (var k = 0; k < items[i][j].Length; ++k) - { - var item = items[i][j][k]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - - break; - } - } - } - - return -1; + throw new ArgumentNullException(nameof(grouper)); } - public int ConsumeTotal(Type[][] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) + var items = new Item[types.Length][][]; + var totals = new int[types.Length][]; + + for (var i = 0; i < types.Length; ++i) { - if (types.Length != amounts.Length) - { - throw new ArgumentException("length of types and amounts must match"); - } - - var items = new Item[types.Length][]; - var totals = new int[types.Length]; - - for (var i = 0; i < types.Length; ++i) - { - items[i] = FindItemsByType(types[i], recurse); - - for (var j = 0; j < items[i].Length; ++j) - { - totals[i] += items[i][j].Amount; - } - - if (totals[i] < amounts[i]) - { - return i; - } - } - - for (var i = 0; i < types.Length; ++i) - { - var need = amounts[i]; - - for (var j = 0; j < items[i].Length; ++j) - { - var item = items[i][j]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - } - - return -1; - } - - public int ConsumeTotal(Type[] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) - { - if (types.Length != amounts.Length) - { - throw new ArgumentException("length of types and amounts must match"); - } - - var items = new Item[types.Length][]; - var totals = new int[types.Length]; - - for (var i = 0; i < types.Length; ++i) - { - items[i] = FindItemsByType(types[i], recurse); - - for (var j = 0; j < items[i].Length; ++j) - { - totals[i] += items[i][j].Amount; - } - - if (totals[i] < amounts[i]) - { - return i; - } - } - - for (var i = 0; i < types.Length; ++i) - { - var need = amounts[i]; - - for (var j = 0; j < items[i].Length; ++j) - { - var item = items[i][j]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - } - - return -1; - } - - public bool ConsumeTotal(Type type, int amount = 1, bool recurse = true, OnItemConsumed callback = null) - { - var items = FindItemsByType(type, recurse); - - // First pass, compute total - var total = 0; - - for (var i = 0; i < items.Length; ++i) - { - total += items[i].Amount; - } - - if (total >= amount) - { - // We've enough, so consume it - - var need = amount; - - for (var i = 0; i < items.Length; ++i) - { - var item = items[i]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - - return true; - } - } - } - - return false; - } - - public int ConsumeUpTo(Type type, int amount, bool recurse = true) - { - var consumed = 0; - - using var toDelete = PooledRefQueue.Create(); - RecurseConsumeUpTo(this, type, amount, recurse, ref consumed, toDelete); - - while (toDelete.Count > 0) - { - toDelete.Dequeue().Delete(); - } - - return consumed; - } - - private static void RecurseConsumeUpTo( - Item current, Type type, int amount, bool recurse, ref int consumed, - PooledRefQueue toDelete - ) - { - if (current == null || current.Items.Count == 0) - { - return; - } - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (type.IsInstanceOfType(item)) - { - var need = amount - consumed; - var theirAmount = item.Amount; - - if (theirAmount <= need) - { - toDelete.Enqueue(item); - consumed += theirAmount; - } - else - { - item.Amount -= need; - consumed += need; - - return; - } - } - else if (recurse && item is Container) - { - RecurseConsumeUpTo(item, type, amount, true, ref consumed, toDelete); - } - } - } - - public int GetBestGroupAmount(Type type, bool recurse, CheckItemGroup grouper) - { - if (grouper == null) - { - throw new ArgumentNullException(nameof(grouper)); - } - - var best = 0; - - var typedItems = FindItemsByType(type, recurse); + var typedItems = FindItemsByType(types[i], recurse); var groups = new List>(); var idx = 0; @@ -1383,36 +1067,413 @@ namespace Server.Items groups.Add(group); } - for (var i = 0; i < groups.Count; ++i) + items[i] = new Item[groups.Count][]; + totals[i] = new int[groups.Count]; + + var hasEnough = false; + + for (var j = 0; j < groups.Count; ++j) { - var items = groups[i].ToArray(); + items[i][j] = groups[j].ToArray(); - var total = 0; - - for (var j = 0; j < items.Length; ++j) + for (var k = 0; k < items[i][j].Length; ++k) { - total += items[j].Amount; + totals[i][j] += items[i][j][k].Amount; } - if (total >= best) + if (totals[i][j] >= amounts[i]) { - best = total; + hasEnough = true; } } - return best; + if (!hasEnough) + { + return i; + } } - public int GetBestGroupAmount(Type[] types, bool recurse, CheckItemGroup grouper) + for (var i = 0; i < items.Length; ++i) { - if (grouper == null) + for (var j = 0; j < items[i].Length; ++j) { - throw new ArgumentNullException(nameof(grouper)); + if (totals[i][j] >= amounts[i]) + { + var need = amounts[i]; + + for (var k = 0; k < items[i][j].Length; ++k) + { + var item = items[i][j][k]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + + break; + } + } + } + + return -1; + } + + public int ConsumeTotal(Type[][] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) + { + if (types.Length != amounts.Length) + { + throw new ArgumentException("length of types and amounts must match"); + } + + var items = new Item[types.Length][]; + var totals = new int[types.Length]; + + for (var i = 0; i < types.Length; ++i) + { + items[i] = FindItemsByType(types[i], recurse); + + for (var j = 0; j < items[i].Length; ++j) + { + totals[i] += items[i][j].Amount; } - var best = 0; + if (totals[i] < amounts[i]) + { + return i; + } + } - var typedItems = FindItemsByType(types, recurse); + for (var i = 0; i < types.Length; ++i) + { + var need = amounts[i]; + + for (var j = 0; j < items[i].Length; ++j) + { + var item = items[i][j]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + } + + return -1; + } + + public int ConsumeTotal(Type[] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) + { + if (types.Length != amounts.Length) + { + throw new ArgumentException("length of types and amounts must match"); + } + + var items = new Item[types.Length][]; + var totals = new int[types.Length]; + + for (var i = 0; i < types.Length; ++i) + { + items[i] = FindItemsByType(types[i], recurse); + + for (var j = 0; j < items[i].Length; ++j) + { + totals[i] += items[i][j].Amount; + } + + if (totals[i] < amounts[i]) + { + return i; + } + } + + for (var i = 0; i < types.Length; ++i) + { + var need = amounts[i]; + + for (var j = 0; j < items[i].Length; ++j) + { + var item = items[i][j]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + } + + return -1; + } + + public bool ConsumeTotal(Type type, int amount = 1, bool recurse = true, OnItemConsumed callback = null) + { + var items = FindItemsByType(type, recurse); + + // First pass, compute total + var total = 0; + + for (var i = 0; i < items.Length; ++i) + { + total += items[i].Amount; + } + + if (total >= amount) + { + // We've enough, so consume it + + var need = amount; + + for (var i = 0; i < items.Length; ++i) + { + var item = items[i]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + + return true; + } + } + } + + return false; + } + + public int ConsumeUpTo(Type type, int amount, bool recurse = true) + { + var consumed = 0; + + using var toDelete = PooledRefQueue.Create(); + RecurseConsumeUpTo(this, type, amount, recurse, ref consumed, toDelete); + + while (toDelete.Count > 0) + { + toDelete.Dequeue().Delete(); + } + + return consumed; + } + + private static void RecurseConsumeUpTo( + Item current, Type type, int amount, bool recurse, ref int consumed, + PooledRefQueue toDelete + ) + { + if (current == null || current.Items.Count == 0) + { + return; + } + + var list = current.Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (type.IsInstanceOfType(item)) + { + var need = amount - consumed; + var theirAmount = item.Amount; + + if (theirAmount <= need) + { + toDelete.Enqueue(item); + consumed += theirAmount; + } + else + { + item.Amount -= need; + consumed += need; + + return; + } + } + else if (recurse && item is Container) + { + RecurseConsumeUpTo(item, type, amount, true, ref consumed, toDelete); + } + } + } + + public int GetBestGroupAmount(Type type, bool recurse, CheckItemGroup grouper) + { + if (grouper == null) + { + throw new ArgumentNullException(nameof(grouper)); + } + + var best = 0; + + var typedItems = FindItemsByType(type, recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + { + group.Add(b); + } + else + { + break; + } + + ++idx; + } + + groups.Add(group); + } + + for (var i = 0; i < groups.Count; ++i) + { + var items = groups[i].ToArray(); + + var total = 0; + + for (var j = 0; j < items.Length; ++j) + { + total += items[j].Amount; + } + + if (total >= best) + { + best = total; + } + } + + return best; + } + + public int GetBestGroupAmount(Type[] types, bool recurse, CheckItemGroup grouper) + { + if (grouper == null) + { + throw new ArgumentNullException(nameof(grouper)); + } + + var best = 0; + + var typedItems = FindItemsByType(types, recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + { + group.Add(b); + } + else + { + break; + } + + ++idx; + } + + groups.Add(group); + } + + for (var j = 0; j < groups.Count; ++j) + { + var items = groups[j].ToArray(); + var total = 0; + foreach (var item in items) + { + total += item.Amount; + } + + if (total >= best) + { + best = total; + } + } + + return best; + } + + public int GetBestGroupAmount(Type[][] types, bool recurse, CheckItemGroup grouper) + { + if (grouper == null) + { + throw new ArgumentNullException(nameof(grouper)); + } + + var best = 0; + + for (var i = 0; i < types.Length; ++i) + { + var typedItems = FindItemsByType(types[i], recurse); var groups = new List>(); var idx = 0; @@ -1448,9 +1509,10 @@ namespace Server.Items { var items = groups[j].ToArray(); var total = 0; - foreach (var item in items) + + for (var k = 0; k < items.Length; ++k) { - total += item.Amount; + total += items[k].Amount; } if (total >= best) @@ -1458,462 +1520,399 @@ namespace Server.Items best = total; } } - - return best; } - public int GetBestGroupAmount(Type[][] types, bool recurse, CheckItemGroup grouper) + return best; + } + + public int GetAmount(Type type, bool recurse = true) + { + var total = 0; + foreach (var item in FindItemsByType(type, recurse)) { - if (grouper == null) - { - throw new ArgumentNullException(nameof(grouper)); - } - - var best = 0; - - for (var i = 0; i < types.Length; ++i) - { - var typedItems = FindItemsByType(types[i], recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - { - group.Add(b); - } - else - { - break; - } - - ++idx; - } - - groups.Add(group); - } - - for (var j = 0; j < groups.Count; ++j) - { - var items = groups[j].ToArray(); - var total = 0; - - for (var k = 0; k < items.Length; ++k) - { - total += items[k].Amount; - } - - if (total >= best) - { - best = total; - } - } - } - - return best; + total += item.Amount; } - public int GetAmount(Type type, bool recurse = true) - { - var total = 0; - foreach (var item in FindItemsByType(type, recurse)) - { - total += item.Amount; - } + return total; + } - return total; + public int GetAmount(Type[] types, bool recurse = true) + { + var total = 0; + foreach (var item in FindItemsByType(types, recurse)) + { + total += item.Amount; } - public int GetAmount(Type[] types, bool recurse = true) - { - var total = 0; - foreach (var item in FindItemsByType(types, recurse)) - { - total += item.Amount; - } + return total; + } - return total; + public Item[] FindItemsByType(Type type, bool recurse = true) + { + if (m_FindItemsList.Count > 0) + { + m_FindItemsList.Clear(); } - public Item[] FindItemsByType(Type type, bool recurse = true) + RecurseFindItemsByType(this, type, recurse, m_FindItemsList); + + return m_FindItemsList.ToArray(); + } + + private static void RecurseFindItemsByType(Item current, Type type, bool recurse, List list) + { + if (current == null || current.Items.Count == 0) { - if (m_FindItemsList.Count > 0) - { - m_FindItemsList.Clear(); - } - - RecurseFindItemsByType(this, type, recurse, m_FindItemsList); - - return m_FindItemsList.ToArray(); + return; } - private static void RecurseFindItemsByType(Item current, Type type, bool recurse, List list) + var items = current.Items; + + for (var i = 0; i < items.Count; ++i) { - if (current == null || current.Items.Count == 0) + var item = items[i]; + + if (type.IsInstanceOfType(item)) { - return; + list.Add(item); } - var items = current.Items; - - for (var i = 0; i < items.Count; ++i) + if (recurse && item is Container) { - var item = items[i]; - - if (type.IsInstanceOfType(item)) - { - list.Add(item); - } - - if (recurse && item is Container) - { - RecurseFindItemsByType(item, type, true, list); - } - } - } - - public Item[] FindItemsByType(Type[] types, bool recurse = true) - { - if (m_FindItemsList.Count > 0) - { - m_FindItemsList.Clear(); - } - - RecurseFindItemsByType(this, types, recurse, m_FindItemsList); - - return m_FindItemsList.ToArray(); - } - - private static void RecurseFindItemsByType(Item current, Type[] types, bool recurse, List list) - { - if (current == null || current.Items.Count == 0) - { - return; - } - - var items = current.Items; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - if (InTypeList(item, types)) - { - list.Add(item); - } - - if (recurse && item is Container) - { - RecurseFindItemsByType(item, types, true, list); - } - } - } - - public Item FindItemByType(Type type, bool recurse = true) => RecurseFindItemByType(this, type, recurse); - - private static Item RecurseFindItemByType(Item current, Type type, bool recurse) - { - if (current == null || current.Items.Count == 0) - { - return null; - } - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (type.IsInstanceOfType(item)) - { - return item; - } - - if (recurse && item is Container) - { - var check = RecurseFindItemByType(item, type, true); - - if (check != null) - { - return check; - } - } - } - - return null; - } - - public Item FindItemByType(Type[] types, bool recurse = true) => RecurseFindItemByType(this, types, recurse); - - private static Item RecurseFindItemByType(Item current, Type[] types, bool recurse) - { - if (current == null || current.Items.Count == 0) - { - return null; - } - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (InTypeList(item, types)) - { - return item; - } - - if (recurse && item is Container) - { - var check = RecurseFindItemByType(item, types, true); - - if (check != null) - { - return check; - } - } - } - - return null; - } - - public List FindItemsByType(Predicate predicate) where T : Item => FindItemsByType(true, predicate); - - /// - /// Performs a Breadth-First search through all the s and - /// nested s within this . - /// - /// Type of objects being searched for - /// - /// Optional: If true, the search will recursively - /// check any nested s; otherwise, nested - /// s will not be searched. - /// - /// - /// Optional: A predicate to check if the - /// of type is one of the targets of the search. - /// - /// - /// A list of s of type that matche the optional - /// . - /// - public List FindItemsByType(bool recurse = true, Predicate predicate = null) where T : Item - { - using var queue = PooledRefQueue.Create(128); - queue.Enqueue(this); - var items = new List(); - while (queue.Count > 0) - { - var container = queue.Dequeue(); - foreach (var item in container.Items) - { - if (item is T typedItem && predicate?.Invoke(typedItem) != false) - { - items.Add(typedItem); - } - else if (recurse && item is Container itemContainer) - { - queue.Enqueue(itemContainer); - } - } - } - - return items; - } - - /// - /// Performs a Breadth-First search through all the s and - /// nested s within this . - /// - /// Type of object being searched for - /// - /// Optional: If true, the search will recursively - /// check any nested s; otherwise, nested - /// s will not be searched. - /// - /// - /// Optional: A predicate to check if the - /// of type is the target of the search. - /// - /// - /// The first of type that matches the optional - /// . - /// - public T FindItemByType(bool recurse = true, Predicate predicate = null) where T : Item - { - using var queue = PooledRefQueue.Create(128); - queue.Enqueue(this); - while (queue.Count > 0) - { - var container = queue.Dequeue(); - foreach (var item in container.Items) - { - if (item is T typedItem && predicate?.Invoke(typedItem) != false) - { - return typedItem; - } - - if (recurse && item is Container itemContainer) - { - queue.Enqueue(itemContainer); - } - } - } - - return null; - } - - private class GroupComparer : IComparer - { - private readonly CheckItemGroup m_Grouper; - - public GroupComparer(CheckItemGroup grouper) => m_Grouper = grouper; - - public int Compare(Item a, Item b) => m_Grouper(a, b); - } - - [Flags] - private enum SaveFlag : byte - { - None = 0x00000000, - MaxItems = 0x00000001, - GumpID = 0x00000002, - DropSound = 0x00000004, - LiftOverride = 0x00000008 - } - - private struct ItemStackEntry - { - public readonly Item m_StackItem; - public readonly Item m_DropItem; - - public ItemStackEntry(Item stack, Item drop) - { - m_StackItem = stack; - m_DropItem = drop; + RecurseFindItemsByType(item, type, true, list); } } } - public class ContainerData + public Item[] FindItemsByType(Type[] types, bool recurse = true) { - private static ILogger _logger; - private static ILogger Logger => _logger ??= LogFactory.GetLogger(typeof(ContainerData)); - private static readonly Dictionary m_Table; - - static ContainerData() + if (m_FindItemsList.Count > 0) { - m_Table = new Dictionary(); + m_FindItemsList.Clear(); + } - var path = Path.Combine(Core.BaseDirectory, "Data/containers.cfg"); + RecurseFindItemsByType(this, types, recurse, m_FindItemsList); - if (!File.Exists(path)) + return m_FindItemsList.ToArray(); + } + + private static void RecurseFindItemsByType(Item current, Type[] types, bool recurse, List list) + { + if (current == null || current.Items.Count == 0) + { + return; + } + + var items = current.Items; + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + + if (InTypeList(item, types)) { - Default = new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); - return; + list.Add(item); } - using (var reader = new StreamReader(path)) + if (recurse && item is Container) { - string line; + RecurseFindItemsByType(item, types, true, list); + } + } + } - while ((line = reader.ReadLine()) != null) + public Item FindItemByType(Type type, bool recurse = true) => RecurseFindItemByType(this, type, recurse); + + private static Item RecurseFindItemByType(Item current, Type type, bool recurse) + { + if (current == null || current.Items.Count == 0) + { + return null; + } + + var list = current.Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (type.IsInstanceOfType(item)) + { + return item; + } + + if (recurse && item is Container) + { + var check = RecurseFindItemByType(item, type, true); + + if (check != null) { - line = line.Trim(); + return check; + } + } + } - if (line.Length == 0 || line.StartsWithOrdinal("#")) + return null; + } + + public Item FindItemByType(Type[] types, bool recurse = true) => RecurseFindItemByType(this, types, recurse); + + private static Item RecurseFindItemByType(Item current, Type[] types, bool recurse) + { + if (current == null || current.Items.Count == 0) + { + return null; + } + + var list = current.Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (InTypeList(item, types)) + { + return item; + } + + if (recurse && item is Container) + { + var check = RecurseFindItemByType(item, types, true); + + if (check != null) + { + return check; + } + } + } + + return null; + } + + public List FindItemsByType(Predicate predicate) where T : Item => FindItemsByType(true, predicate); + + /// + /// Performs a Breadth-First search through all the s and + /// nested s within this . + /// + /// Type of objects being searched for + /// + /// Optional: If true, the search will recursively + /// check any nested s; otherwise, nested + /// s will not be searched. + /// + /// + /// Optional: A predicate to check if the + /// of type is one of the targets of the search. + /// + /// + /// A list of s of type that matche the optional + /// . + /// + public List FindItemsByType(bool recurse = true, Predicate predicate = null) where T : Item + { + using var queue = PooledRefQueue.Create(128); + queue.Enqueue(this); + var items = new List(); + while (queue.Count > 0) + { + var container = queue.Dequeue(); + foreach (var item in container.Items) + { + if (item is T typedItem && predicate?.Invoke(typedItem) != false) + { + items.Add(typedItem); + } + else if (recurse && item is Container itemContainer) + { + queue.Enqueue(itemContainer); + } + } + } + + return items; + } + + /// + /// Performs a Breadth-First search through all the s and + /// nested s within this . + /// + /// Type of object being searched for + /// + /// Optional: If true, the search will recursively + /// check any nested s; otherwise, nested + /// s will not be searched. + /// + /// + /// Optional: A predicate to check if the + /// of type is the target of the search. + /// + /// + /// The first of type that matches the optional + /// . + /// + public T FindItemByType(bool recurse = true, Predicate predicate = null) where T : Item + { + using var queue = PooledRefQueue.Create(128); + queue.Enqueue(this); + while (queue.Count > 0) + { + var container = queue.Dequeue(); + foreach (var item in container.Items) + { + if (item is T typedItem && predicate?.Invoke(typedItem) != false) + { + return typedItem; + } + + if (recurse && item is Container itemContainer) + { + queue.Enqueue(itemContainer); + } + } + } + + return null; + } + + private class GroupComparer : IComparer + { + private readonly CheckItemGroup m_Grouper; + + public GroupComparer(CheckItemGroup grouper) => m_Grouper = grouper; + + public int Compare(Item a, Item b) => m_Grouper(a, b); + } + + [Flags] + private enum SaveFlag : byte + { + None = 0x00000000, + MaxItems = 0x00000001, + GumpID = 0x00000002, + DropSound = 0x00000004, + LiftOverride = 0x00000008 + } + + private struct ItemStackEntry + { + public readonly Item m_StackItem; + public readonly Item m_DropItem; + + public ItemStackEntry(Item stack, Item drop) + { + m_StackItem = stack; + m_DropItem = drop; + } + } +} + +public class ContainerData +{ + private static ILogger _logger; + private static ILogger Logger => _logger ??= LogFactory.GetLogger(typeof(ContainerData)); + private static readonly Dictionary m_Table; + + static ContainerData() + { + m_Table = new Dictionary(); + + var path = Path.Combine(Core.BaseDirectory, "Data/containers.cfg"); + + if (!File.Exists(path)) + { + Default = new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); + return; + } + + using (var reader = new StreamReader(path)) + { + string line; + + while ((line = reader.ReadLine()) != null) + { + line = line.Trim(); + + if (line.Length == 0 || line.StartsWithOrdinal("#")) + { + continue; + } + + try + { + var split = line.Split('\t'); + + if (split.Length >= 3) { - continue; - } + var gumpID = Utility.ToInt32(split[0]); - try - { - var split = line.Split('\t'); - - if (split.Length >= 3) + var aRect = split[1].Split(' '); + if (aRect.Length < 4) { - var gumpID = Utility.ToInt32(split[0]); + continue; + } - var aRect = split[1].Split(' '); - if (aRect.Length < 4) + var x = Utility.ToInt32(aRect[0]); + var y = Utility.ToInt32(aRect[1]); + var width = Utility.ToInt32(aRect[2]); + var height = Utility.ToInt32(aRect[3]); + + var bounds = new Rectangle2D(x, y, width, height); + + var dropSound = Utility.ToInt32(split[2]); + + var data = new ContainerData(gumpID, bounds, dropSound); + + Default ??= data; + + if (split.Length >= 4) + { + var aIDs = split[3].Split(','); + + for (var i = 0; i < aIDs.Length; i++) { - continue; - } + var id = Utility.ToInt32(aIDs[i]); - var x = Utility.ToInt32(aRect[0]); - var y = Utility.ToInt32(aRect[1]); - var width = Utility.ToInt32(aRect[2]); - var height = Utility.ToInt32(aRect[3]); - - var bounds = new Rectangle2D(x, y, width, height); - - var dropSound = Utility.ToInt32(split[2]); - - var data = new ContainerData(gumpID, bounds, dropSound); - - Default ??= data; - - if (split.Length >= 4) - { - var aIDs = split[3].Split(','); - - for (var i = 0; i < aIDs.Length; i++) + if (m_Table.ContainsKey(id)) { - var id = Utility.ToInt32(aIDs[i]); - - if (m_Table.ContainsKey(id)) - { - Logger.Warning("double ItemID entry in Data\\containers.cfg"); - } - else - { - m_Table[id] = data; - } + Logger.Warning("double ItemID entry in Data\\containers.cfg"); + } + else + { + m_Table[id] = data; } } } } - catch - { - // ignored - } + } + catch + { + // ignored } } - - Default ??= new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); } - public ContainerData(int gumpID, Rectangle2D bounds, int dropSound) - { - GumpID = gumpID; - Bounds = bounds; - DropSound = dropSound; - } + Default ??= new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); + } - public static ContainerData Default { get; set; } + public ContainerData(int gumpID, Rectangle2D bounds, int dropSound) + { + GumpID = gumpID; + Bounds = bounds; + DropSound = dropSound; + } - public int GumpID { get; } + public static ContainerData Default { get; set; } - public Rectangle2D Bounds { get; } + public int GumpID { get; } - public int DropSound { get; } + public Rectangle2D Bounds { get; } - public static ContainerData GetData(int itemID) - { - m_Table.TryGetValue(itemID, out var data); - return data ?? Default; - } + public int DropSound { get; } + + public static ContainerData GetData(int itemID) + { + m_Table.TryGetValue(itemID, out var data); + return data ?? Default; } } diff --git a/Projects/Server/Items/Containers.cs b/Projects/Server/Items/Containers.cs index 132cc2fc7..192e06f5c 100644 --- a/Projects/Server/Items/Containers.cs +++ b/Projects/Server/Items/Containers.cs @@ -1,126 +1,125 @@ using Server.Accounting; using Server.Network; -namespace Server.Items +namespace Server.Items; + +public class BankBox : Container { - public class BankBox : Container + public BankBox(Serial serial) : base(serial) { - public BankBox(Serial serial) : base(serial) + } + + public BankBox(Mobile owner) : base(0xE7C) + { + Layer = Layer.Bank; + Movable = false; + Owner = owner; + } + + public override int DefaultMaxWeight => 0; + + public override bool IsVirtualItem => true; + + public Mobile Owner { get; private set; } + + public bool Opened { get; private set; } + + public static bool SendDeleteOnClose { get; set; } + + public void Open() + { + Opened = true; + + if (Owner != null) { - } + Owner.PrivateOverheadMessage( + MessageType.Regular, + 0x3B2, + true, + $"Bank container has {TotalItems} items, {TotalWeight} stones", + Owner.NetState + ); - public BankBox(Mobile owner) : base(0xE7C) - { - Layer = Layer.Bank; - Movable = false; - Owner = owner; - } - - public override int DefaultMaxWeight => 0; - - public override bool IsVirtualItem => true; - - public Mobile Owner { get; private set; } - - public bool Opened { get; private set; } - - public static bool SendDeleteOnClose { get; set; } - - public void Open() - { - Opened = true; - - if (Owner != null) - { - Owner.PrivateOverheadMessage( - MessageType.Regular, - 0x3B2, - true, - $"Bank container has {TotalItems} items, {TotalWeight} stones", - Owner.NetState - ); - - Owner.NetState?.SendEquipUpdate(this); - DisplayTo(Owner); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - - writer.Write(Owner); - writer.Write(Opened); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - Owner = reader.ReadEntity(); - Opened = reader.ReadBool(); - - if (Owner == null) - { - Delete(); - } - - break; - } - } - - if (ItemID == 0xE41) - { - ItemID = 0xE7C; - } - } - - public void Close() - { - Opened = false; - - if (SendDeleteOnClose) - { - Owner?.NetState.SendRemoveEntity(Serial); - } - } - - public override void OnSingleClick(Mobile from) - { - } - - public override void OnDoubleClick(Mobile from) - { - } - - public override DeathMoveResult OnParentDeath(Mobile parent) => DeathMoveResult.RemainEquipped; - - public override bool IsAccessibleTo(Mobile check) => - (check == Owner && Opened || check.AccessLevel >= AccessLevel.GameMaster) && base.IsAccessibleTo(check); - - public override bool OnDragDrop(Mobile from, Item dropped) => - (from == Owner && Opened || from.AccessLevel >= AccessLevel.GameMaster) && base.OnDragDrop(from, dropped); - - public override bool OnDragDropInto(Mobile from, Item item, Point3D p) => - (from == Owner && Opened || from.AccessLevel >= AccessLevel.GameMaster) && - base.OnDragDropInto(from, item, p); - - public override int GetTotal(TotalType type) - { - if (AccountGold.Enabled && Owner?.Account != null && type == TotalType.Gold) - { - return Owner.Account.TotalGold; - } - - return base.GetTotal(type); + Owner.NetState?.SendEquipUpdate(this); + DisplayTo(Owner); } } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); // version + + writer.Write(Owner); + writer.Write(Opened); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + switch (version) + { + case 0: + { + Owner = reader.ReadEntity(); + Opened = reader.ReadBool(); + + if (Owner == null) + { + Delete(); + } + + break; + } + } + + if (ItemID == 0xE41) + { + ItemID = 0xE7C; + } + } + + public void Close() + { + Opened = false; + + if (SendDeleteOnClose) + { + Owner?.NetState.SendRemoveEntity(Serial); + } + } + + public override void OnSingleClick(Mobile from) + { + } + + public override void OnDoubleClick(Mobile from) + { + } + + public override DeathMoveResult OnParentDeath(Mobile parent) => DeathMoveResult.RemainEquipped; + + public override bool IsAccessibleTo(Mobile check) => + (check == Owner && Opened || check.AccessLevel >= AccessLevel.GameMaster) && base.IsAccessibleTo(check); + + public override bool OnDragDrop(Mobile from, Item dropped) => + (from == Owner && Opened || from.AccessLevel >= AccessLevel.GameMaster) && base.OnDragDrop(from, dropped); + + public override bool OnDragDropInto(Mobile from, Item item, Point3D p) => + (from == Owner && Opened || from.AccessLevel >= AccessLevel.GameMaster) && + base.OnDragDropInto(from, item, p); + + public override int GetTotal(TotalType type) + { + if (AccountGold.Enabled && Owner?.Account != null && type == TotalType.Gold) + { + return Owner.Account.TotalGold; + } + + return base.GetTotal(type); + } } diff --git a/Projects/Server/Items/Item.cs b/Projects/Server/Items/Item.cs index 911557ede..a85317de4 100644 --- a/Projects/Server/Items/Item.cs +++ b/Projects/Server/Items/Item.cs @@ -7,1180 +7,342 @@ using Server.Logging; using Server.Network; using Server.Targeting; -namespace Server +namespace Server; + +/// +/// Internal flags used to signal how the item should be updated and resent to nearby clients. +/// +[Flags] +public enum ItemDelta { /// - /// Internal flags used to signal how the item should be updated and resent to nearby clients. + /// Nothing. /// - [Flags] - public enum ItemDelta + None = 0x00000000, + + /// + /// Resend the item. + /// + Update = 0x00000001, + + /// + /// Resend the item only if it is equipped. + /// + EquipOnly = 0x00000002, + + /// + /// Resend the item's properties. + /// + Properties = 0x00000004 +} + +/// +/// Enumeration containing possible ways to handle item ownership on death. +/// +public enum DeathMoveResult +{ + /// + /// The item should be placed onto the corpse. + /// + MoveToCorpse, + + /// + /// The item should remain equipped. + /// + RemainEquipped, + + /// + /// The item should be placed into the owners backpack. + /// + MoveToBackpack +} + +/// +/// Enumeration of an item's loot and steal state. +/// +public enum LootType : byte +{ + /// + /// Stealable. Lootable. + /// + Regular = 0, + + /// + /// Unstealable. Unlootable, unless owned by a murderer. + /// + Newbied = 1, + + /// + /// Unstealable. Unlootable, always. + /// + Blessed = 2, + + /// + /// Stealable. Lootable, always. + /// + Cursed = 3 +} + +public class BounceInfo +{ + public BounceInfo(Item item) { - /// - /// Nothing. - /// - None = 0x00000000, + Map = item.Map; + Location = item.Location; + WorldLoc = item.GetWorldLocation(); + Parent = item.Parent; + } - /// - /// Resend the item. - /// - Update = 0x00000001, + private BounceInfo(Map map, Point3D loc, Point3D worldLoc, IEntity parent) + { + Map = map; + Location = loc; + WorldLoc = worldLoc; + Parent = parent; + } - /// - /// Resend the item only if it is equipped. - /// - EquipOnly = 0x00000002, + public Point3D Location { get; set; } + public Point3D WorldLoc { get; set; } + public Map Map { get; set; } + public IEntity Parent { get; set; } - /// - /// Resend the item's properties. - /// - Properties = 0x00000004 + public static BounceInfo Deserialize(IGenericReader reader) + { + if (reader.ReadBool()) + { + var map = reader.ReadMap(); + var loc = reader.ReadPoint3D(); + var worldLoc = reader.ReadPoint3D(); + + IEntity parent = reader.ReadEntity(); + + return new BounceInfo(map, loc, worldLoc, parent); + } + + return null; + } + + public static void Serialize(BounceInfo info, IGenericWriter writer) + { + if (info == null) + { + writer.Write(false); + } + else + { + writer.Write(true); + + writer.Write(info.Map); + writer.Write(info.Location); + writer.Write(info.WorldLoc); + + if (info.Parent is Mobile mobile) + { + writer.Write(mobile); + } + else if (info.Parent is Item item) + { + writer.Write(item); + } + else + { + writer.Write((Serial)0); + } + } + } +} + +public enum TotalType +{ + Gold, + Items, + Weight +} + +[Flags] +public enum ExpandFlag +{ + None = 0x000, + + Name = 0x001, + Items = 0x002, + Bounce = 0x004, + Holder = 0x008, + Blessed = 0x010, + TempFlag = 0x020, + SaveFlag = 0x040, + Weight = 0x080, + Spawner = 0x100 +} + +public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEntity +{ + private static readonly ILogger logger = LogFactory.GetLogger(typeof(Item)); + + public const int QuestItemHue = 0x4EA; // Hmmmm... "for EA"? + public static readonly List EmptyItems = new(); + private static readonly Queue m_DeltaQueue = new(); + + private static int m_OpenSlots; + private int m_Amount; + + private CompactInfo m_CompactInfo; + + private ItemDelta m_DeltaFlags; + private Direction m_Direction; + private ImplFlag m_Flags; + private int m_Hue; + private int m_ItemID; + private Layer m_Layer; + + private Point3D m_Location; + private LootType m_LootType; + private Map m_Map; + private IEntity m_Parent; // Mobile, Item, or null=World + + private ObjectPropertyList m_PropertyList; + + [Constructible] + public Item(int itemID = 0) + { + m_ItemID = itemID; + Serial = World.NewItem; + + // m_Items = new ArrayList( 1 ); + Visible = true; + Movable = true; + Amount = 1; + m_Map = Map.Internal; + + SetLastMoved(); + + World.AddEntity(this); + SetTypeRef(GetType()); + } + + public Item(Serial serial) + { + Serial = serial; + SetTypeRef(GetType()); + } + + public void SetTypeRef(Type type) + { + TypeRef = World.ItemTypes.IndexOf(type); + + if (TypeRef == -1) + { + World.ItemTypes.Add(type); + TypeRef = World.ItemTypes.Count - 1; + } + } + + public int TempFlags + { + get => LookupCompactInfo()?.m_TempFlags ?? 0; + set + { + var info = AcquireCompactInfo(); + + info.m_TempFlags = value; + + if (info.m_TempFlags == 0) + { + VerifyCompactInfo(); + } + } + } + + public int SavedFlags + { + get => LookupCompactInfo()?.m_SavedFlags ?? 0; + set + { + var info = AcquireCompactInfo(); + + info.m_SavedFlags = value; + + if (info.m_SavedFlags == 0) + { + VerifyCompactInfo(); + } + } } /// - /// Enumeration containing possible ways to handle item ownership on death. + /// The who is currently holding this item. /// - public enum DeathMoveResult + public Mobile HeldBy { - /// - /// The item should be placed onto the corpse. - /// - MoveToCorpse, + get => LookupCompactInfo()?.m_HeldBy; + set + { + var info = AcquireCompactInfo(); - /// - /// The item should remain equipped. - /// - RemainEquipped, + info.m_HeldBy = value; - /// - /// The item should be placed into the owners backpack. - /// - MoveToBackpack + if (info.m_HeldBy == null) + { + VerifyCompactInfo(); + } + } } /// - /// Enumeration of an item's loot and steal state. + /// Overridable. Determines whether the item will show . /// - public enum LootType : byte + public virtual bool DisplayWeight => Core.ML && (Movable || IsLockedDown || IsSecure || ItemData.Weight != 255); + + [CommandProperty(AccessLevel.GameMaster)] + public LootType LootType { - /// - /// Stealable. Lootable. - /// - Regular = 0, - - /// - /// Unstealable. Unlootable, unless owned by a murderer. - /// - Newbied = 1, - - /// - /// Unstealable. Unlootable, always. - /// - Blessed = 2, - - /// - /// Stealable. Lootable, always. - /// - Cursed = 3 - } - - public class BounceInfo - { - public BounceInfo(Item item) + get => m_LootType; + set { - Map = item.Map; - Location = item.Location; - WorldLoc = item.GetWorldLocation(); - Parent = item.Parent; - } - - private BounceInfo(Map map, Point3D loc, Point3D worldLoc, IEntity parent) - { - Map = map; - Location = loc; - WorldLoc = worldLoc; - Parent = parent; - } - - public Point3D Location { get; set; } - public Point3D WorldLoc { get; set; } - public Map Map { get; set; } - public IEntity Parent { get; set; } - - public static BounceInfo Deserialize(IGenericReader reader) - { - if (reader.ReadBool()) + if (m_LootType != value) { - var map = reader.ReadMap(); - var loc = reader.ReadPoint3D(); - var worldLoc = reader.ReadPoint3D(); + m_LootType = value; - IEntity parent = reader.ReadEntity(); - - return new BounceInfo(map, loc, worldLoc, parent); - } - - return null; - } - - public static void Serialize(BounceInfo info, IGenericWriter writer) - { - if (info == null) - { - writer.Write(false); - } - else - { - writer.Write(true); - - writer.Write(info.Map); - writer.Write(info.Location); - writer.Write(info.WorldLoc); - - if (info.Parent is Mobile mobile) + if (DisplayLootType) { - writer.Write(mobile); - } - else if (info.Parent is Item item) - { - writer.Write(item); - } - else - { - writer.Write((Serial)0); - } - } - } - } - - public enum TotalType - { - Gold, - Items, - Weight - } - - [Flags] - public enum ExpandFlag - { - None = 0x000, - - Name = 0x001, - Items = 0x002, - Bounce = 0x004, - Holder = 0x008, - Blessed = 0x010, - TempFlag = 0x020, - SaveFlag = 0x040, - Weight = 0x080, - Spawner = 0x100 - } - - public class Item : IHued, IComparable, ISpawnable, IObjectPropertyListEntity - { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(Item)); - - public const int QuestItemHue = 0x4EA; // Hmmmm... "for EA"? - public static readonly List EmptyItems = new(); - private static readonly Queue m_DeltaQueue = new(); - - private static int m_OpenSlots; - private int m_Amount; - - private CompactInfo m_CompactInfo; - - private ItemDelta m_DeltaFlags; - private Direction m_Direction; - private ImplFlag m_Flags; - private int m_Hue; - private int m_ItemID; - private Layer m_Layer; - - private Point3D m_Location; - private LootType m_LootType; - private Map m_Map; - private IEntity m_Parent; // Mobile, Item, or null=World - - private ObjectPropertyList m_PropertyList; - - [Constructible] - public Item(int itemID = 0) - { - m_ItemID = itemID; - Serial = World.NewItem; - - // m_Items = new ArrayList( 1 ); - Visible = true; - Movable = true; - Amount = 1; - m_Map = Map.Internal; - - SetLastMoved(); - - World.AddEntity(this); - SetTypeRef(GetType()); - } - - public Item(Serial serial) - { - Serial = serial; - SetTypeRef(GetType()); - } - - public void SetTypeRef(Type type) - { - TypeRef = World.ItemTypes.IndexOf(type); - - if (TypeRef == -1) - { - World.ItemTypes.Add(type); - TypeRef = World.ItemTypes.Count - 1; - } - } - - public int TempFlags - { - get => LookupCompactInfo()?.m_TempFlags ?? 0; - set - { - var info = AcquireCompactInfo(); - - info.m_TempFlags = value; - - if (info.m_TempFlags == 0) - { - VerifyCompactInfo(); - } - } - } - - public int SavedFlags - { - get => LookupCompactInfo()?.m_SavedFlags ?? 0; - set - { - var info = AcquireCompactInfo(); - - info.m_SavedFlags = value; - - if (info.m_SavedFlags == 0) - { - VerifyCompactInfo(); - } - } - } - - /// - /// The who is currently holding this item. - /// - public Mobile HeldBy - { - get => LookupCompactInfo()?.m_HeldBy; - set - { - var info = AcquireCompactInfo(); - - info.m_HeldBy = value; - - if (info.m_HeldBy == null) - { - VerifyCompactInfo(); - } - } - } - - /// - /// Overridable. Determines whether the item will show . - /// - public virtual bool DisplayWeight => Core.ML && (Movable || IsLockedDown || IsSecure || ItemData.Weight != 255); - - [CommandProperty(AccessLevel.GameMaster)] - public LootType LootType - { - get => m_LootType; - set - { - if (m_LootType != value) - { - m_LootType = value; - - if (DisplayLootType) - { - InvalidateProperties(); - } - } - } - } - - public static TimeSpan DefaultDecayTime { get; set; } = TimeSpan.FromHours(1.0); - - [CommandProperty(AccessLevel.GameMaster)] - public virtual TimeSpan DecayTime => DefaultDecayTime; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual bool Decays => Movable && Visible; - - public DateTime LastMoved { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Stackable - { - get => GetFlag(ImplFlag.Stackable); - set => SetFlag(ImplFlag.Stackable, value); - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Visible - { - get => GetFlag(ImplFlag.Visible); - set - { - if (GetFlag(ImplFlag.Visible) != value) - { - SetFlag(ImplFlag.Visible, value); - - if (m_Map != null) - { - var worldLoc = GetWorldLocation(); - - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (!m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) - { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - state.Send(removeEntity); - } - } - - eable.Free(); - } - - Delta(ItemDelta.Update); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Movable - { - get => GetFlag(ImplFlag.Movable); - set - { - if (GetFlag(ImplFlag.Movable) != value) - { - SetFlag(ImplFlag.Movable, value); - - Delta(ItemDelta.Update); - } - } - } - - public virtual bool ForceShowProperties => false; - - public virtual bool HandlesOnMovement => false; - - public static int LockedDownFlag { get; set; } - - public static int SecureFlag { get; set; } - - public bool IsLockedDown - { - get => GetTempFlag(LockedDownFlag); - set - { - SetTempFlag(LockedDownFlag, value); - InvalidateProperties(); - } - } - - public bool IsSecure - { - get => GetTempFlag(SecureFlag); - set - { - SetTempFlag(SecureFlag, value); - InvalidateProperties(); - } - } - - public virtual bool IsVirtualItem => false; - - public virtual bool CanSeeStaffOnly(Mobile from) => from.AccessLevel > AccessLevel.Counselor; - - public virtual int LabelNumber - { - get - { - if (m_ItemID < 0x4000) - { - return 1020000 + m_ItemID; - } - - return 1078872 + m_ItemID; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalGold => GetTotal(TotalType.Gold); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalItems => GetTotal(TotalType.Items); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalWeight => GetTotal(TotalType.Weight); - - public virtual double DefaultWeight - { - get - { - if (m_ItemID < 0 || m_ItemID > TileData.MaxItemValue || this is BaseMulti) - { - return 0; - } - - var weight = TileData.ItemTable[m_ItemID].Weight; - - if (weight is 255 or 0) - { - weight = 1; - } - - return weight; - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public double Weight - { - get - { - var info = LookupCompactInfo(); - - return info != null && info.m_Weight != -1 ? info.m_Weight : DefaultWeight; - } - set - { - if (Weight != value) - { - var info = AcquireCompactInfo(); - - var oldPileWeight = PileWeight; - - info.m_Weight = value; - - if (info.m_Weight == -1) - { - VerifyCompactInfo(); - } - - var newPileWeight = PileWeight; - - UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); - InvalidateProperties(); } } } + } - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int PileWeight => (int)Math.Ceiling(Weight * Amount); + public static TimeSpan DefaultDecayTime { get; set; } = TimeSpan.FromHours(1.0); - [Hue] - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Hue + [CommandProperty(AccessLevel.GameMaster)] + public virtual TimeSpan DecayTime => DefaultDecayTime; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual bool Decays => Movable && Visible; + + public DateTime LastMoved { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Stackable + { + get => GetFlag(ImplFlag.Stackable); + set => SetFlag(ImplFlag.Stackable, value); + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Visible + { + get => GetFlag(ImplFlag.Visible); + set { - get => m_Hue; - set + if (GetFlag(ImplFlag.Visible) != value) { - if (m_Hue != value) - { - m_Hue = value; - - Delta(ItemDelta.Update); - } - } - } - - public virtual bool Nontransferable => QuestItem; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual Layer Layer - { - get => m_Layer; - set - { - if (m_Layer != value) - { - m_Layer = value; - - Delta(ItemDelta.EquipOnly); - } - } - } - - public List Items => LookupItems() ?? EmptyItems; - - [CommandProperty(AccessLevel.GameMaster)] - public IEntity RootParent - { - get - { - var p = m_Parent; - - while (p is Item item) - { - if (item.m_Parent == null) - { - break; - } - - p = item.m_Parent; - } - - return p; - } - } - - public bool NoMoveHS { get; set; } - - public virtual int PhysicalResistance => 0; - public virtual int FireResistance => 0; - public virtual int ColdResistance => 0; - public virtual int PoisonResistance => 0; - public virtual int EnergyResistance => 0; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int ItemID - { - get => m_ItemID; - set - { - if (m_ItemID != value) - { - var oldPileWeight = PileWeight; - - m_ItemID = value; - - var newPileWeight = PileWeight; - - UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); - - InvalidateProperties(); - Delta(ItemDelta.Update); - } - } - } - - public virtual string DefaultName => null; - - [CommandProperty(AccessLevel.GameMaster)] - public string Name - { - get => LookupCompactInfo()?.m_Name ?? DefaultName; - set - { - if (value == null || value != DefaultName) - { - var info = AcquireCompactInfo(); - - info.m_Name = value; - - if (info.m_Name == null) - { - VerifyCompactInfo(); - } - - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Developer)] - public IEntity Parent - { - get => m_Parent; - set - { - if (m_Parent == value) - { - return; - } - - var oldParent = m_Parent; - - m_Parent = value; + SetFlag(ImplFlag.Visible, value); if (m_Map != null) { - if (oldParent != null && m_Parent == null) - { - m_Map.OnEnter(this); - } - else if (m_Parent != null) - { - m_Map.OnLeave(this); - } - } - } - } + var worldLoc = GetWorldLocation(); - [CommandProperty(AccessLevel.GameMaster)] - public LightType Light - { - get => (LightType)m_Direction; - set - { - if ((LightType)m_Direction != value) - { - m_Direction = (Direction)value; - Delta(ItemDelta.Update); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Direction Direction - { - get => m_Direction; - set - { - if (m_Direction != value) - { - m_Direction = value; - Delta(ItemDelta.Update); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Amount - { - get => m_Amount; - set - { - var oldValue = m_Amount; - - if (oldValue != value) - { - var oldPileWeight = PileWeight; - - m_Amount = value; - - var newPileWeight = PileWeight; - - UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); - - OnAmountChange(oldValue); - - Delta(ItemDelta.Update); - - if (oldValue > 1 || value > 1) - { - InvalidateProperties(); - } - - if (!Stackable && m_Amount > 1) - { - logger.Warning( - "{Serial}: Amount changed for non-stackable item '{Name}'. ({Amount})", - Serial, - m_Amount, - GetType().Name - ); - } - } - } - } - - public virtual bool HandlesOnSpeech => false; - - public virtual bool BlocksFit => false; - - public bool InSecureTrade => GetSecureTradeCont() != null; - - public ItemData ItemData => TileData.ItemTable[m_ItemID & TileData.MaxItemValue]; - - public virtual bool CanTarget => true; - public virtual bool DisplayLootType => true; - - public static bool ScissorCopyLootType { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool QuestItem - { - get => GetFlag(ImplFlag.QuestItem); - set - { - SetFlag(ImplFlag.QuestItem, value); - InvalidateProperties(); - Delta(ItemDelta.Update); - } - } - - public bool Insured - { - get => GetFlag(ImplFlag.Insured); - set - { - SetFlag(ImplFlag.Insured, value); - InvalidateProperties(); - } - } - - public bool PaidInsurance - { - get => GetFlag(ImplFlag.PaidInsurance); - set => SetFlag(ImplFlag.PaidInsurance, value); - } - - public Mobile BlessedFor - { - get => LookupCompactInfo()?.m_BlessedFor; - set - { - var info = AcquireCompactInfo(); - - info.m_BlessedFor = value; - - if (info.m_BlessedFor == null) - { - VerifyCompactInfo(); - } - - InvalidateProperties(); - } - } - - public int CompareTo(Item other) => other == null ? -1 : Serial.CompareTo(other.Serial); - - public virtual int HuedItemID => m_ItemID; - public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this)); - - /// - /// Overridable. Fills an with everything applicable. By default, this invokes - /// , then Item.GetChildProperties or - /// Mobile.GetChildProperties. This method should be overridden to add any - /// custom - /// properties. - /// - public virtual void GetProperties(IPropertyList list) - { - AddNameProperties(list); - } - - [CommandProperty(AccessLevel.GameMaster, readOnly: true)] - public DateTime Created { get; set; } = Core.Now; - - [CommandProperty(AccessLevel.GameMaster)] - DateTime ISerializable.LastSerialized { get; set; } = Core.Now; - - long ISerializable.SavePosition { get; set; } = -1; - - BufferWriter ISerializable.SaveBuffer { get; set; } - - [CommandProperty(AccessLevel.Counselor)] - public Serial Serial { get; } - - public int TypeRef { get; private set; } - - public virtual void Serialize(IGenericWriter writer) - { - writer.Write(9); // version - - var flags = SaveFlag.None; - - int x = m_Location.m_X, y = m_Location.m_Y, z = m_Location.m_Z; - - if (x != 0 || y != 0 || z != 0) - { - if (x is >= short.MinValue and <= short.MaxValue && y is >= short.MinValue and <= short.MaxValue && - z is >= sbyte.MinValue and <= sbyte.MaxValue) - { - if (x != 0 || y != 0) - { - if (x is >= byte.MinValue and <= byte.MaxValue && y is >= byte.MinValue and <= byte.MaxValue) - { - flags |= SaveFlag.LocationByteXY; - } - else - { - flags |= SaveFlag.LocationShortXY; - } - } - - if (z != 0) - { - flags |= SaveFlag.LocationSByteZ; - } - } - else - { - flags |= SaveFlag.LocationFull; - } - } - - var info = LookupCompactInfo(); - var items = LookupItems(); - - if (m_Direction != Direction.North) - { - flags |= SaveFlag.Direction; - } - - if (info?.m_Bounce != null) - { - flags |= SaveFlag.Bounce; - } - - if (m_LootType != LootType.Regular) - { - flags |= SaveFlag.LootType; - } - - if (m_ItemID != 0) - { - flags |= SaveFlag.ItemID; - } - - if (m_Hue != 0) - { - flags |= SaveFlag.Hue; - } - - if (m_Amount != 1) - { - flags |= SaveFlag.Amount; - } - - if (m_Layer != Layer.Invalid) - { - flags |= SaveFlag.Layer; - } - - if (info?.m_Name != null) - { - flags |= SaveFlag.Name; - } - - if (m_Parent != null) - { - flags |= SaveFlag.Parent; - } - - if (items.Count > 0) - { - flags |= SaveFlag.Items; - } - - if (m_Map != Map.Internal) - { - flags |= SaveFlag.Map; - } - // if (m_InsuredFor != null && !m_InsuredFor.Deleted) - // flags |= SaveFlag.InsuredFor; - - if (info != null) - { - if (info.m_BlessedFor?.Deleted == false) - { - flags |= SaveFlag.BlessedFor; - } - - if (info.m_HeldBy?.Deleted == false) - { - flags |= SaveFlag.HeldBy; - } - - if (info.m_SavedFlags != 0) - { - flags |= SaveFlag.SavedFlags; - } - } - - if (info == null || info.m_Weight == -1.0) - { - flags |= SaveFlag.NullWeight; - } - else - { - if (info.m_Weight == 0.0) - { - flags |= SaveFlag.WeightIs0; - } - else if (info.m_Weight != 1.0) - { - if (info.m_Weight == (int)info.m_Weight) - { - flags |= SaveFlag.IntWeight; - } - else - { - flags |= SaveFlag.WeightNot1or0; - } - } - } - - var implFlags = m_Flags & (ImplFlag.Visible | ImplFlag.Movable | ImplFlag.Stackable | ImplFlag.Insured | - ImplFlag.PaidInsurance | ImplFlag.QuestItem); - - if (implFlags != (ImplFlag.Visible | ImplFlag.Movable)) - { - flags |= SaveFlag.ImplFlags; - } - - writer.Write((int)flags); - - /* begin last moved time optimization */ - var ticks = LastMoved.Ticks; - var now = Core.Now.Ticks; - - var minutes = new TimeSpan(now - ticks).TotalMinutes; - - writer.WriteEncodedInt((int)Math.Clamp(minutes, int.MinValue, int.MaxValue)); - /* end */ - - if (GetSaveFlag(flags, SaveFlag.Direction)) - { - writer.Write((byte)m_Direction); - } - - if (GetSaveFlag(flags, SaveFlag.Bounce)) - { - BounceInfo.Serialize(info?.m_Bounce, writer); - } - - if (GetSaveFlag(flags, SaveFlag.LootType)) - { - writer.Write((byte)m_LootType); - } - - if (GetSaveFlag(flags, SaveFlag.LocationFull)) - { - writer.WriteEncodedInt(x); - writer.WriteEncodedInt(y); - writer.WriteEncodedInt(z); - } - else - { - if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) - { - writer.Write((byte)x); - writer.Write((byte)y); - } - else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) - { - writer.Write((short)x); - writer.Write((short)y); - } - - if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) - { - writer.Write((sbyte)z); - } - } - - if (GetSaveFlag(flags, SaveFlag.ItemID)) - { - writer.WriteEncodedInt(m_ItemID); - } - - if (GetSaveFlag(flags, SaveFlag.Hue)) - { - writer.WriteEncodedInt(m_Hue); - } - - if (GetSaveFlag(flags, SaveFlag.Amount)) - { - writer.WriteEncodedInt(m_Amount); - } - - if (GetSaveFlag(flags, SaveFlag.Layer)) - { - writer.Write((byte)m_Layer); - } - - if (GetSaveFlag(flags, SaveFlag.Name)) - { - writer.Write(info.m_Name); - } - - if (GetSaveFlag(flags, SaveFlag.Parent)) - { - writer.Write(m_Parent); - } - - if (GetSaveFlag(flags, SaveFlag.Items)) - { - writer.Write(items); - } - - if (GetSaveFlag(flags, SaveFlag.IntWeight)) - { - writer.WriteEncodedInt((int)info.m_Weight); - } - else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) - { - writer.Write(info.m_Weight); - } - - if (GetSaveFlag(flags, SaveFlag.Map)) - { - writer.Write(m_Map); - } - - if (GetSaveFlag(flags, SaveFlag.ImplFlags)) - { - writer.WriteEncodedInt((int)implFlags); - } - - if (GetSaveFlag(flags, SaveFlag.InsuredFor)) - { - writer.Write((Mobile)null); - } - - if (GetSaveFlag(flags, SaveFlag.BlessedFor)) - { - writer.Write(info.m_BlessedFor); - } - - if (GetSaveFlag(flags, SaveFlag.HeldBy)) - { - writer.Write(info.m_HeldBy); - } - - if (GetSaveFlag(flags, SaveFlag.SavedFlags)) - { - writer.WriteEncodedInt(info.m_SavedFlags); - } - } - - public void MoveToWorld(WorldLocation worldLocation) - { - MoveToWorld(worldLocation.Location, worldLocation.Map); - } - - /// - /// Moves the Item to a given and . - /// - public void MoveToWorld(Point3D location, Map map) - { - if (Deleted) - { - return; - } - - var oldLocation = GetWorldLocation(); - var oldRealLocation = m_Location; - - SetLastMoved(); - - if (Parent is Mobile mobile) - { - mobile.RemoveItem(this); - } - else if (Parent is Item item) - { - item.RemoveItem(this); - } - - if (m_Map != map) - { - var old = m_Map; - - if (m_Map != null) - { - m_Map.OnLeave(this); - - if (oldLocation.m_X != 0) - { - SendRemovePacket(oldLocation); - } - } - - m_Location = location; - OnLocationChange(oldRealLocation); - - var items = LookupItems(); - - for (var i = 0; i < items.Count; ++i) - { - items[i].Map = map; - } - - m_Map = map; - m_Map?.OnEnter(this); - - OnMapChange(); - - if (m_Map != null) - { - Span oldWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); - Span saWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); - Span hsWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); - - var eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) - { - if (state.HighSeas) - { - var length = OutgoingEntityPackets.CreateWorldEntity(hsWorldItem, this, true); - if (length != hsWorldItem.Length) - { - hsWorldItem = hsWorldItem[..length]; - } - - SendInfoTo(state, hsWorldItem); - } - else if (state.StygianAbyss) - { - var length = OutgoingEntityPackets.CreateWorldEntity(saWorldItem, this, false); - if (length != saWorldItem.Length) - { - saWorldItem = saWorldItem[..length]; - } - - SendInfoTo(state, saWorldItem); - } - else - { - var length = OutgoingItemPackets.CreateWorldItem(oldWorldItem, this); - if (length != oldWorldItem.Length) - { - oldWorldItem = oldWorldItem[..length]; - } - - SendInfoTo(state, oldWorldItem); - } - } - } - - eable.Free(); - } - - RemDelta(ItemDelta.Update); - - if (old == null || old == Map.Internal) - { - InvalidateProperties(); - } - } - else if (m_Map != null) - { - IPooledEnumerable eable; - - if (oldLocation.m_X != 0) - { - eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); @@ -1188,7 +350,7 @@ namespace Server { var m = state.Mobile; - if (!m.InRange(location, GetUpdateRange(m))) + if (!m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) { OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); state.Send(removeEntity); @@ -1198,12 +360,770 @@ namespace Server eable.Free(); } - var oldInternalLocation = m_Location; + Delta(ItemDelta.Update); + } + } + } - m_Location = location; - OnLocationChange(oldRealLocation); + [CommandProperty(AccessLevel.GameMaster)] + public bool Movable + { + get => GetFlag(ImplFlag.Movable); + set + { + if (GetFlag(ImplFlag.Movable) != value) + { + SetFlag(ImplFlag.Movable, value); - eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); + Delta(ItemDelta.Update); + } + } + } + + public virtual bool ForceShowProperties => false; + + public virtual bool HandlesOnMovement => false; + + public static int LockedDownFlag { get; set; } + + public static int SecureFlag { get; set; } + + public bool IsLockedDown + { + get => GetTempFlag(LockedDownFlag); + set + { + SetTempFlag(LockedDownFlag, value); + InvalidateProperties(); + } + } + + public bool IsSecure + { + get => GetTempFlag(SecureFlag); + set + { + SetTempFlag(SecureFlag, value); + InvalidateProperties(); + } + } + + public virtual bool IsVirtualItem => false; + + public virtual bool CanSeeStaffOnly(Mobile from) => from.AccessLevel > AccessLevel.Counselor; + + public virtual int LabelNumber + { + get + { + if (m_ItemID < 0x4000) + { + return 1020000 + m_ItemID; + } + + return 1078872 + m_ItemID; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalGold => GetTotal(TotalType.Gold); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalItems => GetTotal(TotalType.Items); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalWeight => GetTotal(TotalType.Weight); + + public virtual double DefaultWeight + { + get + { + if (m_ItemID < 0 || m_ItemID > TileData.MaxItemValue || this is BaseMulti) + { + return 0; + } + + var weight = TileData.ItemTable[m_ItemID].Weight; + + if (weight is 255 or 0) + { + weight = 1; + } + + return weight; + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public double Weight + { + get + { + var info = LookupCompactInfo(); + + return info != null && info.m_Weight != -1 ? info.m_Weight : DefaultWeight; + } + set + { + if (Weight != value) + { + var info = AcquireCompactInfo(); + + var oldPileWeight = PileWeight; + + info.m_Weight = value; + + if (info.m_Weight == -1) + { + VerifyCompactInfo(); + } + + var newPileWeight = PileWeight; + + UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); + + InvalidateProperties(); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int PileWeight => (int)Math.Ceiling(Weight * Amount); + + [Hue] + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Hue + { + get => m_Hue; + set + { + if (m_Hue != value) + { + m_Hue = value; + + Delta(ItemDelta.Update); + } + } + } + + public virtual bool Nontransferable => QuestItem; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual Layer Layer + { + get => m_Layer; + set + { + if (m_Layer != value) + { + m_Layer = value; + + Delta(ItemDelta.EquipOnly); + } + } + } + + public List Items => LookupItems() ?? EmptyItems; + + [CommandProperty(AccessLevel.GameMaster)] + public IEntity RootParent + { + get + { + var p = m_Parent; + + while (p is Item item) + { + if (item.m_Parent == null) + { + break; + } + + p = item.m_Parent; + } + + return p; + } + } + + public bool NoMoveHS { get; set; } + + public virtual int PhysicalResistance => 0; + public virtual int FireResistance => 0; + public virtual int ColdResistance => 0; + public virtual int PoisonResistance => 0; + public virtual int EnergyResistance => 0; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int ItemID + { + get => m_ItemID; + set + { + if (m_ItemID != value) + { + var oldPileWeight = PileWeight; + + m_ItemID = value; + + var newPileWeight = PileWeight; + + UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); + + InvalidateProperties(); + Delta(ItemDelta.Update); + } + } + } + + public virtual string DefaultName => null; + + [CommandProperty(AccessLevel.GameMaster)] + public string Name + { + get => LookupCompactInfo()?.m_Name ?? DefaultName; + set + { + if (value == null || value != DefaultName) + { + var info = AcquireCompactInfo(); + + info.m_Name = value; + + if (info.m_Name == null) + { + VerifyCompactInfo(); + } + + InvalidateProperties(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Developer)] + public IEntity Parent + { + get => m_Parent; + set + { + if (m_Parent == value) + { + return; + } + + var oldParent = m_Parent; + + m_Parent = value; + + if (m_Map != null) + { + if (oldParent != null && m_Parent == null) + { + m_Map.OnEnter(this); + } + else if (m_Parent != null) + { + m_Map.OnLeave(this); + } + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public LightType Light + { + get => (LightType)m_Direction; + set + { + if ((LightType)m_Direction != value) + { + m_Direction = (Direction)value; + Delta(ItemDelta.Update); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Direction Direction + { + get => m_Direction; + set + { + if (m_Direction != value) + { + m_Direction = value; + Delta(ItemDelta.Update); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Amount + { + get => m_Amount; + set + { + var oldValue = m_Amount; + + if (oldValue != value) + { + var oldPileWeight = PileWeight; + + m_Amount = value; + + var newPileWeight = PileWeight; + + UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); + + OnAmountChange(oldValue); + + Delta(ItemDelta.Update); + + if (oldValue > 1 || value > 1) + { + InvalidateProperties(); + } + + if (!Stackable && m_Amount > 1) + { + logger.Warning( + "{Serial}: Amount changed for non-stackable item '{Name}'. ({Amount})", + Serial, + m_Amount, + GetType().Name + ); + } + } + } + } + + public virtual bool HandlesOnSpeech => false; + + public virtual bool BlocksFit => false; + + public bool InSecureTrade => GetSecureTradeCont() != null; + + public ItemData ItemData => TileData.ItemTable[m_ItemID & TileData.MaxItemValue]; + + public virtual bool CanTarget => true; + public virtual bool DisplayLootType => true; + + public static bool ScissorCopyLootType { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool QuestItem + { + get => GetFlag(ImplFlag.QuestItem); + set + { + SetFlag(ImplFlag.QuestItem, value); + InvalidateProperties(); + Delta(ItemDelta.Update); + } + } + + public bool Insured + { + get => GetFlag(ImplFlag.Insured); + set + { + SetFlag(ImplFlag.Insured, value); + InvalidateProperties(); + } + } + + public bool PaidInsurance + { + get => GetFlag(ImplFlag.PaidInsurance); + set => SetFlag(ImplFlag.PaidInsurance, value); + } + + public Mobile BlessedFor + { + get => LookupCompactInfo()?.m_BlessedFor; + set + { + var info = AcquireCompactInfo(); + + info.m_BlessedFor = value; + + if (info.m_BlessedFor == null) + { + VerifyCompactInfo(); + } + + InvalidateProperties(); + } + } + + public int CompareTo(Item other) => other == null ? -1 : Serial.CompareTo(other.Serial); + + public virtual int HuedItemID => m_ItemID; + public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this)); + + /// + /// Overridable. Fills an with everything applicable. By default, this invokes + /// , then Item.GetChildProperties or + /// Mobile.GetChildProperties. This method should be overridden to add any + /// custom + /// properties. + /// + public virtual void GetProperties(IPropertyList list) + { + AddNameProperties(list); + } + + [CommandProperty(AccessLevel.GameMaster, readOnly: true)] + public DateTime Created { get; set; } = Core.Now; + + [CommandProperty(AccessLevel.GameMaster)] + DateTime ISerializable.LastSerialized { get; set; } = Core.Now; + + long ISerializable.SavePosition { get; set; } = -1; + + BufferWriter ISerializable.SaveBuffer { get; set; } + + [CommandProperty(AccessLevel.Counselor)] + public Serial Serial { get; } + + public int TypeRef { get; private set; } + + public virtual void Serialize(IGenericWriter writer) + { + writer.Write(9); // version + + var flags = SaveFlag.None; + + int x = m_Location.m_X, y = m_Location.m_Y, z = m_Location.m_Z; + + if (x != 0 || y != 0 || z != 0) + { + if (x is >= short.MinValue and <= short.MaxValue && y is >= short.MinValue and <= short.MaxValue && + z is >= sbyte.MinValue and <= sbyte.MaxValue) + { + if (x != 0 || y != 0) + { + if (x is >= byte.MinValue and <= byte.MaxValue && y is >= byte.MinValue and <= byte.MaxValue) + { + flags |= SaveFlag.LocationByteXY; + } + else + { + flags |= SaveFlag.LocationShortXY; + } + } + + if (z != 0) + { + flags |= SaveFlag.LocationSByteZ; + } + } + else + { + flags |= SaveFlag.LocationFull; + } + } + + var info = LookupCompactInfo(); + var items = LookupItems(); + + if (m_Direction != Direction.North) + { + flags |= SaveFlag.Direction; + } + + if (info?.m_Bounce != null) + { + flags |= SaveFlag.Bounce; + } + + if (m_LootType != LootType.Regular) + { + flags |= SaveFlag.LootType; + } + + if (m_ItemID != 0) + { + flags |= SaveFlag.ItemID; + } + + if (m_Hue != 0) + { + flags |= SaveFlag.Hue; + } + + if (m_Amount != 1) + { + flags |= SaveFlag.Amount; + } + + if (m_Layer != Layer.Invalid) + { + flags |= SaveFlag.Layer; + } + + if (info?.m_Name != null) + { + flags |= SaveFlag.Name; + } + + if (m_Parent != null) + { + flags |= SaveFlag.Parent; + } + + if (items.Count > 0) + { + flags |= SaveFlag.Items; + } + + if (m_Map != Map.Internal) + { + flags |= SaveFlag.Map; + } + // if (m_InsuredFor != null && !m_InsuredFor.Deleted) + // flags |= SaveFlag.InsuredFor; + + if (info != null) + { + if (info.m_BlessedFor?.Deleted == false) + { + flags |= SaveFlag.BlessedFor; + } + + if (info.m_HeldBy?.Deleted == false) + { + flags |= SaveFlag.HeldBy; + } + + if (info.m_SavedFlags != 0) + { + flags |= SaveFlag.SavedFlags; + } + } + + if (info == null || info.m_Weight == -1.0) + { + flags |= SaveFlag.NullWeight; + } + else + { + if (info.m_Weight == 0.0) + { + flags |= SaveFlag.WeightIs0; + } + else if (info.m_Weight != 1.0) + { + if (info.m_Weight == (int)info.m_Weight) + { + flags |= SaveFlag.IntWeight; + } + else + { + flags |= SaveFlag.WeightNot1or0; + } + } + } + + var implFlags = m_Flags & (ImplFlag.Visible | ImplFlag.Movable | ImplFlag.Stackable | ImplFlag.Insured | + ImplFlag.PaidInsurance | ImplFlag.QuestItem); + + if (implFlags != (ImplFlag.Visible | ImplFlag.Movable)) + { + flags |= SaveFlag.ImplFlags; + } + + writer.Write((int)flags); + + /* begin last moved time optimization */ + var ticks = LastMoved.Ticks; + var now = Core.Now.Ticks; + + var minutes = new TimeSpan(now - ticks).TotalMinutes; + + writer.WriteEncodedInt((int)Math.Clamp(minutes, int.MinValue, int.MaxValue)); + /* end */ + + if (GetSaveFlag(flags, SaveFlag.Direction)) + { + writer.Write((byte)m_Direction); + } + + if (GetSaveFlag(flags, SaveFlag.Bounce)) + { + BounceInfo.Serialize(info?.m_Bounce, writer); + } + + if (GetSaveFlag(flags, SaveFlag.LootType)) + { + writer.Write((byte)m_LootType); + } + + if (GetSaveFlag(flags, SaveFlag.LocationFull)) + { + writer.WriteEncodedInt(x); + writer.WriteEncodedInt(y); + writer.WriteEncodedInt(z); + } + else + { + if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) + { + writer.Write((byte)x); + writer.Write((byte)y); + } + else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) + { + writer.Write((short)x); + writer.Write((short)y); + } + + if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) + { + writer.Write((sbyte)z); + } + } + + if (GetSaveFlag(flags, SaveFlag.ItemID)) + { + writer.WriteEncodedInt(m_ItemID); + } + + if (GetSaveFlag(flags, SaveFlag.Hue)) + { + writer.WriteEncodedInt(m_Hue); + } + + if (GetSaveFlag(flags, SaveFlag.Amount)) + { + writer.WriteEncodedInt(m_Amount); + } + + if (GetSaveFlag(flags, SaveFlag.Layer)) + { + writer.Write((byte)m_Layer); + } + + if (GetSaveFlag(flags, SaveFlag.Name)) + { + writer.Write(info.m_Name); + } + + if (GetSaveFlag(flags, SaveFlag.Parent)) + { + writer.Write(m_Parent); + } + + if (GetSaveFlag(flags, SaveFlag.Items)) + { + writer.Write(items); + } + + if (GetSaveFlag(flags, SaveFlag.IntWeight)) + { + writer.WriteEncodedInt((int)info.m_Weight); + } + else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) + { + writer.Write(info.m_Weight); + } + + if (GetSaveFlag(flags, SaveFlag.Map)) + { + writer.Write(m_Map); + } + + if (GetSaveFlag(flags, SaveFlag.ImplFlags)) + { + writer.WriteEncodedInt((int)implFlags); + } + + if (GetSaveFlag(flags, SaveFlag.InsuredFor)) + { + writer.Write((Mobile)null); + } + + if (GetSaveFlag(flags, SaveFlag.BlessedFor)) + { + writer.Write(info.m_BlessedFor); + } + + if (GetSaveFlag(flags, SaveFlag.HeldBy)) + { + writer.Write(info.m_HeldBy); + } + + if (GetSaveFlag(flags, SaveFlag.SavedFlags)) + { + writer.WriteEncodedInt(info.m_SavedFlags); + } + } + + public void MoveToWorld(WorldLocation worldLocation) + { + MoveToWorld(worldLocation.Location, worldLocation.Map); + } + + /// + /// Moves the Item to a given and . + /// + public void MoveToWorld(Point3D location, Map map) + { + if (Deleted) + { + return; + } + + var oldLocation = GetWorldLocation(); + var oldRealLocation = m_Location; + + SetLastMoved(); + + if (Parent is Mobile mobile) + { + mobile.RemoveItem(this); + } + else if (Parent is Item item) + { + item.RemoveItem(this); + } + + if (m_Map != map) + { + var old = m_Map; + + if (m_Map != null) + { + m_Map.OnLeave(this); + + if (oldLocation.m_X != 0) + { + SendRemovePacket(oldLocation); + } + } + + m_Location = location; + OnLocationChange(oldRealLocation); + + var items = LookupItems(); + + for (var i = 0; i < items.Count; ++i) + { + items[i].Map = map; + } + + m_Map = map; + m_Map?.OnEnter(this); + + OnMapChange(); + + if (m_Map != null) + { + Span oldWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); + Span saWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); + Span hsWorldItem = stackalloc byte[OutgoingEntityPackets.MaxWorldEntityPacketLength].InitializePacket(); + + var eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); foreach (var state in eable) { @@ -1211,1670 +1131,1591 @@ namespace Server if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) { - SendInfoTo(state); + if (state.HighSeas) + { + var length = OutgoingEntityPackets.CreateWorldEntity(hsWorldItem, this, true); + if (length != hsWorldItem.Length) + { + hsWorldItem = hsWorldItem[..length]; + } + + SendInfoTo(state, hsWorldItem); + } + else if (state.StygianAbyss) + { + var length = OutgoingEntityPackets.CreateWorldEntity(saWorldItem, this, false); + if (length != saWorldItem.Length) + { + saWorldItem = saWorldItem[..length]; + } + + SendInfoTo(state, saWorldItem); + } + else + { + var length = OutgoingItemPackets.CreateWorldItem(oldWorldItem, this); + if (length != oldWorldItem.Length) + { + oldWorldItem = oldWorldItem[..length]; + } + + SendInfoTo(state, oldWorldItem); + } } } eable.Free(); - - m_Map.OnMove(oldInternalLocation, this); - - RemDelta(ItemDelta.Update); } - else + + RemDelta(ItemDelta.Update); + + if (old == null || old == Map.Internal) { - Map = map; - Location = location; + InvalidateProperties(); } } - - /// - /// Has the item been deleted? - /// - public bool Deleted => GetFlag(ImplFlag.Deleted); - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Map Map + else if (m_Map != null) { - get => m_Map; - set + IPooledEnumerable eable; + + if (oldLocation.m_X != 0) { - if (m_Map != value) + eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); + + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); + + foreach (var state in eable) { - var old = m_Map; + var m = state.Mobile; - if (m_Map != null && m_Parent == null) + if (!m.InRange(location, GetUpdateRange(m))) { - m_Map.OnLeave(this); - SendRemovePacket(); - } - - var items = LookupItems(); - - for (var i = 0; i < items.Count; ++i) - { - items[i].Map = value; - } - - m_Map = value; - - if (m_Parent == null) - { - m_Map?.OnEnter(this); - } - - Delta(ItemDelta.Update); - - OnMapChange(); - - if (old == null || old == Map.Internal) - { - InvalidateProperties(); + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + state.Send(removeEntity); } } - } - } - public virtual void ProcessDelta() - { - var flags = m_DeltaFlags; - - SetFlag(ImplFlag.InQueue, false); - m_DeltaFlags = ItemDelta.None; - - var map = m_Map; - - if (map == null || Deleted) - { - return; + eable.Free(); } - var worldLoc = GetWorldLocation(); - var update = (flags & ItemDelta.Update) != 0; + var oldInternalLocation = m_Location; - if (update && m_Parent is Container { IsPublicContainer: false } contParent) - { - var rootParent = contParent.RootParent as Mobile; - Mobile tradeRecip = null; + m_Location = location; + OnLocationChange(oldRealLocation); - if (rootParent != null) - { - var ns = rootParent.NetState; - - if (ns != null && rootParent.CanSee(this) && rootParent.InRange(worldLoc, GetUpdateRange(rootParent))) - { - ns.SendContainerContentUpdate(this); - SendOPLPacketTo(ns); - } - } - - var st = GetSecureTradeCont()?.Trade; - - if (st != null) - { - var test = st.From.Mobile; - - if (test != null && test != rootParent) - { - tradeRecip = test; - } - - test = st.To.Mobile; - - if (test != null && test != rootParent) - { - tradeRecip = test; - } - - var ns = tradeRecip?.NetState; - - if (ns != null && tradeRecip.CanSee(this) && tradeRecip.InRange(worldLoc, GetUpdateRange(tradeRecip))) - { - ns.SendContainerContentUpdate(this); - SendOPLPacketTo(ns); - } - } - - var openers = contParent.Openers; - - if (openers != null) - { - for (var i = 0; i < openers.Count; ++i) - { - var mob = openers[i]; - - var range = GetUpdateRange(mob); - - if (mob.Map != map || !mob.InRange(worldLoc, range)) - { - openers.RemoveAt(i--); - } - else - { - if (mob == rootParent || mob == tradeRecip) - { - continue; - } - - var ns = mob.NetState; - - if (ns != null && mob.CanSee(this)) - { - ns.SendContainerContentUpdate(this); - SendOPLPacketTo(ns); - } - } - } - - if (openers.Count == 0) - { - contParent.Openers = null; - } - } - - return; - } - - var eable = map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); foreach (var state in eable) { var m = state.Mobile; - if (!m.CanSee(this) || !m.InRange(worldLoc, GetUpdateRange(m))) + if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) { - continue; - } - - if (update) - { - if (m_Parent == null) - { - SendInfoTo(state); - } - else - { - if (m_Parent is Item) - { - // TODO: Optimize by writing once? - state.SendContainerContentUpdate(this); - } - else if (m_Parent is Mobile) - { - // TODO: Optimize by writing once? - state.SendEquipUpdate(this); - } - - SendOPLPacketTo(state); - } - } - else if ((flags & ItemDelta.EquipOnly) != 0 && m_Parent is Mobile) - { - state.SendEquipUpdate(this); - SendOPLPacketTo(state); - } - else if (ObjectPropertyList.Enabled && (flags & ItemDelta.Properties) != 0) - { - SendOPLPacketTo(state); + SendInfoTo(state); } } eable.Free(); + + m_Map.OnMove(oldInternalLocation, this); + + RemDelta(ItemDelta.Update); + } + else + { + Map = map; + Location = location; + } + } + + /// + /// Has the item been deleted? + /// + public bool Deleted => GetFlag(ImplFlag.Deleted); + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Map Map + { + get => m_Map; + set + { + if (m_Map != value) + { + var old = m_Map; + + if (m_Map != null && m_Parent == null) + { + m_Map.OnLeave(this); + SendRemovePacket(); + } + + var items = LookupItems(); + + for (var i = 0; i < items.Count; ++i) + { + items[i].Map = value; + } + + m_Map = value; + + if (m_Parent == null) + { + m_Map?.OnEnter(this); + } + + Delta(ItemDelta.Update); + + OnMapChange(); + + if (old == null || old == Map.Internal) + { + InvalidateProperties(); + } + } + } + } + + public virtual void ProcessDelta() + { + var flags = m_DeltaFlags; + + SetFlag(ImplFlag.InQueue, false); + m_DeltaFlags = ItemDelta.None; + + var map = m_Map; + + if (map == null || Deleted) + { + return; } - public virtual void Delete() + var worldLoc = GetWorldLocation(); + var update = (flags & ItemDelta.Update) != 0; + + if (update && m_Parent is Container { IsPublicContainer: false } contParent) { - if (Deleted) + var rootParent = contParent.RootParent as Mobile; + Mobile tradeRecip = null; + + if (rootParent != null) { - return; - } + var ns = rootParent.NetState; - OnDelete(); - - var items = LookupItems(); - - for (var i = items.Count - 1; i >= 0; --i) - { - if (i < items.Count) + if (ns != null && rootParent.CanSee(this) && rootParent.InRange(worldLoc, GetUpdateRange(rootParent))) { - items[i].OnParentDeleted(this); + ns.SendContainerContentUpdate(this); + SendOPLPacketTo(ns); } } - SendRemovePacket(); + var st = GetSecureTradeCont()?.Trade; - SetFlag(ImplFlag.Deleted, true); + if (st != null) + { + var test = st.From.Mobile; - if (Parent is Mobile mobile) - { - mobile.RemoveItem(this); - } - else if (Parent is Item item) - { - item.RemoveItem(this); + if (test != null && test != rootParent) + { + tradeRecip = test; + } + + test = st.To.Mobile; + + if (test != null && test != rootParent) + { + tradeRecip = test; + } + + var ns = tradeRecip?.NetState; + + if (ns != null && tradeRecip.CanSee(this) && tradeRecip.InRange(worldLoc, GetUpdateRange(tradeRecip))) + { + ns.SendContainerContentUpdate(this); + SendOPLPacketTo(ns); + } } - ClearBounce(); + var openers = contParent.Openers; + + if (openers != null) + { + for (var i = 0; i < openers.Count; ++i) + { + var mob = openers[i]; + + var range = GetUpdateRange(mob); + + if (mob.Map != map || !mob.InRange(worldLoc, range)) + { + openers.RemoveAt(i--); + } + else + { + if (mob == rootParent || mob == tradeRecip) + { + continue; + } + + var ns = mob.NetState; + + if (ns != null && mob.CanSee(this)) + { + ns.SendContainerContentUpdate(this); + SendOPLPacketTo(ns); + } + } + } + + if (openers.Count == 0) + { + contParent.Openers = null; + } + } + + return; + } + + var eable = map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (!m.CanSee(this) || !m.InRange(worldLoc, GetUpdateRange(m))) + { + continue; + } + + if (update) + { + if (m_Parent == null) + { + SendInfoTo(state); + } + else + { + if (m_Parent is Item) + { + // TODO: Optimize by writing once? + state.SendContainerContentUpdate(this); + } + else if (m_Parent is Mobile) + { + // TODO: Optimize by writing once? + state.SendEquipUpdate(this); + } + + SendOPLPacketTo(state); + } + } + else if ((flags & ItemDelta.EquipOnly) != 0 && m_Parent is Mobile) + { + state.SendEquipUpdate(this); + SendOPLPacketTo(state); + } + else if (ObjectPropertyList.Enabled && (flags & ItemDelta.Properties) != 0) + { + SendOPLPacketTo(state); + } + } + + eable.Free(); + } + + public virtual void Delete() + { + if (Deleted) + { + return; + } + + OnDelete(); + + var items = LookupItems(); + + for (var i = items.Count - 1; i >= 0; --i) + { + if (i < items.Count) + { + items[i].OnParentDeleted(this); + } + } + + SendRemovePacket(); + + SetFlag(ImplFlag.Deleted, true); + + if (Parent is Mobile mobile) + { + mobile.RemoveItem(this); + } + else if (Parent is Item item) + { + item.RemoveItem(this); + } + + ClearBounce(); + + if (m_Map != null) + { + if (m_Parent == null) + { + m_Map.OnLeave(this); + } + + m_Map = null; + } + + World.RemoveEntity(this); + + OnAfterDelete(); + ClearProperties(); + } + + public ISpawner Spawner + { + get => LookupCompactInfo()?.m_Spawner; + set + { + var info = AcquireCompactInfo(); + + info.m_Spawner = value; + + if (info.m_Spawner == null) + { + VerifyCompactInfo(); + } + } + } + + public virtual void OnBeforeSpawn(Point3D location, Map m) + { + } + + public virtual void OnAfterSpawn() + { + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public virtual Point3D Location + { + get => m_Location; + set + { + var oldLocation = m_Location; + + if (oldLocation == value) + { + return; + } if (m_Map != null) { if (m_Parent == null) { - m_Map.OnLeave(this); - } + IPooledEnumerable eable; - m_Map = null; - } - - World.RemoveEntity(this); - - OnAfterDelete(); - ClearProperties(); - } - - public ISpawner Spawner - { - get => LookupCompactInfo()?.m_Spawner; - set - { - var info = AcquireCompactInfo(); - - info.m_Spawner = value; - - if (info.m_Spawner == null) - { - VerifyCompactInfo(); - } - } - } - - public virtual void OnBeforeSpawn(Point3D location, Map m) - { - } - - public virtual void OnAfterSpawn() - { - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public virtual Point3D Location - { - get => m_Location; - set - { - var oldLocation = m_Location; - - if (oldLocation == value) - { - return; - } - - if (m_Map != null) - { - if (m_Parent == null) + if (m_Location.m_X != 0) { - IPooledEnumerable eable; + eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); - if (m_Location.m_X != 0) - { - eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); - - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (!m.InRange(value, GetUpdateRange(m))) - { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - state.Send(removeEntity); - } - } - - eable.Free(); - } - - var oldLoc = m_Location; - m_Location = value; - - SetLastMoved(); - - eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); foreach (var state in eable) { var m = state.Mobile; - if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m)) && - (!state.HighSeas || !NoMoveHS || (m_DeltaFlags & ItemDelta.Update) != 0 || - !m.InRange(oldLoc, GetUpdateRange(m)))) + if (!m.InRange(value, GetUpdateRange(m))) { - SendInfoTo(state); + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + state.Send(removeEntity); } } eable.Free(); - - RemDelta(ItemDelta.Update); } - else + + var oldLoc = m_Location; + m_Location = value; + + SetLastMoved(); + + eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); + + foreach (var state in eable) { - m_Location = value; - if (m_Parent is Item) + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m)) && + (!state.HighSeas || !NoMoveHS || (m_DeltaFlags & ItemDelta.Update) != 0 || + !m.InRange(oldLoc, GetUpdateRange(m)))) { - Delta(ItemDelta.Update); + SendInfoTo(state); } } - if (m_Parent == null) - { - m_Map.OnMove(oldLocation, this); - } + eable.Free(); + + RemDelta(ItemDelta.Update); } else { m_Location = value; - } - - OnLocationChange(oldLocation); - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int X - { - get => Location.m_X; - set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Y - { - get => Location.m_Y; - set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Z - { - get => Location.m_Z; - set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); - } - - public virtual bool InRange(Point2D p, int range) => Utility.InRange(p.X, p.Y, X, Y, range); - - public virtual bool InRange(Point3D p, int range) => Utility.InRange(p.X, p.Y, X, Y, range); - - public ExpandFlag GetExpandFlags() - { - var info = LookupCompactInfo(); - - ExpandFlag flags = 0; - - if (info != null) - { - if (info.m_BlessedFor != null) - { - flags |= ExpandFlag.Blessed; - } - - if (info.m_Bounce != null) - { - flags |= ExpandFlag.Bounce; - } - - if (info.m_HeldBy != null) - { - flags |= ExpandFlag.Holder; - } - - if (info.m_Items != null) - { - flags |= ExpandFlag.Items; - } - - if (info.m_Name != null) - { - flags |= ExpandFlag.Name; - } - - if (info.m_Spawner != null) - { - flags |= ExpandFlag.Spawner; - } - - if (info.m_SavedFlags != 0) - { - flags |= ExpandFlag.SaveFlag; - } - - if (info.m_TempFlags != 0) - { - flags |= ExpandFlag.TempFlag; - } - - if (info.m_Weight != -1) - { - flags |= ExpandFlag.Weight; - } - } - - return flags; - } - - private CompactInfo LookupCompactInfo() => m_CompactInfo; - - private CompactInfo AcquireCompactInfo() => m_CompactInfo ??= new CompactInfo(); - - private void ReleaseCompactInfo() - { - m_CompactInfo = null; - } - - private void VerifyCompactInfo() - { - var info = m_CompactInfo; - - if (info == null) - { - return; - } - - var isValid = info.m_Name != null - || info.m_Items != null - || info.m_Bounce != null - || info.m_HeldBy != null - || info.m_BlessedFor != null - || info.m_Spawner != null - || info.m_TempFlags != 0 - || info.m_SavedFlags != 0 - || info.m_Weight != -1; - - if (!isValid) - { - ReleaseCompactInfo(); - } - } - - public List LookupItems() => (this is Container container ? container.m_Items : LookupCompactInfo()?.m_Items) ?? EmptyItems; - - public List AcquireItems() - { - if (this is Container cont) - { - return cont.m_Items ?? (cont.m_Items = new List()); - } - - var info = AcquireCompactInfo(); - return info.m_Items ?? (info.m_Items = new List()); - } - - private void SetFlag(ImplFlag flag, bool value) - { - if (value) - { - m_Flags |= flag; - } - else - { - m_Flags &= ~flag; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool GetFlag(ImplFlag flag) => (m_Flags & flag) != 0; - - public BounceInfo GetBounce() => LookupCompactInfo()?.m_Bounce; - - public void RecordBounce() - { - AcquireCompactInfo().m_Bounce = new BounceInfo(this); - } - - public void ClearBounce() - { - var info = LookupCompactInfo(); - - var bounce = info?.m_Bounce; - - if (bounce == null) - { - return; - } - - info.m_Bounce = null; - - if (bounce.Parent is Item parentItem) - { - if (!parentItem.Deleted) - { - parentItem.OnItemBounceCleared(this); - } - } - else if (bounce.Parent is Mobile { Deleted: false } parentMobile) - { - parentMobile.OnItemBounceCleared(this); - } - - VerifyCompactInfo(); - } - - /// - /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Item. - /// Seemingly no longer functional in newer clients. - /// - public virtual void OnHelpRequest(Mobile from) - { - } - - /// - /// Overridable. Method checked to see if the item can be traded. - /// - /// True if the trade is allowed, false if not. - public virtual bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) => true; - - /// - /// Overridable. Virtual event invoked when a trade has completed, either successfully or not. - /// - public virtual void OnSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) - { - } - - /// - /// Overridable. Method checked to see if the elemental resistances of this Item conflict with another Item on the - /// . - /// - /// - /// - /// - /// True - /// - /// There is a conflict. The elemental resistance bonuses of this Item should not be applied to the - /// - /// - /// - /// - /// False - /// There is no conflict. The bonuses should be applied. - /// - /// - /// - public virtual bool CheckPropertyConflict(Mobile m) => false; - - /// - /// Overridable. Sends the object property list to . - /// - public virtual void SendPropertiesTo(NetState ns) - { - ns?.Send(PropertyList.Buffer); - } - - /// - /// Overridable. Adds the name of this item to the given . This method should be overridden - /// if the item requires a complex naming format. - /// - public virtual void AddNameProperty(IPropertyList list) - { - var name = Name; - - if (name == null) - { - if (m_Amount <= 1) - { - list.Add(LabelNumber); - } - else - { - list.Add(1050039, $"{m_Amount}\t{LabelNumber:#}"); // ~1_NUMBER~ ~2_ITEMNAME~ - } - } - else - { - if (m_Amount <= 1) - { - list.Add(name); - } - else - { - list.Add(1050039, $"{m_Amount}\t{Name}"); // ~1_NUMBER~ ~2_ITEMNAME~ - } - } - } - - /// - /// Overridable. Adds the loot type of this item to the given . By default, this will be - /// either 'blessed', 'cursed', or 'insured'. - /// - public virtual void AddLootTypeProperty(IPropertyList list) - { - if (m_LootType == LootType.Blessed) - { - list.Add(1038021); // blessed - } - else if (m_LootType == LootType.Cursed) - { - list.Add(1049643); // cursed - } - else if (Insured) - { - list.Add(1061682); // insured - } - } - - /// - /// Overridable. Adds any elemental resistances of this item to the given . - /// - public virtual void AddResistanceProperties(IPropertyList list) - { - var v = PhysicalResistance; - - if (v != 0) - { - list.Add(1060448, v); // physical resist ~1_val~% - } - - v = FireResistance; - - if (v != 0) - { - list.Add(1060447, v); // fire resist ~1_val~% - } - - v = ColdResistance; - - if (v != 0) - { - list.Add(1060445, v); // cold resist ~1_val~% - } - - v = PoisonResistance; - - if (v != 0) - { - list.Add(1060449, v); // poison resist ~1_val~% - } - - v = EnergyResistance; - - if (v != 0) - { - list.Add(1060446, v); // energy resist ~1_val~% - } - } - - /// - /// Overridable. Displays cliloc 1072788-1072789. - /// - public virtual void AddWeightProperty(IPropertyList list) - { - var weight = PileWeight + TotalWeight; - list.Add(weight == 1 ? 1072788 : 1072789, $"{weight}"); - } - - /// - /// Overridable. Adds header properties. By default, this invokes , - /// (if applicable), and (if - /// ). - /// - public virtual void AddNameProperties(IPropertyList list) - { - AddNameProperty(list); - - if (IsSecure) - { - AddSecureProperty(list); - } - else if (IsLockedDown) - { - AddLockedDownProperty(list); - } - - var blessedFor = BlessedFor; - - if (blessedFor?.Deleted == false) - { - AddBlessedForProperty(list, blessedFor); - } - - if (DisplayLootType) - { - AddLootTypeProperty(list); - } - - if (DisplayWeight) - { - AddWeightProperty(list); - } - - if (QuestItem) - { - AddQuestItemProperty(list); - } - - AppendChildNameProperties(list); - } - - /// - /// Overridable. Adds the "Quest Item" property to the given . - /// - public virtual void AddQuestItemProperty(IPropertyList list) - { - list.Add(1072351); // Quest Item - } - - /// - /// Overridable. Adds the "Locked Down & Secure" property to the given . - /// - public virtual void AddSecureProperty(IPropertyList list) - { - list.Add(501644); // locked down & secure - } - - /// - /// Overridable. Adds the "Locked Down" property to the given . - /// - public virtual void AddLockedDownProperty(IPropertyList list) - { - list.Add(501643); // locked down - } - - /// - /// Overridable. Adds the "Blessed for ~1_NAME~" property to the given . - /// - public virtual void AddBlessedForProperty(IPropertyList list, Mobile m) - { - list.Add(1062203, m.Name); // Blessed for ~1_NAME~ - } - - /// - /// Overridable. Event invoked when a child () is building it's . - /// Recursively calls Item.GetChildProperties or - /// Mobile.GetChildProperties. - /// - public virtual void GetChildProperties(IPropertyList list, Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.GetChildProperties(list, item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.GetChildProperties(list, item); - } - } - - /// - /// Overridable. Event invoked when a child () is building it's Name - /// - /// . Recursively calls Item.GetChildNameProperties or - /// Mobile.GetChildNameProperties. - /// - public virtual void GetChildNameProperties(IPropertyList list, Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.GetChildNameProperties(list, item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.GetChildNameProperties(list, item); - } - } - - public virtual bool IsChildVisibleTo(Mobile m, Item child) => true; - - public void Bounce(Mobile from) - { - m_Parent?.RemoveItem(this); - m_Parent = null; - - var bounce = GetBounce(); - - if (bounce == null) - { - MoveToWorld(from.Location, from.Map); - return; - } - - var parent = bounce.Parent; - - if (parent?.Deleted != false) - { - MoveToWorld(from.Location, from.Map); - return; - } - - if (parent is Item ip) - { - var root = ip.RootParent; - var rpm = root as Mobile; - - if (ip.IsAccessibleTo(from) - && rpm?.CheckNonlocalDrop(from, this, ip) != false - && (!ip.Movable || rpm == from || ip.Map == bounce.Map && root.Location == bounce.WorldLoc) - ) - { - Location = bounce.Location; - ip.AddItem(this); - } - else - { - MoveToWorld(from.Location, from.Map); - } - } - else if (parent is Mobile mobile) - { - if (!mobile.EquipItem(this)) - { - MoveToWorld(bounce.WorldLoc, bounce.Map); - } - } - else - { - MoveToWorld(from.Location, from.Map); - } - - ClearBounce(); - } - - /// - /// Overridable. Method checked to see if this item may be equipped while casting a spell. By default, this returns false. - /// It - /// is overridden on spellbook and spell channeling weapons or shields. - /// - /// True if it may, false if not. - /// - /// - /// public override bool AllowEquippedCast( Mobile from ) - /// { - /// if (from.Int >= 100) - /// return true; - /// - /// return base.AllowEquippedCast( from ); - /// } - /// When placed in an Item script, the item may be cast when equipped if the has 100 or more - /// intelligence. Otherwise, it will drop to their backpack. - /// - public virtual bool AllowEquippedCast(Mobile from) => false; - - public virtual bool CheckConflictingLayer(Mobile m, Item item, Layer layer) => m_Layer == layer; - - // Uses Race.RaceFlag - public virtual int RequiredRaces => Race.AllowAllRaces; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CheckRace(Race race) => Race.IsAllowedRace(race, RequiredRaces); - - public virtual bool CheckRace(Mobile from, bool message = true) - { - var race = from.Race; - var requiredRaces = RequiredRaces; - - if (Race.IsAllowedRace(race, requiredRaces)) - { - return true; - } - - if (!message) - { - return false; - } - - if (requiredRaces == Race.AllowGargoylesOnly) - { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1111707); // Only gargoyles can wear this. - } - else if (race == Race.Gargoyle) - { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1111708); // Gargoyles can't wear this. - } - else if (requiredRaces == Race.AllowElvesOnly) - { - from.SendLocalizedMessage(1072203); // Only Elves may use this. - } - else - { - from.SendMessage($"{race.PluralName} may not use this."); - } - - return false; - } - - public virtual bool CanEquip(Mobile m) => m_Layer != Layer.Invalid && m.FindItemOnLayer(m_Layer) == null; - - public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.GetChildContextMenuEntries(from, list, item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.GetChildContextMenuEntries(from, list, item); - } - } - - public virtual void GetContextMenuEntries(Mobile from, List list) - { - if (m_Parent is Item item) - { - item.GetChildContextMenuEntries(from, list, this); - } - else if (m_Parent is Mobile mobile) - { - mobile.GetChildContextMenuEntries(from, list, this); - } - } - - public virtual bool VerifyMove(Mobile from) => Movable; - - public virtual DeathMoveResult OnParentDeath(Mobile parent) - { - if (!Movable) - { - return DeathMoveResult.RemainEquipped; - } - - if (parent.KeepsItemsOnDeath) - { - return DeathMoveResult.MoveToBackpack; - } - - if (CheckBlessed(parent)) - { - return DeathMoveResult.MoveToBackpack; - } - - if (CheckNewbied() && parent.Kills < 5) - { - return DeathMoveResult.MoveToBackpack; - } - - if (parent.Player && Nontransferable) - { - return DeathMoveResult.MoveToBackpack; - } - - return DeathMoveResult.MoveToCorpse; - } - - public virtual DeathMoveResult OnInventoryDeath(Mobile parent) - { - if (!Movable) - { - return DeathMoveResult.MoveToBackpack; - } - - if (parent.KeepsItemsOnDeath) - { - return DeathMoveResult.MoveToBackpack; - } - - if (CheckBlessed(parent)) - { - return DeathMoveResult.MoveToBackpack; - } - - if (CheckNewbied() && parent.Kills < 5) - { - return DeathMoveResult.MoveToBackpack; - } - - if (parent.Player && Nontransferable) - { - return DeathMoveResult.MoveToBackpack; - } - - return DeathMoveResult.MoveToCorpse; - } - - /// - /// Moves the Item to . The Item does not change maps. - /// - public virtual void MoveToWorld(Point3D location) - { - MoveToWorld(location, m_Map); - } - - public void LabelTo(Mobile to, int number, string args = "") - { - to.NetState.SendMessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", args); - } - - public void LabelTo(Mobile to, string text) - { - to.NetState.SendMessage(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, false, "ENU", "", text); - } - - public void LabelTo(Mobile to, string format, params object[] args) - { - LabelTo(to, string.Format(format, args)); - } - - public void LabelToAffix(Mobile to, int number, AffixType type, string affix, string args = "") - { - to.NetState.SendMessageLocalizedAffix(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", type, affix, args); - } - - public virtual void LabelLootTypeTo(Mobile to) - { - if (m_LootType == LootType.Blessed) - { - LabelTo(to, 1041362); // (blessed) - } - else if (m_LootType == LootType.Cursed) - { - LabelTo(to, "(cursed)"); - } - } - - public bool AtWorldPoint(int x, int y) => m_Parent == null && m_Location.m_X == x && m_Location.m_Y == y; - - public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; - - public virtual bool CanDecay() => - Decays && Parent == null && Map != Map.Internal; - - - public virtual bool OnDecay() => - CanDecay() && Region.Find(Location, Map).OnDecay(this); - - public void SetLastMoved() - { - LastMoved = Core.Now; - } - - public virtual bool CanStackWith(Item dropped) => - dropped.Stackable && Stackable && dropped.GetType() == GetType() && dropped.ItemID == ItemID && - dropped.Hue == Hue && dropped.Name == Name && dropped.Amount + Amount <= 60000 && dropped != this; - - public bool StackWith(Mobile from, Item dropped) => StackWith(from, dropped, true); - - public virtual bool StackWith(Mobile from, Item dropped, bool playSound) - { - if (CanStackWith(dropped)) - { - if (m_LootType != dropped.m_LootType) - { - m_LootType = LootType.Regular; - } - - Amount += dropped.Amount; - dropped.Delete(); - - if (playSound && from != null) - { - var soundID = GetDropSound(); - - if (soundID == -1) + if (m_Parent is Item) { - soundID = 0x42; + Delta(ItemDelta.Update); } - - from.SendSound(soundID, GetWorldLocation()); } - return true; + if (m_Parent == null) + { + m_Map.OnMove(oldLocation, this); + } + } + else + { + m_Location = value; } + OnLocationChange(oldLocation); + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int X + { + get => Location.m_X; + set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Y + { + get => Location.m_Y; + set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Z + { + get => Location.m_Z; + set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); + } + + public virtual bool InRange(Point2D p, int range) => Utility.InRange(p.X, p.Y, X, Y, range); + + public virtual bool InRange(Point3D p, int range) => Utility.InRange(p.X, p.Y, X, Y, range); + + public ExpandFlag GetExpandFlags() + { + var info = LookupCompactInfo(); + + ExpandFlag flags = 0; + + if (info != null) + { + if (info.m_BlessedFor != null) + { + flags |= ExpandFlag.Blessed; + } + + if (info.m_Bounce != null) + { + flags |= ExpandFlag.Bounce; + } + + if (info.m_HeldBy != null) + { + flags |= ExpandFlag.Holder; + } + + if (info.m_Items != null) + { + flags |= ExpandFlag.Items; + } + + if (info.m_Name != null) + { + flags |= ExpandFlag.Name; + } + + if (info.m_Spawner != null) + { + flags |= ExpandFlag.Spawner; + } + + if (info.m_SavedFlags != 0) + { + flags |= ExpandFlag.SaveFlag; + } + + if (info.m_TempFlags != 0) + { + flags |= ExpandFlag.TempFlag; + } + + if (info.m_Weight != -1) + { + flags |= ExpandFlag.Weight; + } + } + + return flags; + } + + private CompactInfo LookupCompactInfo() => m_CompactInfo; + + private CompactInfo AcquireCompactInfo() => m_CompactInfo ??= new CompactInfo(); + + private void ReleaseCompactInfo() + { + m_CompactInfo = null; + } + + private void VerifyCompactInfo() + { + var info = m_CompactInfo; + + if (info == null) + { + return; + } + + var isValid = info.m_Name != null + || info.m_Items != null + || info.m_Bounce != null + || info.m_HeldBy != null + || info.m_BlessedFor != null + || info.m_Spawner != null + || info.m_TempFlags != 0 + || info.m_SavedFlags != 0 + || info.m_Weight != -1; + + if (!isValid) + { + ReleaseCompactInfo(); + } + } + + public List LookupItems() => (this is Container container ? container.m_Items : LookupCompactInfo()?.m_Items) ?? EmptyItems; + + public List AcquireItems() + { + if (this is Container cont) + { + return cont.m_Items ?? (cont.m_Items = new List()); + } + + var info = AcquireCompactInfo(); + return info.m_Items ?? (info.m_Items = new List()); + } + + private void SetFlag(ImplFlag flag, bool value) + { + if (value) + { + m_Flags |= flag; + } + else + { + m_Flags &= ~flag; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool GetFlag(ImplFlag flag) => (m_Flags & flag) != 0; + + public BounceInfo GetBounce() => LookupCompactInfo()?.m_Bounce; + + public void RecordBounce() + { + AcquireCompactInfo().m_Bounce = new BounceInfo(this); + } + + public void ClearBounce() + { + var info = LookupCompactInfo(); + + var bounce = info?.m_Bounce; + + if (bounce == null) + { + return; + } + + info.m_Bounce = null; + + if (bounce.Parent is Item parentItem) + { + if (!parentItem.Deleted) + { + parentItem.OnItemBounceCleared(this); + } + } + else if (bounce.Parent is Mobile { Deleted: false } parentMobile) + { + parentMobile.OnItemBounceCleared(this); + } + + VerifyCompactInfo(); + } + + /// + /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Item. + /// Seemingly no longer functional in newer clients. + /// + public virtual void OnHelpRequest(Mobile from) + { + } + + /// + /// Overridable. Method checked to see if the item can be traded. + /// + /// True if the trade is allowed, false if not. + public virtual bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) => true; + + /// + /// Overridable. Virtual event invoked when a trade has completed, either successfully or not. + /// + public virtual void OnSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) + { + } + + /// + /// Overridable. Method checked to see if the elemental resistances of this Item conflict with another Item on the + /// . + /// + /// + /// + /// + /// True + /// + /// There is a conflict. The elemental resistance bonuses of this Item should not be applied to the + /// + /// + /// + /// + /// False + /// There is no conflict. The bonuses should be applied. + /// + /// + /// + public virtual bool CheckPropertyConflict(Mobile m) => false; + + /// + /// Overridable. Sends the object property list to . + /// + public virtual void SendPropertiesTo(NetState ns) + { + ns?.Send(PropertyList.Buffer); + } + + /// + /// Overridable. Adds the name of this item to the given . This method should be overridden + /// if the item requires a complex naming format. + /// + public virtual void AddNameProperty(IPropertyList list) + { + var name = Name; + + if (name == null) + { + if (m_Amount <= 1) + { + list.Add(LabelNumber); + } + else + { + list.Add(1050039, $"{m_Amount}\t{LabelNumber:#}"); // ~1_NUMBER~ ~2_ITEMNAME~ + } + } + else + { + if (m_Amount <= 1) + { + list.Add(name); + } + else + { + list.Add(1050039, $"{m_Amount}\t{Name}"); // ~1_NUMBER~ ~2_ITEMNAME~ + } + } + } + + /// + /// Overridable. Adds the loot type of this item to the given . By default, this will be + /// either 'blessed', 'cursed', or 'insured'. + /// + public virtual void AddLootTypeProperty(IPropertyList list) + { + if (m_LootType == LootType.Blessed) + { + list.Add(1038021); // blessed + } + else if (m_LootType == LootType.Cursed) + { + list.Add(1049643); // cursed + } + else if (Insured) + { + list.Add(1061682); // insured + } + } + + /// + /// Overridable. Adds any elemental resistances of this item to the given . + /// + public virtual void AddResistanceProperties(IPropertyList list) + { + var v = PhysicalResistance; + + if (v != 0) + { + list.Add(1060448, v); // physical resist ~1_val~% + } + + v = FireResistance; + + if (v != 0) + { + list.Add(1060447, v); // fire resist ~1_val~% + } + + v = ColdResistance; + + if (v != 0) + { + list.Add(1060445, v); // cold resist ~1_val~% + } + + v = PoisonResistance; + + if (v != 0) + { + list.Add(1060449, v); // poison resist ~1_val~% + } + + v = EnergyResistance; + + if (v != 0) + { + list.Add(1060446, v); // energy resist ~1_val~% + } + } + + /// + /// Overridable. Displays cliloc 1072788-1072789. + /// + public virtual void AddWeightProperty(IPropertyList list) + { + var weight = PileWeight + TotalWeight; + list.Add(weight == 1 ? 1072788 : 1072789, $"{weight}"); + } + + /// + /// Overridable. Adds header properties. By default, this invokes , + /// (if applicable), and (if + /// ). + /// + public virtual void AddNameProperties(IPropertyList list) + { + AddNameProperty(list); + + if (IsSecure) + { + AddSecureProperty(list); + } + else if (IsLockedDown) + { + AddLockedDownProperty(list); + } + + var blessedFor = BlessedFor; + + if (blessedFor?.Deleted == false) + { + AddBlessedForProperty(list, blessedFor); + } + + if (DisplayLootType) + { + AddLootTypeProperty(list); + } + + if (DisplayWeight) + { + AddWeightProperty(list); + } + + if (QuestItem) + { + AddQuestItemProperty(list); + } + + AppendChildNameProperties(list); + } + + /// + /// Overridable. Adds the "Quest Item" property to the given . + /// + public virtual void AddQuestItemProperty(IPropertyList list) + { + list.Add(1072351); // Quest Item + } + + /// + /// Overridable. Adds the "Locked Down & Secure" property to the given . + /// + public virtual void AddSecureProperty(IPropertyList list) + { + list.Add(501644); // locked down & secure + } + + /// + /// Overridable. Adds the "Locked Down" property to the given . + /// + public virtual void AddLockedDownProperty(IPropertyList list) + { + list.Add(501643); // locked down + } + + /// + /// Overridable. Adds the "Blessed for ~1_NAME~" property to the given . + /// + public virtual void AddBlessedForProperty(IPropertyList list, Mobile m) + { + list.Add(1062203, m.Name); // Blessed for ~1_NAME~ + } + + /// + /// Overridable. Event invoked when a child () is building it's . + /// Recursively calls Item.GetChildProperties or + /// Mobile.GetChildProperties. + /// + public virtual void GetChildProperties(IPropertyList list, Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.GetChildProperties(list, item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.GetChildProperties(list, item); + } + } + + /// + /// Overridable. Event invoked when a child () is building it's Name + /// + /// . Recursively calls Item.GetChildNameProperties or + /// Mobile.GetChildNameProperties. + /// + public virtual void GetChildNameProperties(IPropertyList list, Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.GetChildNameProperties(list, item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.GetChildNameProperties(list, item); + } + } + + public virtual bool IsChildVisibleTo(Mobile m, Item child) => true; + + public void Bounce(Mobile from) + { + m_Parent?.RemoveItem(this); + m_Parent = null; + + var bounce = GetBounce(); + + if (bounce == null) + { + MoveToWorld(from.Location, from.Map); + return; + } + + var parent = bounce.Parent; + + if (parent?.Deleted != false) + { + MoveToWorld(from.Location, from.Map); + return; + } + + if (parent is Item ip) + { + var root = ip.RootParent; + var rpm = root as Mobile; + + if (ip.IsAccessibleTo(from) + && rpm?.CheckNonlocalDrop(from, this, ip) != false + && (!ip.Movable || rpm == from || ip.Map == bounce.Map && root.Location == bounce.WorldLoc) + ) + { + Location = bounce.Location; + ip.AddItem(this); + } + else + { + MoveToWorld(from.Location, from.Map); + } + } + else if (parent is Mobile mobile) + { + if (!mobile.EquipItem(this)) + { + MoveToWorld(bounce.WorldLoc, bounce.Map); + } + } + else + { + MoveToWorld(from.Location, from.Map); + } + + ClearBounce(); + } + + /// + /// Overridable. Method checked to see if this item may be equipped while casting a spell. By default, this returns false. + /// It + /// is overridden on spellbook and spell channeling weapons or shields. + /// + /// True if it may, false if not. + /// + /// + /// public override bool AllowEquippedCast( Mobile from ) + /// { + /// if (from.Int >= 100) + /// return true; + /// + /// return base.AllowEquippedCast( from ); + /// } + /// When placed in an Item script, the item may be cast when equipped if the has 100 or more + /// intelligence. Otherwise, it will drop to their backpack. + /// + public virtual bool AllowEquippedCast(Mobile from) => false; + + public virtual bool CheckConflictingLayer(Mobile m, Item item, Layer layer) => m_Layer == layer; + + // Uses Race.RaceFlag + public virtual int RequiredRaces => Race.AllowAllRaces; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CheckRace(Race race) => Race.IsAllowedRace(race, RequiredRaces); + + public virtual bool CheckRace(Mobile from, bool message = true) + { + var race = from.Race; + var requiredRaces = RequiredRaces; + + if (Race.IsAllowedRace(race, requiredRaces)) + { + return true; + } + + if (!message) + { return false; } - public virtual bool OnDragDrop(Mobile from, Item dropped) + if (requiredRaces == Race.AllowGargoylesOnly) { - var success = Parent is Container container && container.OnStackAttempt(from, this, dropped) || - StackWith(from, dropped); - - if (success && Spawner != null) - { - Spawner.Remove(this); - Spawner = null; - } - - return success; + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1111707); // Only gargoyles can wear this. + } + else if (race == Race.Gargoyle) + { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1111708); // Gargoyles can't wear this. + } + else if (requiredRaces == Race.AllowElvesOnly) + { + from.SendLocalizedMessage(1072203); // Only Elves may use this. + } + else + { + from.SendMessage($"{race.PluralName} may not use this."); } - public Rectangle2D GetGraphicBounds() + return false; + } + + public virtual bool CanEquip(Mobile m) => m_Layer != Layer.Invalid && m.FindItemOnLayer(m_Layer) == null; + + public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) + { + if (m_Parent is Item parentItem) { - var itemID = m_ItemID; - var doubled = m_Amount > 1; + parentItem.GetChildContextMenuEntries(from, list, item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.GetChildContextMenuEntries(from, list, item); + } + } - if (itemID is >= 0xEEA and <= 0xEF2) // Are we coins? + public virtual void GetContextMenuEntries(Mobile from, List list) + { + if (m_Parent is Item item) + { + item.GetChildContextMenuEntries(from, list, this); + } + else if (m_Parent is Mobile mobile) + { + mobile.GetChildContextMenuEntries(from, list, this); + } + } + + public virtual bool VerifyMove(Mobile from) => Movable; + + public virtual DeathMoveResult OnParentDeath(Mobile parent) + { + if (!Movable) + { + return DeathMoveResult.RemainEquipped; + } + + if (parent.KeepsItemsOnDeath) + { + return DeathMoveResult.MoveToBackpack; + } + + if (CheckBlessed(parent)) + { + return DeathMoveResult.MoveToBackpack; + } + + if (CheckNewbied() && parent.Kills < 5) + { + return DeathMoveResult.MoveToBackpack; + } + + if (parent.Player && Nontransferable) + { + return DeathMoveResult.MoveToBackpack; + } + + return DeathMoveResult.MoveToCorpse; + } + + public virtual DeathMoveResult OnInventoryDeath(Mobile parent) + { + if (!Movable) + { + return DeathMoveResult.MoveToBackpack; + } + + if (parent.KeepsItemsOnDeath) + { + return DeathMoveResult.MoveToBackpack; + } + + if (CheckBlessed(parent)) + { + return DeathMoveResult.MoveToBackpack; + } + + if (CheckNewbied() && parent.Kills < 5) + { + return DeathMoveResult.MoveToBackpack; + } + + if (parent.Player && Nontransferable) + { + return DeathMoveResult.MoveToBackpack; + } + + return DeathMoveResult.MoveToCorpse; + } + + /// + /// Moves the Item to . The Item does not change maps. + /// + public virtual void MoveToWorld(Point3D location) + { + MoveToWorld(location, m_Map); + } + + public void LabelTo(Mobile to, int number, string args = "") + { + to.NetState.SendMessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", args); + } + + public void LabelTo(Mobile to, string text) + { + to.NetState.SendMessage(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, false, "ENU", "", text); + } + + public void LabelTo(Mobile to, string format, params object[] args) + { + LabelTo(to, string.Format(format, args)); + } + + public void LabelToAffix(Mobile to, int number, AffixType type, string affix, string args = "") + { + to.NetState.SendMessageLocalizedAffix(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", type, affix, args); + } + + public virtual void LabelLootTypeTo(Mobile to) + { + if (m_LootType == LootType.Blessed) + { + LabelTo(to, 1041362); // (blessed) + } + else if (m_LootType == LootType.Cursed) + { + LabelTo(to, "(cursed)"); + } + } + + public bool AtWorldPoint(int x, int y) => m_Parent == null && m_Location.m_X == x && m_Location.m_Y == y; + + public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; + + public virtual bool CanDecay() => + Decays && Parent == null && Map != Map.Internal; + + + public virtual bool OnDecay() => + CanDecay() && Region.Find(Location, Map).OnDecay(this); + + public void SetLastMoved() + { + LastMoved = Core.Now; + } + + public virtual bool CanStackWith(Item dropped) => + dropped.Stackable && Stackable && dropped.GetType() == GetType() && dropped.ItemID == ItemID && + dropped.Hue == Hue && dropped.Name == Name && dropped.Amount + Amount <= 60000 && dropped != this; + + public bool StackWith(Mobile from, Item dropped) => StackWith(from, dropped, true); + + public virtual bool StackWith(Mobile from, Item dropped, bool playSound) + { + if (CanStackWith(dropped)) + { + if (m_LootType != dropped.m_LootType) { - var coinBase = (itemID - 0xEEA) / 3; - coinBase *= 3; - coinBase += 0xEEA; + m_LootType = LootType.Regular; + } - doubled = false; + Amount += dropped.Amount; + dropped.Delete(); - itemID = m_Amount switch + if (playSound && from != null) + { + var soundID = GetDropSound(); + + if (soundID == -1) { - <= 1 => coinBase, - <= 5 => coinBase + 1, - _ => coinBase + 2 - }; + soundID = 0x42; + } + + from.SendSound(soundID, GetWorldLocation()); } - var bounds = ItemBounds.Table[itemID & 0x3FFF]; - - if (doubled) - { - bounds.Set(bounds.X, bounds.Y, bounds.Width + 5, bounds.Height + 5); - } - - return bounds; + return true; } - public virtual void AppendChildProperties(IPropertyList list) + return false; + } + + public virtual bool OnDragDrop(Mobile from, Item dropped) + { + var success = Parent is Container container && container.OnStackAttempt(from, this, dropped) || + StackWith(from, dropped); + + if (success && Spawner != null) { - if (m_Parent is Item item) - { - item.GetChildProperties(list, this); - } - else if (m_Parent is Mobile mobile) - { - mobile.GetChildProperties(list, this); - } + Spawner.Remove(this); + Spawner = null; } - public virtual void AppendChildNameProperties(IPropertyList list) + return success; + } + + public Rectangle2D GetGraphicBounds() + { + var itemID = m_ItemID; + var doubled = m_Amount > 1; + + if (itemID is >= 0xEEA and <= 0xEF2) // Are we coins? { - if (m_Parent is Item item) + var coinBase = (itemID - 0xEEA) / 3; + coinBase *= 3; + coinBase += 0xEEA; + + doubled = false; + + itemID = m_Amount switch { - item.GetChildNameProperties(list, this); - } - else if (m_Parent is Mobile mobile) - { - mobile.GetChildNameProperties(list, this); - } + <= 1 => coinBase, + <= 5 => coinBase + 1, + _ => coinBase + 2 + }; } - private ObjectPropertyList InitializePropertyList(ObjectPropertyList list) + var bounds = ItemBounds.Table[itemID & 0x3FFF]; + + if (doubled) { - GetProperties(list); - AppendChildProperties(list); - list.Terminate(); - return list; + bounds.Set(bounds.X, bounds.Y, bounds.Width + 5, bounds.Height + 5); } - public virtual void ClearProperties() + return bounds; + } + + public virtual void AppendChildProperties(IPropertyList list) + { + if (m_Parent is Item item) { - m_PropertyList = null; + item.GetChildProperties(list, this); } + else if (m_Parent is Mobile mobile) + { + mobile.GetChildProperties(list, this); + } + } + + public virtual void AppendChildNameProperties(IPropertyList list) + { + if (m_Parent is Item item) + { + item.GetChildNameProperties(list, this); + } + else if (m_Parent is Mobile mobile) + { + mobile.GetChildNameProperties(list, this); + } + } + + private ObjectPropertyList InitializePropertyList(ObjectPropertyList list) + { + GetProperties(list); + AppendChildProperties(list); + list.Terminate(); + return list; + } + + public virtual void ClearProperties() + { + m_PropertyList = null; + } #nullable enable - public virtual void InvalidateProperties() + public virtual void InvalidateProperties() + { + if (!ObjectPropertyList.Enabled) { - if (!ObjectPropertyList.Enabled) - { - return; - } + return; + } - if (m_Map != null && m_Map != Map.Internal && !World.Loading) + if (m_Map != null && m_Map != Map.Internal && !World.Loading) + { + int? oldHash; + int newHash; + if (m_PropertyList != null) { - int? oldHash; - int newHash; - if (m_PropertyList != null) - { - oldHash = m_PropertyList.Hash; - m_PropertyList.Reset(); - InitializePropertyList(m_PropertyList); - newHash = m_PropertyList.Hash; - } - else - { - oldHash = null; - newHash = PropertyList.Hash; - } - - if (oldHash != newHash) - { - Delta(ItemDelta.Properties); - } + oldHash = m_PropertyList.Hash; + m_PropertyList.Reset(); + InitializePropertyList(m_PropertyList); + newHash = m_PropertyList.Hash; } else { - ClearProperties(); + oldHash = null; + newHash = PropertyList.Hash; + } + + if (oldHash != newHash) + { + Delta(ItemDelta.Properties); } } + else + { + ClearProperties(); + } + } #nullable restore - public virtual int GetPacketFlags() + public virtual int GetPacketFlags() + { + var flags = 0; + + if (!Visible) { - var flags = 0; - - if (!Visible) - { - flags |= 0x80; - } - - if (Movable || ForceShowProperties) - { - flags |= 0x20; - } - - return flags; + flags |= 0x80; } - public virtual bool OnMoveOff(Mobile m) => true; - - public virtual bool OnMoveOver(Mobile m) => true; - - public virtual void OnMovement(Mobile m, Point3D oldLocation) + if (Movable || ForceShowProperties) { + flags |= 0x20; } - public void Internalize() + return flags; + } + + public virtual bool OnMoveOff(Mobile m) => true; + + public virtual bool OnMoveOver(Mobile m) => true; + + public virtual void OnMovement(Mobile m, Point3D oldLocation) + { + } + + public void Internalize() + { + MoveToWorld(Point3D.Zero, Map.Internal); + } + + public virtual void OnMapChange() + { + } + + public virtual void OnRemoved(IEntity parent) + { + } + + public virtual void OnAdded(IEntity parent) + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) + { + if (setIf) { - MoveToWorld(Point3D.Zero, Map.Internal); + flags |= toSet; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; + + public IPooledEnumerable GetObjectsInRange(int range) + { + var map = m_Map; + + return map == null + ? Map.NullEnumerable.Instance + : map.GetObjectsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); + } + + public IPooledEnumerable GetItemsInRange(int range) + { + var map = m_Map; + + return map?.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) + ?? Map.NullEnumerable.Instance; + } + + public IPooledEnumerable GetMobilesInRange(int range) + { + var map = m_Map; + + return map?.GetMobilesInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) + ?? Map.NullEnumerable.Instance; + } + + public IPooledEnumerable GetClientsInRange(int range) + { + var map = m_Map; + + return map.GetClientsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) + ?? Map.NullEnumerable.Instance; + } + + public bool GetTempFlag(int flag) => ((LookupCompactInfo()?.m_TempFlags ?? 0) & flag) != 0; + + public void SetTempFlag(int flag, bool value) + { + var info = AcquireCompactInfo(); + + if (value) + { + info.m_TempFlags |= flag; + } + else + { + info.m_TempFlags &= ~flag; } - public virtual void OnMapChange() + if (info.m_TempFlags == 0) { + VerifyCompactInfo(); + } + } + + public bool GetSavedFlag(int flag) => ((LookupCompactInfo()?.m_SavedFlags ?? 0) & flag) != 0; + + public void SetSavedFlag(int flag, bool value) + { + var info = AcquireCompactInfo(); + + if (value) + { + info.m_SavedFlags |= flag; + } + else + { + info.m_SavedFlags &= ~flag; } - public virtual void OnRemoved(IEntity parent) + if (info.m_SavedFlags == 0) { + VerifyCompactInfo(); } + } - public virtual void OnAdded(IEntity parent) + public virtual void BeforeSerialize() + { + } + + public virtual void Deserialize(IGenericReader reader) + { + var version = reader.ReadInt(); + + SetLastMoved(); + + switch (version) { - } + case 9: + case 8: + case 7: + case 6: + { + var flags = (SaveFlag)reader.ReadInt(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) - { - if (setIf) - { - flags |= toSet; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; - - public IPooledEnumerable GetObjectsInRange(int range) - { - var map = m_Map; - - return map == null - ? Map.NullEnumerable.Instance - : map.GetObjectsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); - } - - public IPooledEnumerable GetItemsInRange(int range) - { - var map = m_Map; - - return map?.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? Map.NullEnumerable.Instance; - } - - public IPooledEnumerable GetMobilesInRange(int range) - { - var map = m_Map; - - return map?.GetMobilesInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? Map.NullEnumerable.Instance; - } - - public IPooledEnumerable GetClientsInRange(int range) - { - var map = m_Map; - - return map.GetClientsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? Map.NullEnumerable.Instance; - } - - public bool GetTempFlag(int flag) => ((LookupCompactInfo()?.m_TempFlags ?? 0) & flag) != 0; - - public void SetTempFlag(int flag, bool value) - { - var info = AcquireCompactInfo(); - - if (value) - { - info.m_TempFlags |= flag; - } - else - { - info.m_TempFlags &= ~flag; - } - - if (info.m_TempFlags == 0) - { - VerifyCompactInfo(); - } - } - - public bool GetSavedFlag(int flag) => ((LookupCompactInfo()?.m_SavedFlags ?? 0) & flag) != 0; - - public void SetSavedFlag(int flag, bool value) - { - var info = AcquireCompactInfo(); - - if (value) - { - info.m_SavedFlags |= flag; - } - else - { - info.m_SavedFlags &= ~flag; - } - - if (info.m_SavedFlags == 0) - { - VerifyCompactInfo(); - } - } - - public virtual void BeforeSerialize() - { - } - - public virtual void Deserialize(IGenericReader reader) - { - var version = reader.ReadInt(); - - SetLastMoved(); - - switch (version) - { - case 9: - case 8: - case 7: - case 6: + if (version < 7) { - var flags = (SaveFlag)reader.ReadInt(); - - if (version < 7) - { - LastMoved = reader.ReadDeltaTime(); - } - else - { - var minutes = reader.ReadEncodedInt(); - - try - { - LastMoved = Core.Now - TimeSpan.FromMinutes(minutes); - } - catch - { - LastMoved = Core.Now; - } - } - - if (GetSaveFlag(flags, SaveFlag.Direction)) - { - m_Direction = (Direction)reader.ReadByte(); - } - - if (GetSaveFlag(flags, SaveFlag.Bounce)) - { - AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); - } - - if (GetSaveFlag(flags, SaveFlag.LootType)) - { - m_LootType = (LootType)reader.ReadByte(); - } - - int x = 0, y = 0, z = 0; - - if (GetSaveFlag(flags, SaveFlag.LocationFull)) - { - x = reader.ReadEncodedInt(); - y = reader.ReadEncodedInt(); - z = reader.ReadEncodedInt(); - } - else - { - if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) - { - x = reader.ReadByte(); - y = reader.ReadByte(); - } - else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) - { - x = reader.ReadShort(); - y = reader.ReadShort(); - } - - if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) - { - z = reader.ReadSByte(); - } - } - - m_Location = new Point3D(x, y, z); - - if (GetSaveFlag(flags, SaveFlag.ItemID)) - { - m_ItemID = reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.Hue)) - { - m_Hue = reader.ReadEncodedInt(); - } - - m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadEncodedInt() : 1; - - if (GetSaveFlag(flags, SaveFlag.Layer)) - { - m_Layer = (Layer)reader.ReadByte(); - } - - if (GetSaveFlag(flags, SaveFlag.Name)) - { - var name = reader.ReadString(); - - if (name != DefaultName) - { - AcquireCompactInfo().m_Name = name; - } - } - - if (GetSaveFlag(flags, SaveFlag.Parent)) - { - Serial parent = reader.ReadSerial(); - - if (parent.IsMobile) - { - m_Parent = World.FindMobile(parent); - } - else if (parent.IsItem) - { - m_Parent = World.FindItem(parent); - } - else - { - m_Parent = null; - } - - if (m_Parent == null && (parent.IsMobile || parent.IsItem)) - { - Delete(); - } - } - - if (GetSaveFlag(flags, SaveFlag.Items)) - { - var items = reader.ReadEntityList(); - - if (this is Container) - { - (this as Container).m_Items = items; - } - else - { - AcquireCompactInfo().m_Items = items; - } - } - - if (version < 8 || !GetSaveFlag(flags, SaveFlag.NullWeight)) - { - double weight; - - if (GetSaveFlag(flags, SaveFlag.IntWeight)) - { - weight = reader.ReadEncodedInt(); - } - else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) - { - weight = reader.ReadDouble(); - } - else if (GetSaveFlag(flags, SaveFlag.WeightIs0)) - { - weight = 0.0; - } - else - { - weight = 1.0; - } - - if (weight != DefaultWeight) - { - AcquireCompactInfo().m_Weight = weight; - } - } - - m_Map = GetSaveFlag(flags, SaveFlag.Map) ? reader.ReadMap() : Map.Internal; - - SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); - - SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); - - if (GetSaveFlag(flags, SaveFlag.Stackable)) - { - SetFlag(ImplFlag.Stackable, reader.ReadBool()); - } - - if (GetSaveFlag(flags, SaveFlag.ImplFlags)) - { - m_Flags = (ImplFlag)reader.ReadEncodedInt(); - } - - if (GetSaveFlag(flags, SaveFlag.InsuredFor)) - /*m_InsuredFor = */ - { - reader.ReadEntity(); - } - - if (GetSaveFlag(flags, SaveFlag.BlessedFor)) - { - AcquireCompactInfo().m_BlessedFor = reader.ReadEntity(); - } - - if (GetSaveFlag(flags, SaveFlag.HeldBy)) - { - AcquireCompactInfo().m_HeldBy = reader.ReadEntity(); - } - - if (GetSaveFlag(flags, SaveFlag.SavedFlags)) - { - AcquireCompactInfo().m_SavedFlags = reader.ReadEncodedInt(); - } - - if (m_Map != null && m_Parent == null) - { - m_Map.OnEnter(this); - } - - break; - } - case 5: - { - var flags = (SaveFlag)reader.ReadInt(); - LastMoved = reader.ReadDeltaTime(); + } + else + { + var minutes = reader.ReadEncodedInt(); - if (GetSaveFlag(flags, SaveFlag.Direction)) + try { - m_Direction = (Direction)reader.ReadByte(); + LastMoved = Core.Now - TimeSpan.FromMinutes(minutes); + } + catch + { + LastMoved = Core.Now; + } + } + + if (GetSaveFlag(flags, SaveFlag.Direction)) + { + m_Direction = (Direction)reader.ReadByte(); + } + + if (GetSaveFlag(flags, SaveFlag.Bounce)) + { + AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); + } + + if (GetSaveFlag(flags, SaveFlag.LootType)) + { + m_LootType = (LootType)reader.ReadByte(); + } + + int x = 0, y = 0, z = 0; + + if (GetSaveFlag(flags, SaveFlag.LocationFull)) + { + x = reader.ReadEncodedInt(); + y = reader.ReadEncodedInt(); + z = reader.ReadEncodedInt(); + } + else + { + if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) + { + x = reader.ReadByte(); + y = reader.ReadByte(); + } + else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) + { + x = reader.ReadShort(); + y = reader.ReadShort(); } - if (GetSaveFlag(flags, SaveFlag.Bounce)) + if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) { - AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); + z = reader.ReadSByte(); + } + } + + m_Location = new Point3D(x, y, z); + + if (GetSaveFlag(flags, SaveFlag.ItemID)) + { + m_ItemID = reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.Hue)) + { + m_Hue = reader.ReadEncodedInt(); + } + + m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadEncodedInt() : 1; + + if (GetSaveFlag(flags, SaveFlag.Layer)) + { + m_Layer = (Layer)reader.ReadByte(); + } + + if (GetSaveFlag(flags, SaveFlag.Name)) + { + var name = reader.ReadString(); + + if (name != DefaultName) + { + AcquireCompactInfo().m_Name = name; + } + } + + if (GetSaveFlag(flags, SaveFlag.Parent)) + { + Serial parent = reader.ReadSerial(); + + if (parent.IsMobile) + { + m_Parent = World.FindMobile(parent); + } + else if (parent.IsItem) + { + m_Parent = World.FindItem(parent); + } + else + { + m_Parent = null; } - if (GetSaveFlag(flags, SaveFlag.LootType)) + if (m_Parent == null && (parent.IsMobile || parent.IsItem)) { - m_LootType = (LootType)reader.ReadByte(); + Delete(); } + } - if (GetSaveFlag(flags, SaveFlag.LocationFull)) + if (GetSaveFlag(flags, SaveFlag.Items)) + { + var items = reader.ReadEntityList(); + + if (this is Container) { - m_Location = reader.ReadPoint3D(); + (this as Container).m_Items = items; } - - if (GetSaveFlag(flags, SaveFlag.ItemID)) + else { - m_ItemID = reader.ReadInt(); - } - - if (GetSaveFlag(flags, SaveFlag.Hue)) - { - m_Hue = reader.ReadInt(); - } - - m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadInt() : 1; - - if (GetSaveFlag(flags, SaveFlag.Layer)) - { - m_Layer = (Layer)reader.ReadByte(); - } - - if (GetSaveFlag(flags, SaveFlag.Name)) - { - var name = reader.ReadString(); - - if (name != DefaultName) - { - AcquireCompactInfo().m_Name = name; - } - } - - if (GetSaveFlag(flags, SaveFlag.Parent)) - { - Serial parent = reader.ReadSerial(); - - if (parent.IsMobile) - { - m_Parent = World.FindMobile(parent); - } - else if (parent.IsItem) - { - m_Parent = World.FindItem(parent); - } - else - { - m_Parent = null; - } - - if (m_Parent == null && (parent.IsMobile || parent.IsItem)) - { - Delete(); - } - } - - if (GetSaveFlag(flags, SaveFlag.Items)) - { - var items = reader.ReadEntityList(); - - if (this is Container cont) - { - cont.m_Items = items; - } - else - { - AcquireCompactInfo().m_Items = items; - } + AcquireCompactInfo().m_Items = items; } + } + if (version < 8 || !GetSaveFlag(flags, SaveFlag.NullWeight)) + { double weight; if (GetSaveFlag(flags, SaveFlag.IntWeight)) @@ -2898,66 +2739,107 @@ namespace Server { AcquireCompactInfo().m_Weight = weight; } - - if (GetSaveFlag(flags, SaveFlag.Map)) - { - m_Map = reader.ReadMap(); - } - else - { - m_Map = Map.Internal; - } - - SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); - SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); - - if (GetSaveFlag(flags, SaveFlag.Stackable)) - { - SetFlag(ImplFlag.Stackable, reader.ReadBool()); - } - - if (m_Map != null && m_Parent == null) - { - m_Map.OnEnter(this); - } - - break; } - case 4: // Just removed variables - case 3: + + m_Map = GetSaveFlag(flags, SaveFlag.Map) ? reader.ReadMap() : Map.Internal; + + SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); + + SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); + + if (GetSaveFlag(flags, SaveFlag.Stackable)) { - m_Direction = (Direction)reader.ReadInt(); - - goto case 2; + SetFlag(ImplFlag.Stackable, reader.ReadBool()); } - case 2: + + if (GetSaveFlag(flags, SaveFlag.ImplFlags)) + { + m_Flags = (ImplFlag)reader.ReadEncodedInt(); + } + + if (GetSaveFlag(flags, SaveFlag.InsuredFor)) + /*m_InsuredFor = */ + { + reader.ReadEntity(); + } + + if (GetSaveFlag(flags, SaveFlag.BlessedFor)) + { + AcquireCompactInfo().m_BlessedFor = reader.ReadEntity(); + } + + if (GetSaveFlag(flags, SaveFlag.HeldBy)) + { + AcquireCompactInfo().m_HeldBy = reader.ReadEntity(); + } + + if (GetSaveFlag(flags, SaveFlag.SavedFlags)) + { + AcquireCompactInfo().m_SavedFlags = reader.ReadEncodedInt(); + } + + if (m_Map != null && m_Parent == null) + { + m_Map.OnEnter(this); + } + + break; + } + case 5: + { + var flags = (SaveFlag)reader.ReadInt(); + + LastMoved = reader.ReadDeltaTime(); + + if (GetSaveFlag(flags, SaveFlag.Direction)) + { + m_Direction = (Direction)reader.ReadByte(); + } + + if (GetSaveFlag(flags, SaveFlag.Bounce)) { AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); - LastMoved = reader.ReadDeltaTime(); - - goto case 1; } - case 1: + + if (GetSaveFlag(flags, SaveFlag.LootType)) { - m_LootType = (LootType)reader.ReadByte(); // m_Newbied = reader.ReadBool(); - - goto case 0; + m_LootType = (LootType)reader.ReadByte(); } - case 0: + + if (GetSaveFlag(flags, SaveFlag.LocationFull)) { m_Location = reader.ReadPoint3D(); - m_ItemID = reader.ReadInt(); - m_Hue = reader.ReadInt(); - m_Amount = reader.ReadInt(); - m_Layer = (Layer)reader.ReadByte(); + } + if (GetSaveFlag(flags, SaveFlag.ItemID)) + { + m_ItemID = reader.ReadInt(); + } + + if (GetSaveFlag(flags, SaveFlag.Hue)) + { + m_Hue = reader.ReadInt(); + } + + m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadInt() : 1; + + if (GetSaveFlag(flags, SaveFlag.Layer)) + { + m_Layer = (Layer)reader.ReadByte(); + } + + if (GetSaveFlag(flags, SaveFlag.Name)) + { var name = reader.ReadString(); if (name != DefaultName) { AcquireCompactInfo().m_Name = name; } + } + if (GetSaveFlag(flags, SaveFlag.Parent)) + { Serial parent = reader.ReadSerial(); if (parent.IsMobile) @@ -2977,1380 +2859,1497 @@ namespace Server { Delete(); } - - var count = reader.ReadInt(); - - if (count > 0) - { - var items = new List(count); - - for (var i = 0; i < count; ++i) - { - var item = reader.ReadEntity(); - - if (item != null) - { - items.Add(item); - } - } - - if (this is Container cont) - { - cont.m_Items = items; - } - else - { - AcquireCompactInfo().m_Items = items; - } - } - - var weight = reader.ReadDouble(); - - if (weight != DefaultWeight) - { - AcquireCompactInfo().m_Weight = weight; - } - - if (version <= 3) - { - reader.ReadInt(); - reader.ReadInt(); - reader.ReadInt(); - } - - m_Map = reader.ReadMap(); - SetFlag(ImplFlag.Visible, reader.ReadBool()); - SetFlag(ImplFlag.Movable, reader.ReadBool()); - - if (version <= 3) - /*m_Deleted =*/ - { - reader.ReadBool(); - } - - Stackable = reader.ReadBool(); - - if (m_Map != null && m_Parent == null) - { - m_Map.OnEnter(this); - } - - break; } - } - if (HeldBy != null) - { - Timer.StartTimer(FixHolding_Sandbox); - } + if (GetSaveFlag(flags, SaveFlag.Items)) + { + var items = reader.ReadEntityList(); - // if (version < 9) - VerifyCompactInfo(); - } + if (this is Container cont) + { + cont.m_Items = items; + } + else + { + AcquireCompactInfo().m_Items = items; + } + } - private void FixHolding_Sandbox() - { - var heldBy = HeldBy; + double weight; - if (heldBy != null) - { - if (GetBounce() != null) - { - Bounce(heldBy); - } - else - { - heldBy.Holding = null; - heldBy.AddToBackpack(this); - ClearBounce(); - } - } - } + if (GetSaveFlag(flags, SaveFlag.IntWeight)) + { + weight = reader.ReadEncodedInt(); + } + else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) + { + weight = reader.ReadDouble(); + } + else if (GetSaveFlag(flags, SaveFlag.WeightIs0)) + { + weight = 0.0; + } + else + { + weight = 1.0; + } - public virtual int GetMaxUpdateRange() => 18; + if (weight != DefaultWeight) + { + AcquireCompactInfo().m_Weight = weight; + } - public virtual int GetUpdateRange(Mobile m) => 18; + if (GetSaveFlag(flags, SaveFlag.Map)) + { + m_Map = reader.ReadMap(); + } + else + { + m_Map = Map.Internal; + } - public virtual void SendInfoTo(NetState ns, ReadOnlySpan world = default) - { - SendWorldPacketTo(ns, world); - SendOPLPacketTo(ns); - } + SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); + SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); - public virtual void SendOPLPacketTo(NetState ns) - { - if (ObjectPropertyList.Enabled) - { - ns.SendOPLInfo(this); - } - } + if (GetSaveFlag(flags, SaveFlag.Stackable)) + { + SetFlag(ImplFlag.Stackable, reader.ReadBool()); + } - public virtual void SendWorldPacketTo(NetState ns, ReadOnlySpan world = default) - { - if (world != null) - { - ns?.Send(world); - return; - } + if (m_Map != null && m_Parent == null) + { + m_Map.OnEnter(this); + } - ns.SendWorldItem(this); - } - - public virtual int GetTotal(TotalType type) => 0; - - public virtual void UpdateTotal(Item sender, TotalType type, int delta) - { - if (!IsVirtualItem) - { - if (m_Parent is Item item) - { - item.UpdateTotal(sender, type, delta); - } - else if (m_Parent is Mobile mobile) - { - mobile.UpdateTotal(sender, type, delta); - } - else - { - HeldBy?.UpdateTotal(sender, type, delta); - } - } - } - - public virtual void UpdateTotals() - { - } - - public virtual void HandleInvalidTransfer(Mobile from) - { - // OSI sends 1074769, bug! - if (QuestItem) - { - // You can only drop quest items into the top-most level of your backpack - // while you still need them for your quest. - from.SendLocalizedMessage(1049343); - } - } - - public bool ParentsContain() where T : Item - { - var p = m_Parent; - - while (p is Item item) - { - if (item is T) - { - return true; - } - - if (item.m_Parent == null) - { break; } + case 4: // Just removed variables + case 3: + { + m_Direction = (Direction)reader.ReadInt(); - p = item.m_Parent; - } + goto case 2; + } + case 2: + { + AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); + LastMoved = reader.ReadDeltaTime(); - return false; + goto case 1; + } + case 1: + { + m_LootType = (LootType)reader.ReadByte(); // m_Newbied = reader.ReadBool(); + + goto case 0; + } + case 0: + { + m_Location = reader.ReadPoint3D(); + m_ItemID = reader.ReadInt(); + m_Hue = reader.ReadInt(); + m_Amount = reader.ReadInt(); + m_Layer = (Layer)reader.ReadByte(); + + var name = reader.ReadString(); + + if (name != DefaultName) + { + AcquireCompactInfo().m_Name = name; + } + + Serial parent = reader.ReadSerial(); + + if (parent.IsMobile) + { + m_Parent = World.FindMobile(parent); + } + else if (parent.IsItem) + { + m_Parent = World.FindItem(parent); + } + else + { + m_Parent = null; + } + + if (m_Parent == null && (parent.IsMobile || parent.IsItem)) + { + Delete(); + } + + var count = reader.ReadInt(); + + if (count > 0) + { + var items = new List(count); + + for (var i = 0; i < count; ++i) + { + var item = reader.ReadEntity(); + + if (item != null) + { + items.Add(item); + } + } + + if (this is Container cont) + { + cont.m_Items = items; + } + else + { + AcquireCompactInfo().m_Items = items; + } + } + + var weight = reader.ReadDouble(); + + if (weight != DefaultWeight) + { + AcquireCompactInfo().m_Weight = weight; + } + + if (version <= 3) + { + reader.ReadInt(); + reader.ReadInt(); + reader.ReadInt(); + } + + m_Map = reader.ReadMap(); + SetFlag(ImplFlag.Visible, reader.ReadBool()); + SetFlag(ImplFlag.Movable, reader.ReadBool()); + + if (version <= 3) + /*m_Deleted =*/ + { + reader.ReadBool(); + } + + Stackable = reader.ReadBool(); + + if (m_Map != null && m_Parent == null) + { + m_Map.OnEnter(this); + } + + break; + } } - public virtual void AddItem(Item item) + if (HeldBy != null) { - if (item?.Deleted != false || item.m_Parent == this) - { - return; - } + Timer.StartTimer(FixHolding_Sandbox); + } - if (item == this) - { - var customException = new InvalidOperationException("Adding item to itself"); - logger.Warning( - customException, - "Adding item to itself: ({Serial1} {Item1}).AddItem({Serial2} {Item2})", - Serial, - GetType().Name, - item.Serial, - item.GetType().Name - ); - return; - } + // if (version < 9) + VerifyCompactInfo(); + } - if (IsChildOf(item)) - { - var customException = new InvalidOperationException("Adding parent item to child"); - logger.Warning( - customException, - "Adding parent item to child: [{Serial1} {Item1}].AddItem( [{Serial2} {Item2}] )", - Serial, - GetType().Name, - item.Serial, - item.GetType().Name - ); - return; - } + private void FixHolding_Sandbox() + { + var heldBy = HeldBy; - if (item.m_Parent is Mobile parentMobile) + if (heldBy != null) + { + if (GetBounce() != null) { - parentMobile.RemoveItem(item); - } - else if (item.m_Parent is Item parentItem) - { - parentItem.RemoveItem(item); + Bounce(heldBy); } else { - item.SendRemovePacket(); + heldBy.Holding = null; + heldBy.AddToBackpack(this); + ClearBounce(); } - - item.Parent = this; - item.Map = m_Map; - - var items = AcquireItems(); - - items.Add(item); - - if (!item.IsVirtualItem) - { - UpdateTotal(item, TotalType.Gold, item.TotalGold); - UpdateTotal(item, TotalType.Items, item.TotalItems + 1); - UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); - } - - item.Delta(ItemDelta.Update); - - item.OnAdded(this); - OnItemAdded(item); } + } - public void Delta(ItemDelta flags) + public virtual int GetMaxUpdateRange() => 18; + + public virtual int GetUpdateRange(Mobile m) => 18; + + public virtual void SendInfoTo(NetState ns, ReadOnlySpan world = default) + { + SendWorldPacketTo(ns, world); + SendOPLPacketTo(ns); + } + + public virtual void SendOPLPacketTo(NetState ns) + { + if (ObjectPropertyList.Enabled) { - if (m_Map == null || m_Map == Map.Internal) - { - return; - } - - m_DeltaFlags |= flags; - - if (!GetFlag(ImplFlag.InQueue)) - { - SetFlag(ImplFlag.InQueue, true); - m_DeltaQueue.Enqueue(this); - } + ns.SendOPLInfo(this); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemDelta(ItemDelta flags) + public virtual void SendWorldPacketTo(NetState ns, ReadOnlySpan world = default) + { + if (world != null) { - m_DeltaFlags &= ~flags; + ns?.Send(world); + return; } - public static void ProcessDeltaQueue() - { - var limit = m_DeltaQueue.Count; + ns.SendWorldItem(this); + } - while (m_DeltaQueue.Count > 0 && --limit >= 0) - { - var item = m_DeltaQueue.Dequeue(); + public virtual int GetTotal(TotalType type) => 0; - if (item == null) - { - continue; - } - - item.SetFlag(ImplFlag.InQueue, false); - - try - { - item.ProcessDelta(); - } - catch (Exception ex) - { - logger.Debug(ex, "Process Delta Queue for {Item} failed", item); - } - } - - if (m_DeltaQueue.Count > 0) - { - logger.Warning( - "{Count} items left in delta queue after processing.", - m_DeltaQueue.Count - ); - } - } - - public virtual void OnDelete() - { - if (Spawner != null) - { - Spawner.Remove(this); - Spawner = null; - } - } - - public virtual void OnParentDeleted(IEntity parent) - { - Delete(); - } - - public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text) - { - if (m_Map == null) - { - return; - } - - var worldLoc = GetWorldLocation(); - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket(); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) - { - var length = OutgoingMessagePackets.CreateMessage( - buffer, Serial, m_ItemID, type, hue, 3, ascii, "ENU", Name, text - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - state.Send(buffer); - } - } - - eable.Free(); - } - - public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "") - { - if (m_Map == null) - { - return; - } - - var worldLoc = GetWorldLocation(); - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket(); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) - { - var length = OutgoingMessagePackets.CreateMessageLocalized( - buffer, Serial, m_ItemID, type, hue, 3, number, Name, args - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - state.Send(buffer); - } - } - - eable.Free(); - } - - public virtual void OnAfterDelete() - { - } - - public virtual void RemoveItem(Item item) - { - var items = LookupItems(); - - if (items.Remove(item)) - { - item.SendRemovePacket(); - - if (!item.IsVirtualItem) - { - UpdateTotal(item, TotalType.Gold, -item.TotalGold); - UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); - UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); - } - - item.Parent = null; - - item.OnRemoved(this); - OnItemRemoved(item); - } - } - - public virtual void OnAfterDuped(Item newItem) - { - } - - public virtual bool OnDragLift(Mobile from) => true; - - public virtual bool OnEquip(Mobile from) => true; - - protected virtual void OnAmountChange(int oldValue) - { - } - - public virtual void OnSpeech(SpeechEventArgs e) - { - } - - public virtual bool OnDroppedToMobile(Mobile from, Mobile target) - { - if (Nontransferable && from.Player) - { - HandleInvalidTransfer(from); - return false; - } - - return true; - } - - public virtual bool DropToMobile(Mobile from, Mobile target, Point3D p) => - !(Deleted || from.Deleted || target.Deleted) && from.Map == target.Map && from.Map != null && - target.Map != null && (from.AccessLevel >= AccessLevel.GameMaster || from.InRange(target.Location, 2)) && - from.CanSee(target) && from.InLOS(target) && from.OnDroppedItemToMobile(this, target) && - OnDroppedToMobile(from, target) && target.OnDragDrop(from, this); - - public virtual bool OnDroppedInto(Mobile from, Container target, Point3D p) - { - if (!from.OnDroppedItemInto(this, target, p)) - { - return false; - } - - if (Nontransferable && from.Player && target != from.Backpack) - { - HandleInvalidTransfer(from); - return false; - } - - return target.OnDragDropInto(from, this, p); - } - - public virtual bool OnDroppedOnto(Mobile from, Item target) - { - if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || - target.Map == null) - { - return false; - } - - if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) - { - return false; - } - - if (!from.CanSee(target) || !from.InLOS(target)) - { - return false; - } - - if (!target.IsAccessibleTo(from)) - { - return false; - } - - if (!from.OnDroppedItemOnto(this, target)) - { - return false; - } - - if (Nontransferable && from.Player && target != from.Backpack) - { - HandleInvalidTransfer(from); - return false; - } - - return target.OnDragDrop(from, this); - } - - public virtual bool DropToItem(Mobile from, Item target, Point3D p) - { - if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || - target.Map == null) - { - return false; - } - - if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) - { - return false; - } - - if (!from.CanSee(target) || !from.InLOS(target)) - { - return false; - } - - if (!target.IsAccessibleTo(from)) - { - return false; - } - - if (target.RootParent is Mobile mobile && !mobile.CheckNonlocalDrop(from, this, target)) - { - return false; - } - - if (!from.OnDroppedItemToItem(this, target, p)) - { - return false; - } - - if (target is Container container && p.m_X != -1 && p.m_Y != -1) - { - return OnDroppedInto(from, container, p); - } - - return OnDroppedOnto(from, target); - } - - public virtual bool OnDroppedToWorld(Mobile from, Point3D p) - { - if (Nontransferable && from.Player) - { - HandleInvalidTransfer(from); - return false; - } - - return true; - } - - public virtual int GetLiftSound(Mobile from) => 0x57; - - public virtual bool DropToWorld(Mobile from, Point3D p) - { - if (Deleted || from.Deleted || from.Map == null) - { - return false; - } - - if (!from.InRange(p, 2)) - { - return false; - } - - var map = from.Map; - - if (map == null) - { - return false; - } - - int x = p.m_X, y = p.m_Y; - var z = int.MinValue; - - var maxZ = from.Z + 16; - - var landTile = map.Tiles.GetLandTile(x, y); - var landFlags = TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags; - - map.GetAverageZ(x, y, out var landZ, out var landAvg, out _); - - if (!landTile.Ignored && (landFlags & TileFlag.Impassable) == 0) - { - if (landAvg <= maxZ) - { - z = landAvg; - } - } - - var tiles = map.Tiles.GetStaticTiles(x, y, true); - - for (var i = 0; i < tiles.Length; ++i) - { - var tile = tiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - if (!id.Surface) - { - continue; - } - - var top = tile.Z + id.CalcHeight; - - if (top > maxZ || top < z) - { - continue; - } - - z = top; - } - - var eable = map.GetItemsInRange(p, 0); - - var items = new List(); - foreach (var item in eable) - { - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) - { - continue; - } - - var id = item.ItemData; - - if (id.Surface) - { - var top = item.Z + id.CalcHeight; - if (top <= maxZ && top >= z) - { - z = top; - } - } - - items.Add(item); - } - - eable.Free(); - - if (z == int.MinValue) - { - return false; - } - - if (z > maxZ) - { - return false; - } - - m_OpenSlots = (1 << 20) - 1; - - var surfaceZ = z; - - for (var i = 0; i < tiles.Length; ++i) - { - var tile = tiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - var checkZ = tile.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - { - ++checkTop; - } - - var zStart = Math.Max(checkZ - z, 0); - var zEnd = Math.Min(checkTop - z, 19); - - if (zStart >= 20 || zEnd < 0) - { - continue; - } - - var bitCount = zEnd - zStart; - - m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); - } - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - var id = item.ItemData; - - var checkZ = item.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - { - ++checkTop; - } - - var zStart = Math.Max(checkZ - z, 0); - var zEnd = Math.Min(checkTop - z, 19); - - if (zStart >= 20 || zEnd < 0) - { - continue; - } - - var bitCount = zEnd - zStart; - - m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); - } - - var height = ItemData.Height; - - if (height == 0) - { - ++height; - } - - if (height > 30) - { - height = 30; - } - - var match = (1 << height) - 1; - var okay = false; - - for (var i = 0; i < 20; ++i) - { - if (i + height > 20) - { - match >>= 1; - } - - okay = ((m_OpenSlots >> i) & match) == match; - - if (okay) - { - z += i; - break; - } - } - - if (!okay) - { - return false; - } - - height = ItemData.Height; - - if (height == 0) - { - ++height; - } - - if (landAvg > z && z + height > landZ) - { - return false; - } - - if ((landFlags & TileFlag.Impassable) != 0 && landAvg > surfaceZ && z + height > landZ) - { - return false; - } - - for (var i = 0; i < tiles.Length; ++i) - { - var tile = tiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - var checkZ = tile.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop > z && z + height > checkZ) - { - return false; - } - - if ((id.Surface || id.Impassable) && checkTop > surfaceZ && z + height > checkZ) - { - return false; - } - } - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - var id = item.ItemData; - - // int checkZ = item.Z; - // int checkTop = checkZ + id.CalcHeight; - - if (item.Z + id.CalcHeight > z && z + height > item.Z) - { - return false; - } - } - - p = new Point3D(x, y, z); - - if (!from.InLOS(new Point3D(x, y, z + 1))) - { - return false; - } - - if (!from.OnDroppedItemToWorld(this, p)) - { - return false; - } - - if (!OnDroppedToWorld(from, p)) - { - return false; - } - - var soundID = GetDropSound(); - - MoveToWorld(p, from.Map); - - from.SendSound(soundID == -1 ? 0x42 : soundID, GetWorldLocation()); - - return true; - } - - public void SendRemovePacket() => SendRemovePacket(GetWorldLocation()); - - public void SendRemovePacket(Point3D worldLoc) - { - if (Deleted || m_Map == null) - { - return; - } - - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.InRange(worldLoc, GetUpdateRange(m))) - { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - state.Send(removeEntity); - } - } - - eable.Free(); - } - - public virtual int GetDropSound() => -1; - - public Point3D GetWorldLocation() - { - var root = RootParent; - - if (root == null) - { - return m_Location; - } - - return root.Location; - - // return root == null ? m_Location : new Point3D( (IPoint3D) root ); - } - - public Point3D GetSurfaceTop() - { - var root = RootParent; - - if (root == null) - { - return new Point3D( - m_Location.m_X, - m_Location.m_Y, - m_Location.m_Z + (ItemData.Surface ? ItemData.CalcHeight : 0) - ); - } - - return root.Location; - } - - public Point3D GetWorldTop() => RootParent?.Location ?? - new Point3D(m_Location.m_X, m_Location.m_Y, m_Location.m_Z + ItemData.CalcHeight); - - public void SendLocalizedMessageTo(Mobile to, int number, string args = "") - { - if (Deleted || !to.CanSee(this)) - { - return; - } - - to.NetState.SendMessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", args); - } - - public void SendLocalizedMessageTo(Mobile to, int number, AffixType affixType, string affix, string args) - { - if (Deleted || !to.CanSee(this)) - { - return; - } - - to.NetState.SendMessageLocalizedAffix( - Serial, - ItemID, - MessageType.Regular, - 0x3B2, - 3, - number, - "", - affixType, - affix, - args - ); - } - - public virtual void OnSnoop(Mobile from) - { - } - - public SecureTradeContainer GetSecureTradeCont() - { - object p = this; - - while (p is Item item) - { - if (item is SecureTradeContainer container) - { - return container; - } - - p = item.m_Parent; - } - - return null; - } - - public virtual void OnItemAdded(Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.OnSubItemAdded(item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnSubItemAdded(item); - } - } - - public virtual void OnItemRemoved(Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.OnSubItemRemoved(item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnSubItemRemoved(item); - } - } - - public virtual void OnSubItemAdded(Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.OnSubItemAdded(item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnSubItemAdded(item); - } - } - - public virtual void OnSubItemRemoved(Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.OnSubItemRemoved(item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnSubItemRemoved(item); - } - } - - public virtual void OnItemBounceCleared(Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.OnSubItemBounceCleared(item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnSubItemBounceCleared(item); - } - } - - public virtual void OnSubItemBounceCleared(Item item) - { - if (m_Parent is Item parentItem) - { - parentItem.OnSubItemBounceCleared(item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnSubItemBounceCleared(item); - } - } - - public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => - m_Parent switch - { - Item item => item.CheckTarget(from, targ, targeted), - Mobile mobile => mobile.CheckTarget(from, targ, targeted), - _ => true - }; - - public virtual bool IsAccessibleTo(Mobile check) + public virtual void UpdateTotal(Item sender, TotalType type, int delta) + { + if (!IsVirtualItem) { if (m_Parent is Item item) { - return item.IsAccessibleTo(check); + item.UpdateTotal(sender, type, delta); + } + else if (m_Parent is Mobile mobile) + { + mobile.UpdateTotal(sender, type, delta); + } + else + { + HeldBy?.UpdateTotal(sender, type, delta); + } + } + } + + public virtual void UpdateTotals() + { + } + + public virtual void HandleInvalidTransfer(Mobile from) + { + // OSI sends 1074769, bug! + if (QuestItem) + { + // You can only drop quest items into the top-most level of your backpack + // while you still need them for your quest. + from.SendLocalizedMessage(1049343); + } + } + + public bool ParentsContain() where T : Item + { + var p = m_Parent; + + while (p is Item item) + { + if (item is T) + { + return true; } - var reg = Region.Find(GetWorldLocation(), m_Map); + if (item.m_Parent == null) + { + break; + } - return reg.CheckAccessibility(this, check); - - /*SecureTradeContainer cont = GetSecureTradeCont(); - - if (cont != null && !cont.IsChildOf( check )) - return false; - - return true;*/ + p = item.m_Parent; } - public bool IsChildOf(IEntity o) => IsChildOf(o, false); + return false; + } - public bool IsChildOf(IEntity o, bool allowNull) + public virtual void AddItem(Item item) + { + if (item?.Deleted != false || item.m_Parent == this) { - var p = m_Parent; + return; + } - if ((p == null || o == null) && !allowNull) + if (item == this) + { + var customException = new InvalidOperationException("Adding item to itself"); + logger.Warning( + customException, + "Adding item to itself: ({Serial1} {Item1}).AddItem({Serial2} {Item2})", + Serial, + GetType().Name, + item.Serial, + item.GetType().Name + ); + return; + } + + if (IsChildOf(item)) + { + var customException = new InvalidOperationException("Adding parent item to child"); + logger.Warning( + customException, + "Adding parent item to child: [{Serial1} {Item1}].AddItem( [{Serial2} {Item2}] )", + Serial, + GetType().Name, + item.Serial, + item.GetType().Name + ); + return; + } + + if (item.m_Parent is Mobile parentMobile) + { + parentMobile.RemoveItem(item); + } + else if (item.m_Parent is Item parentItem) + { + parentItem.RemoveItem(item); + } + else + { + item.SendRemovePacket(); + } + + item.Parent = this; + item.Map = m_Map; + + var items = AcquireItems(); + + items.Add(item); + + if (!item.IsVirtualItem) + { + UpdateTotal(item, TotalType.Gold, item.TotalGold); + UpdateTotal(item, TotalType.Items, item.TotalItems + 1); + UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); + } + + item.Delta(ItemDelta.Update); + + item.OnAdded(this); + OnItemAdded(item); + } + + public void Delta(ItemDelta flags) + { + if (m_Map == null || m_Map == Map.Internal) + { + return; + } + + m_DeltaFlags |= flags; + + if (!GetFlag(ImplFlag.InQueue)) + { + SetFlag(ImplFlag.InQueue, true); + m_DeltaQueue.Enqueue(this); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RemDelta(ItemDelta flags) + { + m_DeltaFlags &= ~flags; + } + + public static void ProcessDeltaQueue() + { + var limit = m_DeltaQueue.Count; + + while (m_DeltaQueue.Count > 0 && --limit >= 0) + { + var item = m_DeltaQueue.Dequeue(); + + if (item == null) + { + continue; + } + + item.SetFlag(ImplFlag.InQueue, false); + + try + { + item.ProcessDelta(); + } + catch (Exception ex) + { + logger.Debug(ex, "Process Delta Queue for {Item} failed", item); + } + } + + if (m_DeltaQueue.Count > 0) + { + logger.Warning( + "{Count} items left in delta queue after processing.", + m_DeltaQueue.Count + ); + } + } + + public virtual void OnDelete() + { + if (Spawner != null) + { + Spawner.Remove(this); + Spawner = null; + } + } + + public virtual void OnParentDeleted(IEntity parent) + { + Delete(); + } + + public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text) + { + if (m_Map == null) + { + return; + } + + var worldLoc = GetWorldLocation(); + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket(); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) + { + var length = OutgoingMessagePackets.CreateMessage( + buffer, Serial, m_ItemID, type, hue, 3, ascii, "ENU", Name, text + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + state.Send(buffer); + } + } + + eable.Free(); + } + + public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "") + { + if (m_Map == null) + { + return; + } + + var worldLoc = GetWorldLocation(); + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket(); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) + { + var length = OutgoingMessagePackets.CreateMessageLocalized( + buffer, Serial, m_ItemID, type, hue, 3, number, Name, args + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + state.Send(buffer); + } + } + + eable.Free(); + } + + public virtual void OnAfterDelete() + { + } + + public virtual void RemoveItem(Item item) + { + var items = LookupItems(); + + if (items.Remove(item)) + { + item.SendRemovePacket(); + + if (!item.IsVirtualItem) + { + UpdateTotal(item, TotalType.Gold, -item.TotalGold); + UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); + UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); + } + + item.Parent = null; + + item.OnRemoved(this); + OnItemRemoved(item); + } + } + + public virtual void OnAfterDuped(Item newItem) + { + } + + public virtual bool OnDragLift(Mobile from) => true; + + public virtual bool OnEquip(Mobile from) => true; + + protected virtual void OnAmountChange(int oldValue) + { + } + + public virtual void OnSpeech(SpeechEventArgs e) + { + } + + public virtual bool OnDroppedToMobile(Mobile from, Mobile target) + { + if (Nontransferable && from.Player) + { + HandleInvalidTransfer(from); + return false; + } + + return true; + } + + public virtual bool DropToMobile(Mobile from, Mobile target, Point3D p) => + !(Deleted || from.Deleted || target.Deleted) && from.Map == target.Map && from.Map != null && + target.Map != null && (from.AccessLevel >= AccessLevel.GameMaster || from.InRange(target.Location, 2)) && + from.CanSee(target) && from.InLOS(target) && from.OnDroppedItemToMobile(this, target) && + OnDroppedToMobile(from, target) && target.OnDragDrop(from, this); + + public virtual bool OnDroppedInto(Mobile from, Container target, Point3D p) + { + if (!from.OnDroppedItemInto(this, target, p)) + { + return false; + } + + if (Nontransferable && from.Player && target != from.Backpack) + { + HandleInvalidTransfer(from); + return false; + } + + return target.OnDragDropInto(from, this, p); + } + + public virtual bool OnDroppedOnto(Mobile from, Item target) + { + if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || + target.Map == null) + { + return false; + } + + if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) + { + return false; + } + + if (!from.CanSee(target) || !from.InLOS(target)) + { + return false; + } + + if (!target.IsAccessibleTo(from)) + { + return false; + } + + if (!from.OnDroppedItemOnto(this, target)) + { + return false; + } + + if (Nontransferable && from.Player && target != from.Backpack) + { + HandleInvalidTransfer(from); + return false; + } + + return target.OnDragDrop(from, this); + } + + public virtual bool DropToItem(Mobile from, Item target, Point3D p) + { + if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || + target.Map == null) + { + return false; + } + + if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) + { + return false; + } + + if (!from.CanSee(target) || !from.InLOS(target)) + { + return false; + } + + if (!target.IsAccessibleTo(from)) + { + return false; + } + + if (target.RootParent is Mobile mobile && !mobile.CheckNonlocalDrop(from, this, target)) + { + return false; + } + + if (!from.OnDroppedItemToItem(this, target, p)) + { + return false; + } + + if (target is Container container && p.m_X != -1 && p.m_Y != -1) + { + return OnDroppedInto(from, container, p); + } + + return OnDroppedOnto(from, target); + } + + public virtual bool OnDroppedToWorld(Mobile from, Point3D p) + { + if (Nontransferable && from.Player) + { + HandleInvalidTransfer(from); + return false; + } + + return true; + } + + public virtual int GetLiftSound(Mobile from) => 0x57; + + public virtual bool DropToWorld(Mobile from, Point3D p) + { + if (Deleted || from.Deleted || from.Map == null) + { + return false; + } + + if (!from.InRange(p, 2)) + { + return false; + } + + var map = from.Map; + + if (map == null) + { + return false; + } + + int x = p.m_X, y = p.m_Y; + var z = int.MinValue; + + var maxZ = from.Z + 16; + + var landTile = map.Tiles.GetLandTile(x, y); + var landFlags = TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags; + + map.GetAverageZ(x, y, out var landZ, out var landAvg, out _); + + if (!landTile.Ignored && (landFlags & TileFlag.Impassable) == 0) + { + if (landAvg <= maxZ) + { + z = landAvg; + } + } + + var tiles = map.Tiles.GetStaticTiles(x, y, true); + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + if (!id.Surface) + { + continue; + } + + var top = tile.Z + id.CalcHeight; + + if (top > maxZ || top < z) + { + continue; + } + + z = top; + } + + var eable = map.GetItemsInRange(p, 0); + + var items = new List(); + foreach (var item in eable) + { + if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) + { + continue; + } + + var id = item.ItemData; + + if (id.Surface) + { + var top = item.Z + id.CalcHeight; + if (top <= maxZ && top >= z) + { + z = top; + } + } + + items.Add(item); + } + + eable.Free(); + + if (z == int.MinValue) + { + return false; + } + + if (z > maxZ) + { + return false; + } + + m_OpenSlots = (1 << 20) - 1; + + var surfaceZ = z; + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + var checkZ = tile.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + { + ++checkTop; + } + + var zStart = Math.Max(checkZ - z, 0); + var zEnd = Math.Min(checkTop - z, 19); + + if (zStart >= 20 || zEnd < 0) + { + continue; + } + + var bitCount = zEnd - zStart; + + m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); + } + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + var id = item.ItemData; + + var checkZ = item.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + { + ++checkTop; + } + + var zStart = Math.Max(checkZ - z, 0); + var zEnd = Math.Min(checkTop - z, 19); + + if (zStart >= 20 || zEnd < 0) + { + continue; + } + + var bitCount = zEnd - zStart; + + m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); + } + + var height = ItemData.Height; + + if (height == 0) + { + ++height; + } + + if (height > 30) + { + height = 30; + } + + var match = (1 << height) - 1; + var okay = false; + + for (var i = 0; i < 20; ++i) + { + if (i + height > 20) + { + match >>= 1; + } + + okay = ((m_OpenSlots >> i) & match) == match; + + if (okay) + { + z += i; + break; + } + } + + if (!okay) + { + return false; + } + + height = ItemData.Height; + + if (height == 0) + { + ++height; + } + + if (landAvg > z && z + height > landZ) + { + return false; + } + + if ((landFlags & TileFlag.Impassable) != 0 && landAvg > surfaceZ && z + height > landZ) + { + return false; + } + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + var checkZ = tile.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop > z && z + height > checkZ) { return false; } + if ((id.Surface || id.Impassable) && checkTop > surfaceZ && z + height > checkZ) + { + return false; + } + } + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + var id = item.ItemData; + + // int checkZ = item.Z; + // int checkTop = checkZ + id.CalcHeight; + + if (item.Z + id.CalcHeight > z && z + height > item.Z) + { + return false; + } + } + + p = new Point3D(x, y, z); + + if (!from.InLOS(new Point3D(x, y, z + 1))) + { + return false; + } + + if (!from.OnDroppedItemToWorld(this, p)) + { + return false; + } + + if (!OnDroppedToWorld(from, p)) + { + return false; + } + + var soundID = GetDropSound(); + + MoveToWorld(p, from.Map); + + from.SendSound(soundID == -1 ? 0x42 : soundID, GetWorldLocation()); + + return true; + } + + public void SendRemovePacket() => SendRemovePacket(GetWorldLocation()); + + public void SendRemovePacket(Point3D worldLoc) + { + if (Deleted || m_Map == null) + { + return; + } + + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.InRange(worldLoc, GetUpdateRange(m))) + { + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + state.Send(removeEntity); + } + } + + eable.Free(); + } + + public virtual int GetDropSound() => -1; + + public Point3D GetWorldLocation() + { + var root = RootParent; + + if (root == null) + { + return m_Location; + } + + return root.Location; + + // return root == null ? m_Location : new Point3D( (IPoint3D) root ); + } + + public Point3D GetSurfaceTop() + { + var root = RootParent; + + if (root == null) + { + return new Point3D( + m_Location.m_X, + m_Location.m_Y, + m_Location.m_Z + (ItemData.Surface ? ItemData.CalcHeight : 0) + ); + } + + return root.Location; + } + + public Point3D GetWorldTop() => RootParent?.Location ?? + new Point3D(m_Location.m_X, m_Location.m_Y, m_Location.m_Z + ItemData.CalcHeight); + + public void SendLocalizedMessageTo(Mobile to, int number, string args = "") + { + if (Deleted || !to.CanSee(this)) + { + return; + } + + to.NetState.SendMessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", args); + } + + public void SendLocalizedMessageTo(Mobile to, int number, AffixType affixType, string affix, string args) + { + if (Deleted || !to.CanSee(this)) + { + return; + } + + to.NetState.SendMessageLocalizedAffix( + Serial, + ItemID, + MessageType.Regular, + 0x3B2, + 3, + number, + "", + affixType, + affix, + args + ); + } + + public virtual void OnSnoop(Mobile from) + { + } + + public SecureTradeContainer GetSecureTradeCont() + { + object p = this; + + while (p is Item item) + { + if (item is SecureTradeContainer container) + { + return container; + } + + p = item.m_Parent; + } + + return null; + } + + public virtual void OnItemAdded(Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnSubItemAdded(item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnSubItemAdded(item); + } + } + + public virtual void OnItemRemoved(Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnSubItemRemoved(item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnSubItemRemoved(item); + } + } + + public virtual void OnSubItemAdded(Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnSubItemAdded(item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnSubItemAdded(item); + } + } + + public virtual void OnSubItemRemoved(Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnSubItemRemoved(item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnSubItemRemoved(item); + } + } + + public virtual void OnItemBounceCleared(Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnSubItemBounceCleared(item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnSubItemBounceCleared(item); + } + } + + public virtual void OnSubItemBounceCleared(Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnSubItemBounceCleared(item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnSubItemBounceCleared(item); + } + } + + public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => + m_Parent switch + { + Item item => item.CheckTarget(from, targ, targeted), + Mobile mobile => mobile.CheckTarget(from, targ, targeted), + _ => true + }; + + public virtual bool IsAccessibleTo(Mobile check) + { + if (m_Parent is Item item) + { + return item.IsAccessibleTo(check); + } + + var reg = Region.Find(GetWorldLocation(), m_Map); + + return reg.CheckAccessibility(this, check); + + /*SecureTradeContainer cont = GetSecureTradeCont(); + + if (cont != null && !cont.IsChildOf( check )) + return false; + + return true;*/ + } + + public bool IsChildOf(IEntity o) => IsChildOf(o, false); + + public bool IsChildOf(IEntity o, bool allowNull) + { + var p = m_Parent; + + if ((p == null || o == null) && !allowNull) + { + return false; + } + + if (p == o) + { + return true; + } + + while (p is Item item) + { + if (item.m_Parent == null) + { + break; + } + + p = item.m_Parent; + if (p == o) { return true; } - - while (p is Item item) - { - if (item.m_Parent == null) - { - break; - } - - p = item.m_Parent; - - if (p == o) - { - return true; - } - } - - return false; } - public virtual void OnItemUsed(Mobile from, Item item) + return false; + } + + public virtual void OnItemUsed(Mobile from, Item item) + { + if (m_Parent is Item parentItem) { - if (m_Parent is Item parentItem) - { - parentItem.OnItemUsed(from, item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnItemUsed(from, item); - } + parentItem.OnItemUsed(from, item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnItemUsed(from, item); + } + } + + public bool CheckItemUse(Mobile from) => CheckItemUse(from, this); + + public virtual bool CheckItemUse(Mobile from, Item item) => + m_Parent switch + { + Item parentItem => parentItem.CheckItemUse(from, item), + Mobile parentMobile => parentMobile.CheckItemUse(from, item), + _ => true + }; + + public virtual void OnItemLifted(Mobile from, Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnItemLifted(from, item); + } + else if (m_Parent is Mobile parentMobile) + { + parentMobile.OnItemLifted(from, item); + } + } + + public bool CheckLift(Mobile from) + { + var reject = LRReason.Inspecific; + + return CheckLift(from, this, ref reject); + } + + public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => + m_Parent switch + { + Item parentItem => parentItem.CheckLift(from, item, ref reject), + Mobile parentMobile => parentMobile.CheckLift(from, item, ref reject), + _ => true + }; + + public virtual void OnSingleClickContained(Mobile from, Item item) + { + if (m_Parent is Item parentItem) + { + parentItem.OnSingleClickContained(from, item); + } + } + + public virtual void OnAosSingleClick(Mobile from) + { + var opl = PropertyList; + + if (opl.Header > 0) + { + from.NetState.SendMessageLocalized( + Serial, + m_ItemID, + MessageType.Label, + 0x3B2, + 3, + opl.Header, + Name, + opl.HeaderArgs + ); + } + } + + public virtual void OnSingleClick(Mobile from) + { + if (Deleted || !from.CanSee(this)) + { + return; } - public bool CheckItemUse(Mobile from) => CheckItemUse(from, this); - - public virtual bool CheckItemUse(Mobile from, Item item) => - m_Parent switch - { - Item parentItem => parentItem.CheckItemUse(from, item), - Mobile parentMobile => parentMobile.CheckItemUse(from, item), - _ => true - }; - - public virtual void OnItemLifted(Mobile from, Item item) + if (DisplayLootType) { - if (m_Parent is Item parentItem) - { - parentItem.OnItemLifted(from, item); - } - else if (m_Parent is Mobile parentMobile) - { - parentMobile.OnItemLifted(from, item); - } + LabelLootTypeTo(from); } - public bool CheckLift(Mobile from) - { - var reject = LRReason.Inspecific; + var ns = from.NetState; - return CheckLift(from, this, ref reject); + if (ns.CannotSendPackets()) + { + return; } - public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => - m_Parent switch - { - Item parentItem => parentItem.CheckLift(from, item, ref reject), - Mobile parentMobile => parentMobile.CheckLift(from, item, ref reject), - _ => true - }; - - public virtual void OnSingleClickContained(Mobile from, Item item) + if (Name == null) { - if (m_Parent is Item parentItem) + if (m_Amount <= 1) { - parentItem.OnSingleClickContained(from, item); - } - } - - public virtual void OnAosSingleClick(Mobile from) - { - var opl = PropertyList; - - if (opl.Header > 0) - { - from.NetState.SendMessageLocalized( - Serial, - m_ItemID, - MessageType.Label, - 0x3B2, - 3, - opl.Header, - Name, - opl.HeaderArgs - ); - } - } - - public virtual void OnSingleClick(Mobile from) - { - if (Deleted || !from.CanSee(this)) - { - return; - } - - if (DisplayLootType) - { - LabelLootTypeTo(from); - } - - var ns = from.NetState; - - if (ns.CannotSendPackets()) - { - return; - } - - if (Name == null) - { - if (m_Amount <= 1) - { - ns.SendMessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, LabelNumber); - } - else - { - ns.SendMessageLocalizedAffix( - Serial, - m_ItemID, - MessageType.Label, - 0x3B2, - 3, - LabelNumber, - "", - AffixType.Append, - $" : {m_Amount}" - ); - } + ns.SendMessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, LabelNumber); } else { - ns.SendMessage( + ns.SendMessageLocalizedAffix( Serial, m_ItemID, MessageType.Label, 0x3B2, 3, - false, - "ENU", + LabelNumber, "", - $"{Name}{(m_Amount > 1 ? $" : {m_Amount}" : "")}" + AffixType.Append, + $" : {m_Amount}" ); } } - - public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem) + else { - ScissorHelper(from, newItem, amountPerOldItem, true); + ns.SendMessage( + Serial, + m_ItemID, + MessageType.Label, + 0x3B2, + 3, + false, + "ENU", + "", + $"{Name}{(m_Amount > 1 ? $" : {m_Amount}" : "")}" + ); } + } - public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem, bool carryHue) + public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem) + { + ScissorHelper(from, newItem, amountPerOldItem, true); + } + + public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem, bool carryHue) + { + // let's not go over 60000 + var amount = Math.Min(Amount, 60000 / amountPerOldItem); + + Amount -= amount; + + var ourHue = Hue; + var thisMap = Map; + var thisParent = m_Parent; + var worldLoc = GetWorldLocation(); + var type = LootType; + + if (Amount == 0) { - // let's not go over 60000 - var amount = Math.Min(Amount, 60000 / amountPerOldItem); - - Amount -= amount; - - var ourHue = Hue; - var thisMap = Map; - var thisParent = m_Parent; - var worldLoc = GetWorldLocation(); - var type = LootType; - - if (Amount == 0) - { - Delete(); - } - - newItem.Amount = amount * amountPerOldItem; - - if (carryHue) - { - newItem.Hue = ourHue; - } - - if (ScissorCopyLootType) - { - newItem.LootType = type; - } - - if ((thisParent as Container)?.TryDropItem(from, newItem, false) != true) - { - newItem.MoveToWorld(worldLoc, thisMap); - } - } - - public virtual void Consume() - { - Consume(1); - } - - public virtual void Consume(int amount) - { - Amount -= amount; - - if (Amount <= 0) - { - Delete(); - } - } - - public virtual void ReplaceWith(Item newItem) - { - if (m_Parent is Container container) - { - container.AddItem(newItem); - newItem.Location = m_Location; - } - else - { - newItem.MoveToWorld(GetWorldLocation(), m_Map); - } - Delete(); } - public virtual bool CheckBlessed(Mobile m) => - m_LootType == LootType.Blessed || Mobile.InsuranceEnabled && Insured || m != null && m == BlessedFor; + newItem.Amount = amount * amountPerOldItem; - public virtual bool CheckNewbied() => m_LootType == LootType.Newbied; - - public virtual bool IsStandardLoot() => - (!Mobile.InsuranceEnabled || !Insured) && BlessedFor == null && m_LootType == LootType.Regular; - - public override string ToString() => $"{Serial} \"{GetType().Name}\""; - - public virtual void OnSectorActivate() + if (carryHue) { + newItem.Hue = ourHue; } - public virtual void OnSectorDeactivate() + if (ScissorCopyLootType) { + newItem.LootType = type; } - public virtual void OnLocationChange(Point3D oldLocation) + if ((thisParent as Container)?.TryDropItem(from, newItem, false) != true) { - } - - public virtual void OnDoubleClick(Mobile from) - { - } - - public virtual void OnDoubleClickOutOfRange(Mobile from) - { - } - - public virtual void OnDoubleClickCantSee(Mobile from) - { - } - - public virtual void OnDoubleClickDead(Mobile from) - { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. - } - - public virtual void OnDoubleClickNotAccessible(Mobile from) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - public virtual void OnDoubleClickSecureTrade(Mobile from) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - [Flags] - private enum ImplFlag : byte - { - None = 0x00, - Visible = 0x01, - Movable = 0x02, - Deleted = 0x04, - Stackable = 0x08, - InQueue = 0x10, - Insured = 0x20, - PaidInsurance = 0x40, - QuestItem = 0x80 - } - - private class CompactInfo - { - public Mobile m_BlessedFor; - public BounceInfo m_Bounce; - - public Mobile m_HeldBy; - - public List m_Items; - public string m_Name; - public int m_SavedFlags; - - public ISpawner m_Spawner; - - public int m_TempFlags; - - public double m_Weight = -1; - } - - [Flags] - private enum SaveFlag : uint - { - None = 0x00000000, - Direction = 0x00000001, - Bounce = 0x00000002, - LootType = 0x00000004, - LocationFull = 0x00000008, - ItemID = 0x00000010, - Hue = 0x00000020, - Amount = 0x00000040, - Layer = 0x00000080, - Name = 0x00000100, - Parent = 0x00000200, - Items = 0x00000400, - WeightNot1or0 = 0x00000800, - Map = 0x00001000, - Visible = 0x00002000, - Movable = 0x00004000, - Stackable = 0x00008000, - WeightIs0 = 0x00010000, - LocationSByteZ = 0x00020000, - LocationShortXY = 0x00040000, - LocationByteXY = 0x00080000, - ImplFlags = 0x00100000, - InsuredFor = 0x00200000, - BlessedFor = 0x00400000, - HeldBy = 0x00800000, - IntWeight = 0x01000000, - SavedFlags = 0x02000000, - NullWeight = 0x04000000 + newItem.MoveToWorld(worldLoc, thisMap); } } + + public virtual void Consume() + { + Consume(1); + } + + public virtual void Consume(int amount) + { + Amount -= amount; + + if (Amount <= 0) + { + Delete(); + } + } + + public virtual void ReplaceWith(Item newItem) + { + if (m_Parent is Container container) + { + container.AddItem(newItem); + newItem.Location = m_Location; + } + else + { + newItem.MoveToWorld(GetWorldLocation(), m_Map); + } + + Delete(); + } + + public virtual bool CheckBlessed(Mobile m) => + m_LootType == LootType.Blessed || Mobile.InsuranceEnabled && Insured || m != null && m == BlessedFor; + + public virtual bool CheckNewbied() => m_LootType == LootType.Newbied; + + public virtual bool IsStandardLoot() => + (!Mobile.InsuranceEnabled || !Insured) && BlessedFor == null && m_LootType == LootType.Regular; + + public override string ToString() => $"{Serial} \"{GetType().Name}\""; + + public virtual void OnSectorActivate() + { + } + + public virtual void OnSectorDeactivate() + { + } + + public virtual void OnLocationChange(Point3D oldLocation) + { + } + + public virtual void OnDoubleClick(Mobile from) + { + } + + public virtual void OnDoubleClickOutOfRange(Mobile from) + { + } + + public virtual void OnDoubleClickCantSee(Mobile from) + { + } + + public virtual void OnDoubleClickDead(Mobile from) + { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. + } + + public virtual void OnDoubleClickNotAccessible(Mobile from) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + public virtual void OnDoubleClickSecureTrade(Mobile from) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + [Flags] + private enum ImplFlag : byte + { + None = 0x00, + Visible = 0x01, + Movable = 0x02, + Deleted = 0x04, + Stackable = 0x08, + InQueue = 0x10, + Insured = 0x20, + PaidInsurance = 0x40, + QuestItem = 0x80 + } + + private class CompactInfo + { + public Mobile m_BlessedFor; + public BounceInfo m_Bounce; + + public Mobile m_HeldBy; + + public List m_Items; + public string m_Name; + public int m_SavedFlags; + + public ISpawner m_Spawner; + + public int m_TempFlags; + + public double m_Weight = -1; + } + + [Flags] + private enum SaveFlag : uint + { + None = 0x00000000, + Direction = 0x00000001, + Bounce = 0x00000002, + LootType = 0x00000004, + LocationFull = 0x00000008, + ItemID = 0x00000010, + Hue = 0x00000020, + Amount = 0x00000040, + Layer = 0x00000080, + Name = 0x00000100, + Parent = 0x00000200, + Items = 0x00000400, + WeightNot1or0 = 0x00000800, + Map = 0x00001000, + Visible = 0x00002000, + Movable = 0x00004000, + Stackable = 0x00008000, + WeightIs0 = 0x00010000, + LocationSByteZ = 0x00020000, + LocationShortXY = 0x00040000, + LocationByteXY = 0x00080000, + ImplFlags = 0x00100000, + InsuredFor = 0x00200000, + BlessedFor = 0x00400000, + HeldBy = 0x00800000, + IntWeight = 0x01000000, + SavedFlags = 0x02000000, + NullWeight = 0x04000000 + } } diff --git a/Projects/Server/Items/Layer.cs b/Projects/Server/Items/Layer.cs index 4758422f7..f0d22ee14 100644 --- a/Projects/Server/Items/Layer.cs +++ b/Projects/Server/Items/Layer.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Layer.cs * * * @@ -13,176 +13,175 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server +namespace Server; + +/// +/// Enumeration of item layer values. +/// +public enum Layer : byte { /// - /// Enumeration of item layer values. + /// Invalid layer. /// - public enum Layer : byte - { - /// - /// Invalid layer. - /// - Invalid = 0x00, + Invalid = 0x00, - /// - /// First valid layer. Equivalent to Layer.OneHanded. - /// - FirstValid = 0x01, + /// + /// First valid layer. Equivalent to Layer.OneHanded. + /// + FirstValid = 0x01, - /// - /// One handed weapon. - /// - OneHanded = 0x01, + /// + /// One handed weapon. + /// + OneHanded = 0x01, - /// - /// Two handed weapon or shield. - /// - TwoHanded = 0x02, + /// + /// Two handed weapon or shield. + /// + TwoHanded = 0x02, - /// - /// Shoes. - /// - Shoes = 0x03, + /// + /// Shoes. + /// + Shoes = 0x03, - /// - /// Pants. - /// - Pants = 0x04, + /// + /// Pants. + /// + Pants = 0x04, - /// - /// Shirts. - /// - Shirt = 0x05, + /// + /// Shirts. + /// + Shirt = 0x05, - /// - /// Helmets, hats, and masks. - /// - Helm = 0x06, + /// + /// Helmets, hats, and masks. + /// + Helm = 0x06, - /// - /// Gloves. - /// - Gloves = 0x07, + /// + /// Gloves. + /// + Gloves = 0x07, - /// - /// Rings. - /// - Ring = 0x08, + /// + /// Rings. + /// + Ring = 0x08, - /// - /// Talismans. - /// - Talisman = 0x09, + /// + /// Talismans. + /// + Talisman = 0x09, - /// - /// Gorgets and necklaces. - /// - Neck = 0x0A, + /// + /// Gorgets and necklaces. + /// + Neck = 0x0A, - /// - /// Hair. - /// - Hair = 0x0B, + /// + /// Hair. + /// + Hair = 0x0B, - /// - /// Half aprons. - /// - Waist = 0x0C, + /// + /// Half aprons. + /// + Waist = 0x0C, - /// - /// Torso, inner layer. - /// - InnerTorso = 0x0D, + /// + /// Torso, inner layer. + /// + InnerTorso = 0x0D, - /// - /// Bracelets. - /// - Bracelet = 0x0E, + /// + /// Bracelets. + /// + Bracelet = 0x0E, - /// - /// Unused. - /// - Unused_xF = 0x0F, + /// + /// Unused. + /// + Unused_xF = 0x0F, - /// - /// Beards and mustaches. - /// - FacialHair = 0x10, + /// + /// Beards and mustaches. + /// + FacialHair = 0x10, - /// - /// Torso, outer layer. - /// - MiddleTorso = 0x11, + /// + /// Torso, outer layer. + /// + MiddleTorso = 0x11, - /// - /// Earings. - /// - Earrings = 0x12, + /// + /// Earings. + /// + Earrings = 0x12, - /// - /// Arms and sleeves. - /// - Arms = 0x13, + /// + /// Arms and sleeves. + /// + Arms = 0x13, - /// - /// Cloaks. - /// - Cloak = 0x14, + /// + /// Cloaks. + /// + Cloak = 0x14, - /// - /// Backpacks. - /// - Backpack = 0x15, + /// + /// Backpacks. + /// + Backpack = 0x15, - /// - /// Torso, outer layer. - /// - OuterTorso = 0x16, + /// + /// Torso, outer layer. + /// + OuterTorso = 0x16, - /// - /// Leggings, outer layer. - /// - OuterLegs = 0x17, + /// + /// Leggings, outer layer. + /// + OuterLegs = 0x17, - /// - /// Leggings, inner layer. - /// - InnerLegs = 0x18, + /// + /// Leggings, inner layer. + /// + InnerLegs = 0x18, - /// - /// Last valid non-internal layer. Equivalent to Layer.InnerLegs. - /// - LastUserValid = 0x18, + /// + /// Last valid non-internal layer. Equivalent to Layer.InnerLegs. + /// + LastUserValid = 0x18, - /// - /// Mount item layer. - /// - Mount = 0x19, + /// + /// Mount item layer. + /// + Mount = 0x19, - /// - /// Vendor 'buy pack' layer. - /// - ShopBuy = 0x1A, + /// + /// Vendor 'buy pack' layer. + /// + ShopBuy = 0x1A, - /// - /// Vendor 'resale pack' layer. - /// - ShopResale = 0x1B, + /// + /// Vendor 'resale pack' layer. + /// + ShopResale = 0x1B, - /// - /// Vendor 'sell pack' layer. - /// - ShopSell = 0x1C, + /// + /// Vendor 'sell pack' layer. + /// + ShopSell = 0x1C, - /// - /// Bank box layer. - /// - Bank = 0x1D, + /// + /// Bank box layer. + /// + Bank = 0x1D, - /// - /// Last valid layer. Equivalent to Layer.Bank. - /// - LastValid = 0x1D - } + /// + /// Last valid layer. Equivalent to Layer.Bank. + /// + LastValid = 0x1D } diff --git a/Projects/Server/Items/LightType.cs b/Projects/Server/Items/LightType.cs index 6245926a8..c01a6a626 100644 --- a/Projects/Server/Items/LightType.cs +++ b/Projects/Server/Items/LightType.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: LightType.cs * * * @@ -13,291 +13,290 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server +namespace Server; + +public enum LightType { - public enum LightType - { - /// - /// Window shape, arched, ray shining east. - /// - ArchedWindowEast, + /// + /// Window shape, arched, ray shining east. + /// + ArchedWindowEast, - /// - /// Medium circular shape. - /// - Circle225, + /// + /// Medium circular shape. + /// + Circle225, - /// - /// Small circular shape. - /// - Circle150, + /// + /// Small circular shape. + /// + Circle150, - /// - /// Door shape, shining south. - /// - DoorSouth, + /// + /// Door shape, shining south. + /// + DoorSouth, - /// - /// Door shape, shining east. - /// - DoorEast, + /// + /// Door shape, shining east. + /// + DoorEast, - /// - /// Large semicircular shape (180 degrees), north wall. - /// - NorthBig, + /// + /// Large semicircular shape (180 degrees), north wall. + /// + NorthBig, - /// - /// Large pie shape (90 degrees), north-east corner. - /// - NorthEastBig, + /// + /// Large pie shape (90 degrees), north-east corner. + /// + NorthEastBig, - /// - /// Large semicircular shape (180 degrees), east wall. - /// - EastBig, + /// + /// Large semicircular shape (180 degrees), east wall. + /// + EastBig, - /// - /// Large semicircular shape (180 degrees), west wall. - /// - WestBig, + /// + /// Large semicircular shape (180 degrees), west wall. + /// + WestBig, - /// - /// Large pie shape (90 degrees), south-west corner. - /// - SouthWestBig, + /// + /// Large pie shape (90 degrees), south-west corner. + /// + SouthWestBig, - /// - /// Large semicircular shape (180 degrees), south wall. - /// - SouthBig, + /// + /// Large semicircular shape (180 degrees), south wall. + /// + SouthBig, - /// - /// Medium semicircular shape (180 degrees), north wall. - /// - NorthSmall, + /// + /// Medium semicircular shape (180 degrees), north wall. + /// + NorthSmall, - /// - /// Medium pie shape (90 degrees), north-east corner. - /// - NorthEastSmall, + /// + /// Medium pie shape (90 degrees), north-east corner. + /// + NorthEastSmall, - /// - /// Medium semicircular shape (180 degrees), east wall. - /// - EastSmall, + /// + /// Medium semicircular shape (180 degrees), east wall. + /// + EastSmall, - /// - /// Medium semicircular shape (180 degrees), west wall. - /// - WestSmall, + /// + /// Medium semicircular shape (180 degrees), west wall. + /// + WestSmall, - /// - /// Medium semicircular shape (180 degrees), south wall. - /// - SouthSmall, + /// + /// Medium semicircular shape (180 degrees), south wall. + /// + SouthSmall, - /// - /// Shaped like a wall decoration, north wall. - /// - DecorationNorth, + /// + /// Shaped like a wall decoration, north wall. + /// + DecorationNorth, - /// - /// Shaped like a wall decoration, north-east corner. - /// - DecorationNorthEast, + /// + /// Shaped like a wall decoration, north-east corner. + /// + DecorationNorthEast, - /// - /// Small semicircular shape (180 degrees), east wall. - /// - EastTiny, + /// + /// Small semicircular shape (180 degrees), east wall. + /// + EastTiny, - /// - /// Shaped like a wall decoration, west wall. - /// - DecorationWest, + /// + /// Shaped like a wall decoration, west wall. + /// + DecorationWest, - /// - /// Shaped like a wall decoration, south-west corner. - /// - DecorationSouthWest, + /// + /// Shaped like a wall decoration, south-west corner. + /// + DecorationSouthWest, - /// - /// Small semicircular shape (180 degrees), south wall. - /// - SouthTiny, + /// + /// Small semicircular shape (180 degrees), south wall. + /// + SouthTiny, - /// - /// Window shape, rectangular, no ray, shining south. - /// - RectWindowSouthNoRay, + /// + /// Window shape, rectangular, no ray, shining south. + /// + RectWindowSouthNoRay, - /// - /// Window shape, rectangular, no ray, shining east. - /// - RectWindowEastNoRay, + /// + /// Window shape, rectangular, no ray, shining east. + /// + RectWindowEastNoRay, - /// - /// Window shape, rectangular, ray shining south. - /// - RectWindowSouth, + /// + /// Window shape, rectangular, ray shining south. + /// + RectWindowSouth, - /// - /// Window shape, rectangular, ray shining east. - /// - RectWindowEast, + /// + /// Window shape, rectangular, ray shining east. + /// + RectWindowEast, - /// - /// Window shape, arched, no ray, shining south. - /// - ArchedWindowSouthNoRay, + /// + /// Window shape, arched, no ray, shining south. + /// + ArchedWindowSouthNoRay, - /// - /// Window shape, arched, no ray, shining east. - /// - ArchedWindowEastNoRay, + /// + /// Window shape, arched, no ray, shining east. + /// + ArchedWindowEastNoRay, - /// - /// Window shape, arched, ray shining south. - /// - ArchedWindowSouth, + /// + /// Window shape, arched, ray shining south. + /// + ArchedWindowSouth, - /// - /// Large circular shape. - /// - Circle300, + /// + /// Large circular shape. + /// + Circle300, - /// - /// Large pie shape (90 degrees), north-west corner. - /// - NorthWestBig, + /// + /// Large pie shape (90 degrees), north-west corner. + /// + NorthWestBig, - /// - /// Negative light. Medium pie shape (90 degrees), south-east corner. - /// - DarkSouthEast, + /// + /// Negative light. Medium pie shape (90 degrees), south-east corner. + /// + DarkSouthEast, - /// - /// Negative light. Medium semicircular shape (180 degrees), south wall. - /// - DarkSouth, + /// + /// Negative light. Medium semicircular shape (180 degrees), south wall. + /// + DarkSouth, - /// - /// Negative light. Medium pie shape (90 degrees), north-west corner. - /// - DarkNorthWest, + /// + /// Negative light. Medium pie shape (90 degrees), north-west corner. + /// + DarkNorthWest, - /// - /// Negative light. Medium pie shape (90 degrees), south-east corner. Equivalent to LightType.SouthEast. - /// - DarkSouthEast2, + /// + /// Negative light. Medium pie shape (90 degrees), south-east corner. Equivalent to LightType.SouthEast. + /// + DarkSouthEast2, - /// - /// Negative light. Medium circular shape (180 degrees), east wall. - /// - DarkEast, + /// + /// Negative light. Medium circular shape (180 degrees), east wall. + /// + DarkEast, - /// - /// Negative light. Large circular shape. - /// - DarkCircle300, + /// + /// Negative light. Large circular shape. + /// + DarkCircle300, - /// - /// Opened door shape, shining south. - /// - DoorOpenSouth, + /// + /// Opened door shape, shining south. + /// + DoorOpenSouth, - /// - /// Opened door shape, shining east. - /// - DoorOpenEast, + /// + /// Opened door shape, shining east. + /// + DoorOpenEast, - /// - /// Window shape, square, ray shining east. - /// - SquareWindowEast, + /// + /// Window shape, square, ray shining east. + /// + SquareWindowEast, - /// - /// Window shape, square, no ray, shining east. - /// - SquareWindowEastNoRay, + /// + /// Window shape, square, no ray, shining east. + /// + SquareWindowEastNoRay, - /// - /// Window shape, square, ray shining south. - /// - SquareWindowSouth, + /// + /// Window shape, square, ray shining south. + /// + SquareWindowSouth, - /// - /// Window shape, square, no ray, shining south. - /// - SquareWindowSouthNoRay, + /// + /// Window shape, square, no ray, shining south. + /// + SquareWindowSouthNoRay, - /// - /// Empty. - /// - Empty, + /// + /// Empty. + /// + Empty, - /// - /// Window shape, skinny, no ray, shining south. - /// - SkinnyWindowSouthNoRay, + /// + /// Window shape, skinny, no ray, shining south. + /// + SkinnyWindowSouthNoRay, - /// - /// Window shape, skinny, ray shining east. - /// - SkinnyWindowEast, + /// + /// Window shape, skinny, ray shining east. + /// + SkinnyWindowEast, - /// - /// Window shape, skinny, no ray, shining east. - /// - SkinnyWindowEastNoRay, + /// + /// Window shape, skinny, no ray, shining east. + /// + SkinnyWindowEastNoRay, - /// - /// Shaped like a hole, shining south. - /// - HoleSouth, + /// + /// Shaped like a hole, shining south. + /// + HoleSouth, - /// - /// Shaped like a hole, shining south. - /// - HoleEast, + /// + /// Shaped like a hole, shining south. + /// + HoleEast, - /// - /// Large circular shape with a moongate graphic embedded. - /// - Moongate, + /// + /// Large circular shape with a moongate graphic embedded. + /// + Moongate, - /// - /// Unknown usage. Many rows of slightly angled lines. - /// - Strips, + /// + /// Unknown usage. Many rows of slightly angled lines. + /// + Strips, - /// - /// Shaped like a small hole, shining south. - /// - SmallHoleSouth, + /// + /// Shaped like a small hole, shining south. + /// + SmallHoleSouth, - /// - /// Shaped like a small hole, shining east. - /// - SmallHoleEast, + /// + /// Shaped like a small hole, shining east. + /// + SmallHoleEast, - /// - /// Large semicircular shape (180 degrees), north wall. Identical graphic as LightType.NorthBig, but slightly - /// different - /// positioning. - /// - NorthBig2, + /// + /// Large semicircular shape (180 degrees), north wall. Identical graphic as LightType.NorthBig, but slightly + /// different + /// positioning. + /// + NorthBig2, - /// - /// Large semicircular shape (180 degrees), west wall. Identical graphic as LightType.WestBig, but slightly different - /// positioning. - /// - WestBig2, + /// + /// Large semicircular shape (180 degrees), west wall. Identical graphic as LightType.WestBig, but slightly different + /// positioning. + /// + WestBig2, - /// - /// Large pie shape (90 degrees), north-west corner. Equivalent to LightType.NorthWestBig. - /// - NorthWestBig2 - } + /// + /// Large pie shape (90 degrees), north-west corner. Equivalent to LightType.NorthWestBig. + /// + NorthWestBig2 } diff --git a/Projects/Server/Items/SecureTradeContainer.cs b/Projects/Server/Items/SecureTradeContainer.cs index 3c28e8089..2c0952aed 100644 --- a/Projects/Server/Items/SecureTradeContainer.cs +++ b/Projects/Server/Items/SecureTradeContainer.cs @@ -1,113 +1,112 @@ using Server.Accounting; using Server.Network; -namespace Server.Items +namespace Server.Items; + +public class SecureTradeContainer : Container { - public class SecureTradeContainer : Container + public SecureTradeContainer(SecureTrade trade) : base(0x1E5E) { - public SecureTradeContainer(SecureTrade trade) : base(0x1E5E) - { - Trade = trade; + Trade = trade; - Movable = false; + Movable = false; + } + + public SecureTradeContainer(Serial serial) : base(serial) + { + } + + public SecureTrade Trade { get; } + + public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) + { + if (item == Trade.From.VirtualCheck || item == Trade.To.VirtualCheck) + { + return true; } - public SecureTradeContainer(Serial serial) : base(serial) + var to = Trade.From.Container != this ? Trade.From.Mobile : Trade.To.Mobile; + + return m.CheckTrade(to, item, this, message, checkItems, plusItems, plusWeight); + } + + public override bool CheckLift(Mobile from, Item item, ref LRReason reject) + { + reject = LRReason.CannotLift; + return false; + } + + public override bool IsAccessibleTo(Mobile check) => + IsChildOf(check) && Trade?.Valid == true && base.IsAccessibleTo(check); + + public override void OnItemAdded(Item item) + { + if (item is not VirtualCheck) { - } - - public SecureTrade Trade { get; } - - public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) - { - if (item == Trade.From.VirtualCheck || item == Trade.To.VirtualCheck) - { - return true; - } - - var to = Trade.From.Container != this ? Trade.From.Mobile : Trade.To.Mobile; - - return m.CheckTrade(to, item, this, message, checkItems, plusItems, plusWeight); - } - - public override bool CheckLift(Mobile from, Item item, ref LRReason reject) - { - reject = LRReason.CannotLift; - return false; - } - - public override bool IsAccessibleTo(Mobile check) => - IsChildOf(check) && Trade?.Valid == true && base.IsAccessibleTo(check); - - public override void OnItemAdded(Item item) - { - if (item is not VirtualCheck) - { - ClearChecks(); - } - } - - public override void OnItemRemoved(Item item) - { - if (item is not VirtualCheck) - { - ClearChecks(); - } - } - - public override void OnSubItemAdded(Item item) - { - if (item is not VirtualCheck) - { - ClearChecks(); - } - } - - public override void OnSubItemRemoved(Item item) - { - if (item is not VirtualCheck) - { - ClearChecks(); - } - } - - public void ClearChecks() - { - if (Trade == null) - { - return; - } - - if (Trade.From?.IsDisposed == false) - { - Trade.From.Accepted = false; - } - - if (Trade.To?.IsDisposed == false) - { - Trade.To.Accepted = false; - } - - Trade.Update(); - } - - public override bool IsChildVisibleTo(Mobile m, Item child) => - child is VirtualCheck - ? AccountGold.Enabled && m.NetState is not { NewSecureTrading: true } - : base.IsChildVisibleTo(m, child); - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); + ClearChecks(); } } + + public override void OnItemRemoved(Item item) + { + if (item is not VirtualCheck) + { + ClearChecks(); + } + } + + public override void OnSubItemAdded(Item item) + { + if (item is not VirtualCheck) + { + ClearChecks(); + } + } + + public override void OnSubItemRemoved(Item item) + { + if (item is not VirtualCheck) + { + ClearChecks(); + } + } + + public void ClearChecks() + { + if (Trade == null) + { + return; + } + + if (Trade.From?.IsDisposed == false) + { + Trade.From.Accepted = false; + } + + if (Trade.To?.IsDisposed == false) + { + Trade.To.Accepted = false; + } + + Trade.Update(); + } + + public override bool IsChildVisibleTo(Mobile m, Item child) => + child is VirtualCheck + ? AccountGold.Enabled && m.NetState is not { NewSecureTrading: true } + : base.IsChildVisibleTo(m, child); + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + } } diff --git a/Projects/Server/Items/VirtualCheck.cs b/Projects/Server/Items/VirtualCheck.cs index c72866f20..d94076cec 100644 --- a/Projects/Server/Items/VirtualCheck.cs +++ b/Projects/Server/Items/VirtualCheck.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: VirtualCheck.cs * * * @@ -16,382 +16,381 @@ using Server.Gumps; using Server.Network; -namespace Server.Items +namespace Server.Items; + +public sealed class VirtualCheck : Item { - public sealed class VirtualCheck : Item + public static bool UseEditGump { get; private set; } + + public static void Configure() { - public static bool UseEditGump { get; private set; } + UseEditGump = ServerConfiguration.GetSetting("virtualChecks.useEditGump", Core.TOL); + } - public static void Configure() + private int m_Gold; + + private int m_Plat; + + public VirtualCheck(int plat = 0, int gold = 0) : base(0x14F0) + { + Plat = plat; + Gold = gold; + + Movable = false; + } + + public VirtualCheck(Serial serial) + : base(serial) + { + } + + public override bool IsVirtualItem => true; + + public override bool DisplayWeight => false; + public override bool DisplayLootType => false; + + public override double DefaultWeight => 0; + + public override string DefaultName => "Offer Of Currency"; + + public EditGump Editor { get; private set; } + + [CommandProperty(AccessLevel.Administrator)] + public int Plat + { + get => m_Plat; + set { - UseEditGump = ServerConfiguration.GetSetting("virtualChecks.useEditGump", Core.TOL); + m_Plat = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Administrator)] + public int Gold + { + get => m_Gold; + set + { + m_Gold = value; + InvalidateProperties(); + } + } + + public override bool IsAccessibleTo(Mobile check) + { + var c = GetSecureTradeCont(); + + if (check == null || c == null) + { + return base.IsAccessibleTo(check); } - private int m_Gold; + return c.RootParent == check && IsChildOf(c); + } - private int m_Plat; - - public VirtualCheck(int plat = 0, int gold = 0) : base(0x14F0) + public override void OnDoubleClickSecureTrade(Mobile from) + { + if (UseEditGump && IsAccessibleTo(from)) { - Plat = plat; - Gold = gold; - - Movable = false; - } - - public VirtualCheck(Serial serial) - : base(serial) - { - } - - public override bool IsVirtualItem => true; - - public override bool DisplayWeight => false; - public override bool DisplayLootType => false; - - public override double DefaultWeight => 0; - - public override string DefaultName => "Offer Of Currency"; - - public EditGump Editor { get; private set; } - - [CommandProperty(AccessLevel.Administrator)] - public int Plat - { - get => m_Plat; - set + if (Editor?.Check?.Deleted != false) { - m_Plat = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Administrator)] - public int Gold - { - get => m_Gold; - set - { - m_Gold = value; - InvalidateProperties(); - } - } - - public override bool IsAccessibleTo(Mobile check) - { - var c = GetSecureTradeCont(); - - if (check == null || c == null) - { - return base.IsAccessibleTo(check); - } - - return c.RootParent == check && IsChildOf(c); - } - - public override void OnDoubleClickSecureTrade(Mobile from) - { - if (UseEditGump && IsAccessibleTo(from)) - { - if (Editor?.Check?.Deleted != false) - { - Editor = new EditGump(from, this); - Editor.Send(); - } - else - { - Editor.Refresh(true); - } + Editor = new EditGump(from, this); + Editor.Send(); } else { - if (Editor != null) - { - Editor.Close(); - Editor = null; - } - - base.OnDoubleClickSecureTrade(from); + Editor.Refresh(true); } } - - public override void OnSingleClick(Mobile from) + else { - LabelTo(from, "Offer: {0:#,0} platinum, {1:#,0} gold", Plat, Gold); - } - - public override void GetProperties(IPropertyList list) - { - base.GetProperties(list); - - list.Add(1060738, $"{Plat:#,0} platinum, {Gold:#,0} gold"); // value: ~1_val~ - } - - public void UpdateTrade(Mobile user) - { - var c = GetSecureTradeCont(); - - if (c?.Trade == null) - { - return; - } - - if (user == c.Trade.From.Mobile) - { - c.Trade.UpdateFromCurrency(); - } - else if (user == c.Trade.To.Mobile) - { - c.Trade.UpdateToCurrency(); - } - - c.ClearChecks(); - } - - public override void OnAfterDelete() - { - base.OnAfterDelete(); - if (Editor != null) { Editor.Close(); Editor = null; } + + base.OnDoubleClickSecureTrade(from); + } + } + + public override void OnSingleClick(Mobile from) + { + LabelTo(from, "Offer: {0:#,0} platinum, {1:#,0} gold", Plat, Gold); + } + + public override void GetProperties(IPropertyList list) + { + base.GetProperties(list); + + list.Add(1060738, $"{Plat:#,0} platinum, {Gold:#,0} gold"); // value: ~1_val~ + } + + public void UpdateTrade(Mobile user) + { + var c = GetSecureTradeCont(); + + if (c?.Trade == null) + { + return; } - public override void Serialize(IGenericWriter writer) + if (user == c.Trade.From.Mobile) { + c.Trade.UpdateFromCurrency(); + } + else if (user == c.Trade.To.Mobile) + { + c.Trade.UpdateToCurrency(); } - public override void Deserialize(IGenericReader reader) + c.ClearChecks(); + } + + public override void OnAfterDelete() + { + base.OnAfterDelete(); + + if (Editor != null) { - Delete(); + Editor.Close(); + Editor = null; + } + } + + public override void Serialize(IGenericWriter writer) + { + } + + public override void Deserialize(IGenericReader reader) + { + Delete(); + } + + public class EditGump : Gump + { + public enum Buttons + { + Close, + Clear, + Accept, + AllPlat, + AllGold } - public class EditGump : Gump + private int m_Plat, m_Gold; + + public EditGump(Mobile user, VirtualCheck check) : base(50, 50) { - public enum Buttons + User = user; + Check = check; + + m_Plat = Check.Plat; + m_Gold = Check.Gold; + + Closable = true; + Disposable = true; + Draggable = true; + Resizable = false; + + User.CloseGump(); + + CompileLayout(); + } + + public Mobile User { get; } + public VirtualCheck Check { get; private set; } + + public override void OnServerClose(NetState owner) + { + base.OnServerClose(owner); + + if (Check?.Deleted == false) { - Close, - Clear, - Accept, - AllPlat, - AllGold + Check.UpdateTrade(User); + } + } + + public void Close() + { + User.CloseGump(); + + if (Check?.Deleted == false) + { + Check.UpdateTrade(User); + } + else + { + Check = null; + } + } + + public void Send() + { + if (Check?.Deleted == false) + { + User.SendGump(this); + } + else + { + Close(); + } + } + + public void Refresh(bool recompile) + { + if (Check?.Deleted != false) + { + Close(); + return; } - private int m_Plat, m_Gold; - - public EditGump(Mobile user, VirtualCheck check) : base(50, 50) + if (recompile) { - User = user; - Check = check; - - m_Plat = Check.Plat; - m_Gold = Check.Gold; - - Closable = true; - Disposable = true; - Draggable = true; - Resizable = false; - - User.CloseGump(); - CompileLayout(); } - public Mobile User { get; } - public VirtualCheck Check { get; private set; } + Close(); + Send(); + } - public override void OnServerClose(NetState owner) + private void CompileLayout() + { + if (Check?.Deleted != false) { - base.OnServerClose(owner); - - if (Check?.Deleted == false) - { - Check.UpdateTrade(User); - } + return; } - public void Close() + Entries.ForEach(e => e.Parent = null); + Entries.Clear(); + + AddPage(0); + + AddBackground(0, 0, 400, 160, 3500); + + // Title + AddImageTiled(25, 35, 350, 3, 96); + AddImage(10, 8, 113); + AddImage(360, 8, 113); + + var title = + $"
BANK OF {User.RawName.ToUpper()}
"; + + AddHtml(40, 15, 320, 20, title); + + // Platinum Row + AddBackground(15, 60, 175, 20, 9300); + AddBackground(20, 45, 165, 30, 9350); + AddItem(20, 45, 3826); // Plat + AddLabel(60, 50, 0, User.Account.TotalPlat.ToString("#,0")); + + AddButton(195, 50, 95, 95, (int)Buttons.AllPlat); // -> + + AddBackground(210, 60, 175, 20, 9300); + AddBackground(215, 45, 165, 30, 9350); + AddTextEntry(225, 50, 145, 20, 0, 0, m_Plat.ToString(), User.Account.TotalPlat.ToString().Length); + + // Gold Row + AddBackground(15, 100, 175, 20, 9300); + AddBackground(20, 85, 165, 30, 9350); + AddItem(20, 85, 3823); // Gold + AddLabel(60, 90, 0, User.Account.TotalGold.ToString("#,0")); + + AddButton(195, 90, 95, 95, (int)Buttons.AllGold); // -> + + AddBackground(210, 100, 175, 20, 9300); + AddBackground(215, 85, 165, 30, 9350); + AddTextEntry(225, 90, 145, 20, 0, 1, m_Gold.ToString(), User.Account.TotalGold.ToString().Length); + + // Buttons + AddButton(20, 128, 12006, 12007, (int)Buttons.Close); + AddButton(215, 128, 12003, 12004, (int)Buttons.Clear); + AddButton(305, 128, 12000, 12002, (int)Buttons.Accept); + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + if (Check?.Deleted != false || sender.Mobile != User) { - User.CloseGump(); - - if (Check?.Deleted == false) - { - Check.UpdateTrade(User); - } - else - { - Check = null; - } - } - - public void Send() - { - if (Check?.Deleted == false) - { - User.SendGump(this); - } - else - { - Close(); - } - } - - public void Refresh(bool recompile) - { - if (Check?.Deleted != false) - { - Close(); - return; - } - - if (recompile) - { - CompileLayout(); - } - Close(); - Send(); + return; } - private void CompileLayout() + bool refresh = false, updated = false; + + switch ((Buttons)info.ButtonID) { - if (Check?.Deleted != false) - { - return; - } + case Buttons.Close: + break; + case Buttons.Clear: + { + m_Plat = m_Gold = 0; + refresh = true; + } + break; + case Buttons.Accept: + { + var platText = info.GetTextEntry(0).Text; + var goldText = info.GetTextEntry(1).Text; - Entries.ForEach(e => e.Parent = null); - Entries.Clear(); - - AddPage(0); - - AddBackground(0, 0, 400, 160, 3500); - - // Title - AddImageTiled(25, 35, 350, 3, 96); - AddImage(10, 8, 113); - AddImage(360, 8, 113); - - var title = - $"
BANK OF {User.RawName.ToUpper()}
"; - - AddHtml(40, 15, 320, 20, title); - - // Platinum Row - AddBackground(15, 60, 175, 20, 9300); - AddBackground(20, 45, 165, 30, 9350); - AddItem(20, 45, 3826); // Plat - AddLabel(60, 50, 0, User.Account.TotalPlat.ToString("#,0")); - - AddButton(195, 50, 95, 95, (int)Buttons.AllPlat); // -> - - AddBackground(210, 60, 175, 20, 9300); - AddBackground(215, 45, 165, 30, 9350); - AddTextEntry(225, 50, 145, 20, 0, 0, m_Plat.ToString(), User.Account.TotalPlat.ToString().Length); - - // Gold Row - AddBackground(15, 100, 175, 20, 9300); - AddBackground(20, 85, 165, 30, 9350); - AddItem(20, 85, 3823); // Gold - AddLabel(60, 90, 0, User.Account.TotalGold.ToString("#,0")); - - AddButton(195, 90, 95, 95, (int)Buttons.AllGold); // -> - - AddBackground(210, 100, 175, 20, 9300); - AddBackground(215, 85, 165, 30, 9350); - AddTextEntry(225, 90, 145, 20, 0, 1, m_Gold.ToString(), User.Account.TotalGold.ToString().Length); - - // Buttons - AddButton(20, 128, 12006, 12007, (int)Buttons.Close); - AddButton(215, 128, 12003, 12004, (int)Buttons.Clear); - AddButton(305, 128, 12000, 12002, (int)Buttons.Accept); - } - - public override void OnResponse(NetState sender, RelayInfo info) - { - if (Check?.Deleted != false || sender.Mobile != User) - { - Close(); - return; - } - - bool refresh = false, updated = false; - - switch ((Buttons)info.ButtonID) - { - case Buttons.Close: - break; - case Buttons.Clear: + if (!int.TryParse(platText, out m_Plat)) { - m_Plat = m_Gold = 0; + User.SendMessage("That is not a valid amount of platinum."); refresh = true; } - break; - case Buttons.Accept: + else if (!int.TryParse(goldText, out m_Gold)) { - var platText = info.GetTextEntry(0).Text; - var goldText = info.GetTextEntry(1).Text; + User.SendMessage("That is not a valid amount of gold."); + refresh = true; + } + else + { + var totalPlat = User.Account.TotalPlat; + var totalGold = User.Account.TotalGold; - if (!int.TryParse(platText, out m_Plat)) + if (totalPlat < m_Plat || totalGold < m_Gold) { - User.SendMessage("That is not a valid amount of platinum."); - refresh = true; - } - else if (!int.TryParse(goldText, out m_Gold)) - { - User.SendMessage("That is not a valid amount of gold."); + m_Plat = User.Account.TotalPlat; + m_Gold = User.Account.TotalGold; + User.SendMessage("You do not have that much currency."); refresh = true; } else { - var totalPlat = User.Account.TotalPlat; - var totalGold = User.Account.TotalGold; - - if (totalPlat < m_Plat || totalGold < m_Gold) - { - m_Plat = User.Account.TotalPlat; - m_Gold = User.Account.TotalGold; - User.SendMessage("You do not have that much currency."); - refresh = true; - } - else - { - Check.Plat = m_Plat; - Check.Gold = m_Gold; - updated = true; - } + Check.Plat = m_Plat; + Check.Gold = m_Gold; + updated = true; } } - break; - case Buttons.AllPlat: - { - m_Plat = User.Account.TotalPlat; - refresh = true; - } - break; - case Buttons.AllGold: - { - m_Gold = User.Account.TotalGold; - refresh = true; - } - break; - } - - if (updated) - { - User.SendMessage("Your offer has been updated."); - } - - if (refresh && Check?.Deleted == false) - { - Refresh(true); - return; - } - - Close(); + } + break; + case Buttons.AllPlat: + { + m_Plat = User.Account.TotalPlat; + refresh = true; + } + break; + case Buttons.AllGold: + { + m_Gold = User.Account.TotalGold; + refresh = true; + } + break; } + + if (updated) + { + User.SendMessage("Your offer has been updated."); + } + + if (refresh && Check?.Deleted == false) + { + Refresh(true); + return; + } + + Close(); } } } diff --git a/Projects/Server/Items/VirtualHair.cs b/Projects/Server/Items/VirtualHair.cs index 91debb93b..d8473a891 100644 --- a/Projects/Server/Items/VirtualHair.cs +++ b/Projects/Server/Items/VirtualHair.cs @@ -3,146 +3,145 @@ using System.Buffers; using System.Runtime.CompilerServices; using Server.Network; -namespace Server +namespace Server; + +public static class OutgoingVirtualHairPackets { - public static class OutgoingVirtualHairPackets + public const int EquipUpdatePacketLength = 15; + public const int RemovePacketLength = 5; + + public static void SendHairEquipUpdatePacket(this NetState ns, Mobile m, uint hairSerial, int itemId, int hue, Layer layer) { - public const int EquipUpdatePacketLength = 15; - public const int RemovePacketLength = 5; - - public static void SendHairEquipUpdatePacket(this NetState ns, Mobile m, uint hairSerial, int itemId, int hue, Layer layer) + if (ns.CannotSendPackets()) { - if (ns.CannotSendPackets()) - { - return; - } - - Span buffer = stackalloc byte[EquipUpdatePacketLength]; - CreateHairEquipUpdatePacket(buffer, m, hairSerial, itemId, hue, layer); - ns.Send(buffer); + return; } - public static void CreateHairEquipUpdatePacket(Span buffer, Mobile m, uint hairSerial, int itemId, int hue, Layer layer) - { - if (buffer[0] != 0) - { - return; - } - - var writer = new SpanWriter(buffer); - writer.Write((byte)0x2E); // Packet ID - - writer.Write(hairSerial); - writer.Write((short)itemId); - writer.Write((byte)0); - writer.Write((byte)layer); - writer.Write(m.Serial); - writer.Write((short)(m.SolidHueOverride >= 0 ? m.SolidHueOverride : hue)); - } - - public static void SendRemoveHairPacket(this NetState ns, uint hairSerial) - { - if (ns.CannotSendPackets()) - { - return; - } - - Span buffer = stackalloc byte[RemovePacketLength]; - CreateRemoveHairPacket(buffer, hairSerial); - ns.Send(buffer); - } - - public static void CreateRemoveHairPacket(Span buffer, uint hairSerial) - { - if (buffer[0] != 0) - { - return; - } - - var writer = new SpanWriter(buffer); - writer.Write((byte)0x1D); // Packet ID - writer.Write(hairSerial); - } + Span buffer = stackalloc byte[EquipUpdatePacketLength]; + CreateHairEquipUpdatePacket(buffer, m, hairSerial, itemId, hue, layer); + ns.Send(buffer); } - public abstract class BaseHairInfo + public static void CreateHairEquipUpdatePacket(Span buffer, Mobile m, uint hairSerial, int itemId, int hue, Layer layer) { - protected BaseHairInfo(int itemid, int hue = 0) + if (buffer[0] != 0) { - ItemID = itemid; - Hue = hue; + return; } - protected BaseHairInfo(IGenericReader reader) - { - var version = reader.ReadInt(); + var writer = new SpanWriter(buffer); + writer.Write((byte)0x2E); // Packet ID - switch (version) - { - case 0: - { - ItemID = reader.ReadInt(); - Hue = reader.ReadInt(); - break; - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int ItemID { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int Hue { get; set; } - - public virtual void Serialize(IGenericWriter writer) - { - writer.Write(0); // version - writer.Write(ItemID); - writer.Write(Hue); - } + writer.Write(hairSerial); + writer.Write((short)itemId); + writer.Write((byte)0); + writer.Write((byte)layer); + writer.Write(m.Serial); + writer.Write((short)(m.SolidHueOverride >= 0 ? m.SolidHueOverride : hue)); } - public class HairInfo : BaseHairInfo + public static void SendRemoveHairPacket(this NetState ns, uint hairSerial) { - public HairInfo(int itemid) - : base(itemid) + if (ns.CannotSendPackets()) { + return; } - public HairInfo(int itemid, int hue) - : base(itemid, hue) - { - } - - public HairInfo(IGenericReader reader) - : base(reader) - { - } - - // TODO: Can we make this higher for newer clients? - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - m.Value * 4; + Span buffer = stackalloc byte[RemovePacketLength]; + CreateRemoveHairPacket(buffer, hairSerial); + ns.Send(buffer); } - public class FacialHairInfo : BaseHairInfo + public static void CreateRemoveHairPacket(Span buffer, uint hairSerial) { - public FacialHairInfo(int itemid) - : base(itemid) + if (buffer[0] != 0) { + return; } - public FacialHairInfo(int itemid, int hue) - : base(itemid, hue) - { - } - - public FacialHairInfo(IGenericReader reader) - : base(reader) - { - } - - // TODO: Can we make this higher for newer clients? - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - 1 - m.Value* 4; + var writer = new SpanWriter(buffer); + writer.Write((byte)0x1D); // Packet ID + writer.Write(hairSerial); } } + +public abstract class BaseHairInfo +{ + protected BaseHairInfo(int itemid, int hue = 0) + { + ItemID = itemid; + Hue = hue; + } + + protected BaseHairInfo(IGenericReader reader) + { + var version = reader.ReadInt(); + + switch (version) + { + case 0: + { + ItemID = reader.ReadInt(); + Hue = reader.ReadInt(); + break; + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int ItemID { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int Hue { get; set; } + + public virtual void Serialize(IGenericWriter writer) + { + writer.Write(0); // version + writer.Write(ItemID); + writer.Write(Hue); + } +} + +public class HairInfo : BaseHairInfo +{ + public HairInfo(int itemid) + : base(itemid) + { + } + + public HairInfo(int itemid, int hue) + : base(itemid, hue) + { + } + + public HairInfo(IGenericReader reader) + : base(reader) + { + } + + // TODO: Can we make this higher for newer clients? + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - m.Value * 4; +} + +public class FacialHairInfo : BaseHairInfo +{ + public FacialHairInfo(int itemid) + : base(itemid) + { + } + + public FacialHairInfo(int itemid, int hue) + : base(itemid, hue) + { + } + + public FacialHairInfo(IGenericReader reader) + : base(reader) + { + } + + // TODO: Can we make this higher for newer clients? + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FakeSerial(Serial m) => 0x7FFFFFFF - 0x400 - 1 - m.Value* 4; +} diff --git a/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs b/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs index 86e8f503e..10fef9468 100644 --- a/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs +++ b/Projects/Server/Json/Converters/ClientVersionConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ClientVersionConverterFactory.cs * * * @@ -25,4 +25,4 @@ public class ClientVersionConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new ClientVersionConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/GuidConverter.cs b/Projects/Server/Json/Converters/GuidConverter.cs index 53034d2d4..64a5fb13e 100644 --- a/Projects/Server/Json/Converters/GuidConverter.cs +++ b/Projects/Server/Json/Converters/GuidConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GuidConverter.cs * * * @@ -33,4 +33,4 @@ public class GuidConverter : JsonConverter public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/GuidConverterFactory.cs b/Projects/Server/Json/Converters/GuidConverterFactory.cs index 8ec9a5444..4cfdfb110 100644 --- a/Projects/Server/Json/Converters/GuidConverterFactory.cs +++ b/Projects/Server/Json/Converters/GuidConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GuidConverterFactory.cs * * * @@ -25,4 +25,4 @@ public class GuidConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new GuidConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/IPEndPointConverter.cs b/Projects/Server/Json/Converters/IPEndPointConverter.cs index 41b4bab66..aa4d01861 100644 --- a/Projects/Server/Json/Converters/IPEndPointConverter.cs +++ b/Projects/Server/Json/Converters/IPEndPointConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IPEndPointConverter.cs * * * @@ -34,4 +34,4 @@ public class IPEndPointConverter : JsonConverter public override void Write(Utf8JsonWriter writer, IPEndPoint value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/IPEndPointConverterFactory.cs b/Projects/Server/Json/Converters/IPEndPointConverterFactory.cs index 3906cbb4d..a7f02cd47 100644 --- a/Projects/Server/Json/Converters/IPEndPointConverterFactory.cs +++ b/Projects/Server/Json/Converters/IPEndPointConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IPEndPointConverterFactory.cs * * * @@ -26,4 +26,4 @@ public class IPEndPointConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new IPEndPointConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/MapConverter.cs b/Projects/Server/Json/Converters/MapConverter.cs index a220e5ba0..3880f60ec 100644 --- a/Projects/Server/Json/Converters/MapConverter.cs +++ b/Projects/Server/Json/Converters/MapConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MapConverter.cs * * * @@ -31,4 +31,4 @@ public class MapConverter : JsonConverter public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) => writer.WriteStringValue(value.Name); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/MapConverterFactory.cs b/Projects/Server/Json/Converters/MapConverterFactory.cs index a6d312c52..3abf14edf 100644 --- a/Projects/Server/Json/Converters/MapConverterFactory.cs +++ b/Projects/Server/Json/Converters/MapConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MapConverterFactory.cs * * * @@ -25,4 +25,4 @@ public class MapConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new MapConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/Point2DConverter.cs b/Projects/Server/Json/Converters/Point2DConverter.cs index 33bc8719b..40293fd0f 100644 --- a/Projects/Server/Json/Converters/Point2DConverter.cs +++ b/Projects/Server/Json/Converters/Point2DConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Point2DConverter.cs * * * @@ -107,4 +107,4 @@ public class Point2DConverter : JsonConverter writer.WriteNumberValue(value.Y); writer.WriteEndArray(); } -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/Point2DConverterFactory.cs b/Projects/Server/Json/Converters/Point2DConverterFactory.cs index 3e064bd9d..81f097f0d 100644 --- a/Projects/Server/Json/Converters/Point2DConverterFactory.cs +++ b/Projects/Server/Json/Converters/Point2DConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Point2DConverterFactory.cs * * * @@ -26,4 +26,4 @@ public class Point2DConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new Point2DConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/Point3DConverter.cs b/Projects/Server/Json/Converters/Point3DConverter.cs index d508dcd66..5bfcfd24b 100644 --- a/Projects/Server/Json/Converters/Point3DConverter.cs +++ b/Projects/Server/Json/Converters/Point3DConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Point3DConverter.cs * * * @@ -109,4 +109,4 @@ public class Point3DConverter : JsonConverter writer.WriteNumberValue(value.Z); writer.WriteEndArray(); } -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/Point3DConverterFactory.cs b/Projects/Server/Json/Converters/Point3DConverterFactory.cs index f7dd0835f..04c3cd419 100644 --- a/Projects/Server/Json/Converters/Point3DConverterFactory.cs +++ b/Projects/Server/Json/Converters/Point3DConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Point3DConverterFactory.cs * * * @@ -26,4 +26,4 @@ public class Point3DConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new Point3DConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/Rectangle3DConverter.cs b/Projects/Server/Json/Converters/Rectangle3DConverter.cs index b614c8196..a224c4f67 100644 --- a/Projects/Server/Json/Converters/Rectangle3DConverter.cs +++ b/Projects/Server/Json/Converters/Rectangle3DConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Rectangle3DConverter.cs * * * @@ -180,4 +180,4 @@ public class Rectangle3DConverter : JsonConverter writer.WriteNumberValue(value.Depth); writer.WriteEndArray(); } -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/Rectangle3DConverterFactory.cs b/Projects/Server/Json/Converters/Rectangle3DConverterFactory.cs index ca5c44fd8..3f71966cc 100644 --- a/Projects/Server/Json/Converters/Rectangle3DConverterFactory.cs +++ b/Projects/Server/Json/Converters/Rectangle3DConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Rectangle3DConverterFactory.cs * * * @@ -25,4 +25,4 @@ public class Rectangle3DConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new Rectangle3DConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/TimeSpanConverter.cs b/Projects/Server/Json/Converters/TimeSpanConverter.cs index 17ccb2583..0404a0da2 100644 --- a/Projects/Server/Json/Converters/TimeSpanConverter.cs +++ b/Projects/Server/Json/Converters/TimeSpanConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TimeSpanConverter.cs * * * @@ -26,4 +26,4 @@ public class TimeSpanConverter : JsonConverter public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString()); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/TimeSpanConverterFactory.cs b/Projects/Server/Json/Converters/TimeSpanConverterFactory.cs index 9b6164665..b9564ade1 100644 --- a/Projects/Server/Json/Converters/TimeSpanConverterFactory.cs +++ b/Projects/Server/Json/Converters/TimeSpanConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TimeSpanConverterFactory.cs * * * @@ -25,4 +25,4 @@ public class TimeSpanConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new TimeSpanConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/TypeConverter.cs b/Projects/Server/Json/Converters/TypeConverter.cs index 3b59c6024..dcd253ec6 100644 --- a/Projects/Server/Json/Converters/TypeConverter.cs +++ b/Projects/Server/Json/Converters/TypeConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TypeConverter.cs * * * diff --git a/Projects/Server/Json/Converters/TypeConverterFactory.cs b/Projects/Server/Json/Converters/TypeConverterFactory.cs index 4392bba1e..4fc7698e1 100644 --- a/Projects/Server/Json/Converters/TypeConverterFactory.cs +++ b/Projects/Server/Json/Converters/TypeConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TypeConverterFactory.cs * * * diff --git a/Projects/Server/Json/Converters/WorldLocationConverter.cs b/Projects/Server/Json/Converters/WorldLocationConverter.cs index 9e957f993..fc328c908 100644 --- a/Projects/Server/Json/Converters/WorldLocationConverter.cs +++ b/Projects/Server/Json/Converters/WorldLocationConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: WorldLocationConverter.cs * * * @@ -185,4 +185,4 @@ public class WorldLocationConverter : JsonConverter writer.WriteStringValue(value.Map.ToString()); writer.WriteEndArray(); } -} \ No newline at end of file +} diff --git a/Projects/Server/Json/Converters/WorldLocationConverterFactory.cs b/Projects/Server/Json/Converters/WorldLocationConverterFactory.cs index 961684e90..42d5ac99c 100644 --- a/Projects/Server/Json/Converters/WorldLocationConverterFactory.cs +++ b/Projects/Server/Json/Converters/WorldLocationConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: WorldLocationConverterFactory.cs * * * @@ -25,4 +25,4 @@ public class WorldLocationConverterFactory : JsonConverterFactory public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new WorldLocationConverter(); -} \ No newline at end of file +} diff --git a/Projects/Server/Json/DynamicJson.cs b/Projects/Server/Json/DynamicJson.cs index c787881e9..4c2ceea79 100644 --- a/Projects/Server/Json/DynamicJson.cs +++ b/Projects/Server/Json/DynamicJson.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: DynamicJson.cs * * * @@ -18,50 +18,49 @@ using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -namespace Server.Json +namespace Server.Json; + +public class DynamicJson { - public class DynamicJson + public static DynamicJson Create(Type type) => new() { - public static DynamicJson Create(Type type) => new() + Type = type.Name, + Data = new Dictionary() + }; + + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonExtensionData] + public Dictionary Data { get; set; } + + // TODO: Use JSON Node in .NET 6 + public void SetProperty(string key, JsonSerializerOptions options, T value) + { + using var doc = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value, options)); + Data[key] = doc.RootElement.Clone(); + } + + public bool GetProperty(string key, JsonSerializerOptions options, out T t) + { + if (Data.TryGetValue(key, out var el)) { - Type = type.Name, - Data = new Dictionary() - }; - - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonExtensionData] - public Dictionary Data { get; set; } - - // TODO: Use JSON Node in .NET 6 - public void SetProperty(string key, JsonSerializerOptions options, T value) - { - using var doc = JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(value, options)); - Data[key] = doc.RootElement.Clone(); + t = el.ToObject(options); + return true; } - public bool GetProperty(string key, JsonSerializerOptions options, out T t) - { - if (Data.TryGetValue(key, out var el)) - { - t = el.ToObject(options); - return true; - } + t = default; + return false; + } - t = default; - return false; + public bool GetEnumProperty(string key, JsonSerializerOptions options, out T t) where T : struct, Enum + { + if (Data.TryGetValue(key, out var el)) + { + return Enum.TryParse(el.ToObject(options), out t); } - public bool GetEnumProperty(string key, JsonSerializerOptions options, out T t) where T : struct, Enum - { - if (Data.TryGetValue(key, out var el)) - { - return Enum.TryParse(el.ToObject(options), out t); - } - - t = default; - return false; - } + t = default; + return false; } } diff --git a/Projects/Server/Json/JsonConfig.cs b/Projects/Server/Json/JsonConfig.cs index 7bddd24c8..72294610c 100644 --- a/Projects/Server/Json/JsonConfig.cs +++ b/Projects/Server/Json/JsonConfig.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: JsonConfig.cs * * * @@ -21,93 +21,92 @@ using System.Text.Json; using System.Text.Json.Serialization; using Server.Text; -namespace Server.Json +namespace Server.Json; + +public static class JsonConfig { - public static class JsonConfig + public static readonly JsonSerializerOptions DefaultOptions = GetOptions(); + + public static JsonSerializerOptions GetOptions(params JsonConverterFactory[] converters) { - public static readonly JsonSerializerOptions DefaultOptions = GetOptions(); - - public static JsonSerializerOptions GetOptions(params JsonConverterFactory[] converters) + // In the future this should be optimized by cloning DefaultOptions + var options = new JsonSerializerOptions { - // In the future this should be optimized by cloning DefaultOptions - var options = new JsonSerializerOptions - { - WriteIndented = true, - AllowTrailingCommas = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - ReadCommentHandling = JsonCommentHandling.Skip, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }; + WriteIndented = true, + AllowTrailingCommas = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + ReadCommentHandling = JsonCommentHandling.Skip, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; - options.Converters.Add(new ClientVersionConverterFactory()); - options.Converters.Add(new GuidConverterFactory()); - options.Converters.Add(new JsonStringEnumConverter()); - options.Converters.Add(new MapConverterFactory()); - options.Converters.Add(new Point3DConverterFactory()); - options.Converters.Add(new Rectangle3DConverterFactory()); - options.Converters.Add(new TimeSpanConverterFactory()); - options.Converters.Add(new IPEndPointConverterFactory()); - options.Converters.Add(new TypeConverterFactory()); - options.Converters.Add(new WorldLocationConverterFactory()); + options.Converters.Add(new ClientVersionConverterFactory()); + options.Converters.Add(new GuidConverterFactory()); + options.Converters.Add(new JsonStringEnumConverter()); + options.Converters.Add(new MapConverterFactory()); + options.Converters.Add(new Point3DConverterFactory()); + options.Converters.Add(new Rectangle3DConverterFactory()); + options.Converters.Add(new TimeSpanConverterFactory()); + options.Converters.Add(new IPEndPointConverterFactory()); + options.Converters.Add(new TypeConverterFactory()); + options.Converters.Add(new WorldLocationConverterFactory()); - for (var i = 0; i < converters.Length; i++) - { - options.Converters.Add(converters[i]); - } - - return options; + for (var i = 0; i < converters.Length; i++) + { + options.Converters.Add(converters[i]); } - public static T Deserialize(string filePath, JsonSerializerOptions options = null) - { - if (!File.Exists(filePath)) - { - return default; - } + return options; + } - var text = File.ReadAllText(filePath, TextEncoding.UTF8); - return JsonSerializer.Deserialize(text, options ?? DefaultOptions); + public static T Deserialize(string filePath, JsonSerializerOptions options = null) + { + if (!File.Exists(filePath)) + { + return default; } - public static string Serialize(object value, JsonSerializerOptions options = null) => - JsonSerializer.Serialize(value, options ?? DefaultOptions); + var text = File.ReadAllText(filePath, TextEncoding.UTF8); + return JsonSerializer.Deserialize(text, options ?? DefaultOptions); + } - public static void Serialize(string filePath, object value, JsonSerializerOptions options = null) + public static string Serialize(object value, JsonSerializerOptions options = null) => + JsonSerializer.Serialize(value, options ?? DefaultOptions); + + public static void Serialize(string filePath, object value, JsonSerializerOptions options = null) + { + var contents = Serialize(value, options); + + if (File.Exists(filePath)) { - var contents = Serialize(value, options); - - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - - Directory.CreateDirectory(Path.GetDirectoryName(filePath)!); - - File.WriteAllText(filePath, contents); + File.Delete(filePath); } - public static T ToObject(this ref Utf8JsonReader reader, JsonSerializerOptions options = null) => - JsonSerializer.Deserialize(ref reader, options); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)!); - public static T ToObject(this JsonElement element, JsonSerializerOptions options = null) + File.WriteAllText(filePath, contents); + } + + public static T ToObject(this ref Utf8JsonReader reader, JsonSerializerOptions options = null) => + JsonSerializer.Deserialize(ref reader, options); + + public static T ToObject(this JsonElement element, JsonSerializerOptions options = null) + { + var bufferWriter = new ArrayBufferWriter(); + using (var writer = new Utf8JsonWriter(bufferWriter)) { - var bufferWriter = new ArrayBufferWriter(); - using (var writer = new Utf8JsonWriter(bufferWriter)) - { - element.WriteTo(writer); - } - - return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, options); + element.WriteTo(writer); } - public static T ToObject(this JsonDocument document, JsonSerializerOptions options = null) - { - if (document == null) - { - throw new ArgumentNullException(nameof(document)); - } + return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, options); + } - return document.RootElement.ToObject(options); + public static T ToObject(this JsonDocument document, JsonSerializerOptions options = null) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); } + + return document.RootElement.ToObject(options); } } diff --git a/Projects/Server/Json/JsonUtilities.cs b/Projects/Server/Json/JsonUtilities.cs index b4976c9e5..2f14c1a71 100644 --- a/Projects/Server/Json/JsonUtilities.cs +++ b/Projects/Server/Json/JsonUtilities.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: JsonPropertySorter.cs * * * @@ -20,81 +20,80 @@ using System.Text; using System.Text.Encodings.Web; using System.Text.Json; -namespace Server.Json +namespace Server.Json; + +public static class JsonUtilities { - public static class JsonUtilities + public static string SortByPropertyName(string jsonStr) { - public static string SortByPropertyName(string jsonStr) + using JsonDocument doc = JsonDocument.Parse(jsonStr); + return SortByPropertyName(doc.RootElement); + } + + public static string SortByPropertyName(JsonElement je) + { + // TODO: Better way to do this than a stream? + using var ms = new MemoryStream(); + JsonWriterOptions opts = new JsonWriterOptions { - using JsonDocument doc = JsonDocument.Parse(jsonStr); - return SortByPropertyName(doc.RootElement); + Indented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + using (var writer = new Utf8JsonWriter(ms, opts)) + { + WriteJsonElementSorted(je, writer); } - public static string SortByPropertyName(JsonElement je) + ms.TryGetBuffer(out var buffer); + return Encoding.UTF8.GetString(buffer); + } + + private static void WriteJsonElementSorted(JsonElement je, Utf8JsonWriter writer) + { + switch(je.ValueKind) { - // TODO: Better way to do this than a stream? - using var ms = new MemoryStream(); - JsonWriterOptions opts = new JsonWriterOptions - { - Indented = true, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }; + case JsonValueKind.Object: + writer.WriteStartObject(); - using (var writer = new Utf8JsonWriter(ms, opts)) - { - WriteJsonElementSorted(je, writer); - } + // TODO: This is slow, can make it faster? + foreach (JsonProperty x in je.EnumerateObject().OrderBy(prop => prop.Name)) + { + writer.WritePropertyName(x.Name); + WriteJsonElementSorted(x.Value, writer); + } - ms.TryGetBuffer(out var buffer); - return Encoding.UTF8.GetString(buffer); - } + writer.WriteEndObject(); + break; + case JsonValueKind.Array: + writer.WriteStartArray(); + foreach(JsonElement x in je.EnumerateArray()) + { + WriteJsonElementSorted(x, writer); + } + writer.WriteEndArray(); + break; + case JsonValueKind.Number: + writer.WriteNumberValue(je.GetDouble()); + break; + case JsonValueKind.String: + // Escape the string + writer.WriteStringValue(je.GetString()); + break; + case JsonValueKind.Null: + writer.WriteNullValue(); + break; + case JsonValueKind.True: + writer.WriteBooleanValue(true); + break; + case JsonValueKind.False: + writer.WriteBooleanValue(false); + break; + case JsonValueKind.Undefined: // Don't write anything + break; + default: + throw new NotImplementedException($"Kind: {je.ValueKind}"); - private static void WriteJsonElementSorted(JsonElement je, Utf8JsonWriter writer) - { - switch(je.ValueKind) - { - case JsonValueKind.Object: - writer.WriteStartObject(); - - // TODO: This is slow, can make it faster? - foreach (JsonProperty x in je.EnumerateObject().OrderBy(prop => prop.Name)) - { - writer.WritePropertyName(x.Name); - WriteJsonElementSorted(x.Value, writer); - } - - writer.WriteEndObject(); - break; - case JsonValueKind.Array: - writer.WriteStartArray(); - foreach(JsonElement x in je.EnumerateArray()) - { - WriteJsonElementSorted(x, writer); - } - writer.WriteEndArray(); - break; - case JsonValueKind.Number: - writer.WriteNumberValue(je.GetDouble()); - break; - case JsonValueKind.String: - // Escape the string - writer.WriteStringValue(je.GetString()); - break; - case JsonValueKind.Null: - writer.WriteNullValue(); - break; - case JsonValueKind.True: - writer.WriteBooleanValue(true); - break; - case JsonValueKind.False: - writer.WriteBooleanValue(false); - break; - case JsonValueKind.Undefined: // Don't write anything - break; - default: - throw new NotImplementedException($"Kind: {je.ValueKind}"); - - } } } } diff --git a/Projects/Server/KeywordList.cs b/Projects/Server/KeywordList.cs index eb3662c7f..ce7270350 100644 --- a/Projects/Server/KeywordList.cs +++ b/Projects/Server/KeywordList.cs @@ -1,65 +1,64 @@ using System; -namespace Server +namespace Server; + +public class KeywordList { - public class KeywordList + private static readonly int[] m_EmptyInts = Array.Empty(); + private int[] m_Keywords; + + public KeywordList() { - private static readonly int[] m_EmptyInts = Array.Empty(); - private int[] m_Keywords; + m_Keywords = new int[8]; + Count = 0; + } - public KeywordList() + public int Count { get; private set; } + + public bool Contains(int keyword) + { + var contains = false; + + for (var i = 0; !contains && i < Count; ++i) { - m_Keywords = new int[8]; - Count = 0; + contains = keyword == m_Keywords[i]; } - public int Count { get; private set; } + return contains; + } - public bool Contains(int keyword) + public void Add(int keyword) + { + if (Count + 1 > m_Keywords.Length) { - var contains = false; + var old = m_Keywords; + m_Keywords = new int[old.Length * 2]; - for (var i = 0; !contains && i < Count; ++i) + for (var i = 0; i < old.Length; ++i) { - contains = keyword == m_Keywords[i]; + m_Keywords[i] = old[i]; } - - return contains; } - public void Add(int keyword) + m_Keywords[Count++] = keyword; + } + + public int[] ToArray() + { + if (Count == 0) { - if (Count + 1 > m_Keywords.Length) - { - var old = m_Keywords; - m_Keywords = new int[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - { - m_Keywords[i] = old[i]; - } - } - - m_Keywords[Count++] = keyword; + return m_EmptyInts; } - public int[] ToArray() + var keywords = new int[Count]; + + for (var i = 0; i < Count; ++i) { - if (Count == 0) - { - return m_EmptyInts; - } - - var keywords = new int[Count]; - - for (var i = 0; i < Count; ++i) - { - keywords[i] = m_Keywords[i]; - } - - Count = 0; - - return keywords; + keywords[i] = m_Keywords[i]; } + + Count = 0; + + return keywords; } } diff --git a/Projects/Server/Logging/ILogger.cs b/Projects/Server/Logging/ILogger.cs index d1c6f8b7a..cce7b27f1 100644 --- a/Projects/Server/Logging/ILogger.cs +++ b/Projects/Server/Logging/ILogger.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ILogger.cs * * * @@ -15,23 +15,22 @@ using System; -namespace Server.Logging +namespace Server.Logging; + +public interface ILogger { - public interface ILogger - { - void Debug(string message, params object[] args); - void Debug(Exception exception, string message, params object[] args); + void Debug(string message, params object[] args); + void Debug(Exception exception, string message, params object[] args); - void Information(string message, params object[] args); - void Information(Exception exception, string message, params object[] args); + void Information(string message, params object[] args); + void Information(Exception exception, string message, params object[] args); - void Warning(string message, params object[] args); - void Warning(Exception exception, string message, params object[] args); + void Warning(string message, params object[] args); + void Warning(Exception exception, string message, params object[] args); - void Error(string message, params object[] args); - void Error(Exception exception, string message, params object[] args); + void Error(string message, params object[] args); + void Error(Exception exception, string message, params object[] args); - void Fatal(string message, params object[] args); - void Fatal(Exception exception, string message, params object[] args); - } + void Fatal(string message, params object[] args); + void Fatal(Exception exception, string message, params object[] args); } diff --git a/Projects/Server/Logging/SerilogLogger.cs b/Projects/Server/Logging/SerilogLogger.cs index 6aa9178d9..89eb90f97 100644 --- a/Projects/Server/Logging/SerilogLogger.cs +++ b/Projects/Server/Logging/SerilogLogger.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SerilogLogger.cs * * * @@ -16,53 +16,52 @@ using System; using System.Runtime.CompilerServices; -namespace Server.Logging +namespace Server.Logging; + +public class SerilogLogger : ILogger { - public class SerilogLogger : ILogger - { - private readonly Serilog.ILogger serilogLogger; + private readonly Serilog.ILogger serilogLogger; - public SerilogLogger(Serilog.ILogger serilogLogger) => - this.serilogLogger = serilogLogger; + public SerilogLogger(Serilog.ILogger serilogLogger) => + this.serilogLogger = serilogLogger; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Debug(string message, params object[] args) => - serilogLogger.Debug(message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Debug(string message, params object[] args) => + serilogLogger.Debug(message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Debug(Exception exception, string message, params object[] args) => - serilogLogger.Debug(exception, message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Debug(Exception exception, string message, params object[] args) => + serilogLogger.Debug(exception, message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Information(string message, params object[] args) => - serilogLogger.Information(message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Information(string message, params object[] args) => + serilogLogger.Information(message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Information(Exception exception, string message, params object[] args) => - serilogLogger.Information(exception, message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Information(Exception exception, string message, params object[] args) => + serilogLogger.Information(exception, message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Warning(string message, params object[] args) => - serilogLogger.Warning(message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Warning(string message, params object[] args) => + serilogLogger.Warning(message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Warning(Exception exception, string message, params object[] args) => - serilogLogger.Information(exception, message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Warning(Exception exception, string message, params object[] args) => + serilogLogger.Warning(exception, message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Error(string message, params object[] args) => - serilogLogger.Error(message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Error(string message, params object[] args) => + serilogLogger.Error(message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Error(Exception exception, string message, params object[] args) => - serilogLogger.Error(exception, message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Error(Exception exception, string message, params object[] args) => + serilogLogger.Error(exception, message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Fatal(string message, params object[] args) => - serilogLogger.Fatal(message, args); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Fatal(string message, params object[] args) => + serilogLogger.Fatal(message, args); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Fatal(Exception exception, string message, params object[] args) => - serilogLogger.Fatal(exception, message, args); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Fatal(Exception exception, string message, params object[] args) => + serilogLogger.Fatal(exception, message, args); } diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index f964a49b0..62d5569bb 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Main.cs * * * @@ -30,662 +30,661 @@ using Server.Json; using Server.Logging; using Server.Network; -namespace Server +namespace Server; + +public static class Core { - public static class Core + private static readonly ILogger logger = LogFactory.GetLogger(typeof(Core)); + + private static bool _crashed; + private static string _baseDirectory; + + private static bool _profiling; + private static long _profileStart; + private static long _profileTime; +#nullable enable + private static bool? _isRunningFromXUnit; +#nullable restore + + private static int _itemCount; + private static int _mobileCount; + public static EventLoopContext LoopContext { get; set; } + + private static readonly Type[] _serialTypeArray = { typeof(Serial) }; + + public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + public static readonly bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + public static readonly bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); + public static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD; + public static readonly bool IsBSD = IsDarwin || IsFreeBSD; + public static readonly bool Unix = IsBSD || IsLinux; + + private const string AssembliesConfiguration = "Data/assemblies.json"; + +#nullable enable + // TODO: Find a way to get rid of this + public static bool IsRunningFromXUnit { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(Core)); + get + { + if (_isRunningFromXUnit != null) + { + return _isRunningFromXUnit.Value; + } - private static bool _crashed; - private static string _baseDirectory; + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + if (a.FullName.InsensitiveStartsWith("xunit")) + { + _isRunningFromXUnit = true; + return true; + } + } - private static bool _profiling; - private static long _profileStart; - private static long _profileTime; -#nullable enable - private static bool? _isRunningFromXUnit; + _isRunningFromXUnit = false; + return false; + } + } #nullable restore - private static int _itemCount; - private static int _mobileCount; - public static EventLoopContext LoopContext { get; set; } - - private static readonly Type[] _serialTypeArray = { typeof(Serial) }; - - public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - public static readonly bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - public static readonly bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); - public static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD; - public static readonly bool IsBSD = IsDarwin || IsFreeBSD; - public static readonly bool Unix = IsBSD || IsLinux; - - private const string AssembliesConfiguration = "Data/assemblies.json"; - -#nullable enable - // TODO: Find a way to get rid of this - public static bool IsRunningFromXUnit + public static bool Profiling + { + get => _profiling; + set { - get - { - if (_isRunningFromXUnit != null) - { - return _isRunningFromXUnit.Value; - } - - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - if (a.FullName.InsensitiveStartsWith("xunit")) - { - _isRunningFromXUnit = true; - return true; - } - } - - _isRunningFromXUnit = false; - return false; - } - } -#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 Assembly { get; set; } - - // Assembly file version - public static Version Version => new(ThisAssembly.AssemblyFileVersion); - - public static Process Process { get; private set; } - - public static Thread Thread { get; private set; } - - [ThreadStatic] - private static long _tickCount; - - [ThreadStatic] - 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 - { - get - { - if (_tickCount != 0) - { - return _tickCount; - } - - 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 - { - get => _now == DateTime.MinValue ? DateTime.UtcNow : _now; - set => _now = value; - } - - private static long _cycleIndex = 1; - private static float[] _cyclesPerSecond = new float[100]; - - public static float CyclesPerSecond => _cyclesPerSecond[(_cycleIndex - 1) % _cyclesPerSecond.Length]; - - public static bool MultiProcessor { get; private set; } - - public static int ProcessorCount { get; private set; } - - public static string BaseDirectory - { - get - { - if (_baseDirectory == null) - { - try - { - _baseDirectory = Assembly.Location; - - if (_baseDirectory.Length > 0) - { - _baseDirectory = Path.GetDirectoryName(_baseDirectory); - } - } - catch - { - _baseDirectory = ""; - } - } - - return _baseDirectory; - } - } - - public static CancellationTokenSource ClosingTokenSource { get; } = new(); - - 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; - - public static int ScriptItems => _itemCount; - public static int ScriptMobiles => _mobileCount; - - public static Expansion Expansion { get; set; } - - public static bool T2A => Expansion >= Expansion.T2A; - - public static bool UOR => Expansion >= Expansion.UOR; - - public static bool UOTD => Expansion >= Expansion.UOTD; - - public static bool LBR => Expansion >= Expansion.LBR; - - public static bool AOS => Expansion >= Expansion.AOS; - - public static bool SE => Expansion >= Expansion.SE; - - public static bool ML => Expansion >= Expansion.ML; - - public static bool SA => Expansion >= Expansion.SA; - - public static bool HS => Expansion >= Expansion.HS; - - public static bool TOL => Expansion >= Expansion.TOL; - - public static bool EJ => Expansion >= Expansion.EJ; - - public static string FindDataFile(string path, bool throwNotFound = true) - { - string fullPath = null; - - foreach (var p in ServerConfiguration.DataDirectories) - { - fullPath = Path.Combine(p, path); - - if (IsLinux && !File.Exists(fullPath)) - { - var fi = new FileInfo(fullPath); - if (fi.Directory != null && Directory.Exists(fi.Directory.FullName)) - { - fullPath = fi.Directory.EnumerateFiles( - fi.Name, - new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive } - ).FirstOrDefault()?.FullName; - } - } - - if (File.Exists(fullPath)) - { - break; - } - - fullPath = null; - } - - if (fullPath == null && throwNotFound) - { - throw new FileNotFoundException($"Data: {path} was not found"); - } - - return fullPath; - } - - public static IEnumerable FindDataFileByPattern(string pattern) - { - var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }; - foreach (var p in ServerConfiguration.DataDirectories) - { - if (Directory.Exists(p)) - { - foreach (var file in Directory.EnumerateFiles(p, pattern, options)) - { - yield return file; - } - } - } - } - - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - Console.WriteLine(e.IsTerminating ? "Error:" : "Warning:"); - Console.WriteLine(e.ExceptionObject); - - if (e.IsTerminating) - { - _crashed = true; - - var close = false; - - try - { - var args = new ServerCrashedEventArgs(e.ExceptionObject as Exception); - - EventSink.InvokeServerCrashed(args); - - close = args.Close; - } - catch - { - // ignored - } - - if (!close) - { - Console.WriteLine("This exception is fatal, press return to exit"); - Console.ReadLine(); - } - - Kill(); - } - } - - private static void CurrentDomain_ProcessExit(object sender, EventArgs e) - { - if (!Closing) - { - HandleClosed(); - } - } - - private static void Console_CancelKeyPressed(object sender, ConsoleCancelEventArgs e) - { - var keypress = e.SpecialKey switch - { - ConsoleSpecialKey.ControlBreak => "CTRL+BREAK", - _ => "CTRL+C" - }; - - logger.Information($"Detected {keypress} pressed."); - e.Cancel = true; - Kill(); - } - - public static void Kill(bool restart = false) - { - if (Closing) + if (_profiling == value) { return; } - HandleClosed(); + _profiling = value; - if (restart) + if (_profileStart > 0) { - if (IsWindows) + _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 Assembly { get; set; } + + // Assembly file version + public static Version Version => new(ThisAssembly.AssemblyFileVersion); + + public static Process Process { get; private set; } + + public static Thread Thread { get; private set; } + + [ThreadStatic] + private static long _tickCount; + + [ThreadStatic] + 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 + { + get + { + if (_tickCount != 0) + { + return _tickCount; + } + + 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 + { + get => _now == DateTime.MinValue ? DateTime.UtcNow : _now; + set => _now = value; + } + + private static long _cycleIndex = 1; + private static float[] _cyclesPerSecond = new float[100]; + + public static float CyclesPerSecond => _cyclesPerSecond[(_cycleIndex - 1) % _cyclesPerSecond.Length]; + + public static bool MultiProcessor { get; private set; } + + public static int ProcessorCount { get; private set; } + + public static string BaseDirectory + { + get + { + if (_baseDirectory == null) + { + try { - Process.Start("dotnet", Assembly.Location); - } - else - { - var process = new Process + _baseDirectory = Assembly.Location; + + if (_baseDirectory.Length > 0) { - StartInfo = new ProcessStartInfo - { - FileName = "dotnet", - Arguments = Assembly.Location, - UseShellExecute = true - } - }; - - process.Start(); + _baseDirectory = Path.GetDirectoryName(_baseDirectory); + } } - } - - Process.Kill(); - } - - private static void HandleClosed() - { - ClosingTokenSource.Cancel(); - - logger.Information("Shutting down"); - - World.WaitForWriteCompletion(); - - if (!_crashed) - { - EventSink.InvokeShutdown(); - } - } - - public static void Main(string[] args) - { - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; - AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler.AssemblyResolver; - - LoopContext = new EventLoopContext(); - - SynchronizationContext.SetSynchronizationContext(LoopContext); - - foreach (var a in args) - { - if (a.InsensitiveEquals("-profile")) + catch { - Profiling = true; + _baseDirectory = ""; } } - Thread = Thread.CurrentThread; - Process = Process.GetCurrentProcess(); - Assembly = Assembly.GetEntryAssembly(); + return _baseDirectory; + } + } - if (Assembly == null) + public static CancellationTokenSource ClosingTokenSource { get; } = new(); + + public static bool Closing => ClosingTokenSource.IsCancellationRequested; + + public static string Arguments + { + get + { + var sb = new StringBuilder(); + + if (_profiling) { - throw new Exception("Assembly entry is missing."); + Utility.Separate(sb, "-profile", " "); } - if (Thread != null) + return sb.ToString(); + } + } + + public static int GlobalUpdateRange { get; set; } = 18; + + public static int GlobalMaxUpdateRange { get; set; } = 24; + + public static int ScriptItems => _itemCount; + public static int ScriptMobiles => _mobileCount; + + public static Expansion Expansion { get; set; } + + public static bool T2A => Expansion >= Expansion.T2A; + + public static bool UOR => Expansion >= Expansion.UOR; + + public static bool UOTD => Expansion >= Expansion.UOTD; + + public static bool LBR => Expansion >= Expansion.LBR; + + public static bool AOS => Expansion >= Expansion.AOS; + + public static bool SE => Expansion >= Expansion.SE; + + public static bool ML => Expansion >= Expansion.ML; + + public static bool SA => Expansion >= Expansion.SA; + + public static bool HS => Expansion >= Expansion.HS; + + public static bool TOL => Expansion >= Expansion.TOL; + + public static bool EJ => Expansion >= Expansion.EJ; + + public static string FindDataFile(string path, bool throwNotFound = true) + { + string fullPath = null; + + foreach (var p in ServerConfiguration.DataDirectories) + { + fullPath = Path.Combine(p, path); + + if (IsLinux && !File.Exists(fullPath)) { - Thread.Name = "Core Thread"; + var fi = new FileInfo(fullPath); + if (fi.Directory != null && Directory.Exists(fi.Directory.FullName)) + { + fullPath = fi.Directory.EnumerateFiles( + fi.Name, + new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive } + ).FirstOrDefault()?.FullName; + } } - if (BaseDirectory.Length > 0) + if (File.Exists(fullPath)) { - Directory.SetCurrentDirectory(BaseDirectory); + break; } - Utility.PushColor(ConsoleColor.Green); - Console.WriteLine( - "ModernUO - [https://github.com/modernuo/modernuo] Version {0}.{1}.{2}.{3}", - Version.Major, - Version.Minor, - Version.Build, - Version.Revision - ); - Utility.PopColor(); + fullPath = null; + } - Utility.PushColor(ConsoleColor.DarkGray); - Console.WriteLine(@"Copyright 2019-2020 ModernUO Development Team + if (fullPath == null && throwNotFound) + { + throw new FileNotFoundException($"Data: {path} was not found"); + } + + return fullPath; + } + + public static IEnumerable FindDataFileByPattern(string pattern) + { + var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive }; + foreach (var p in ServerConfiguration.DataDirectories) + { + if (Directory.Exists(p)) + { + foreach (var file in Directory.EnumerateFiles(p, pattern, options)) + { + yield return file; + } + } + } + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Console.WriteLine(e.IsTerminating ? "Error:" : "Warning:"); + Console.WriteLine(e.ExceptionObject); + + if (e.IsTerminating) + { + _crashed = true; + + var close = false; + + try + { + var args = new ServerCrashedEventArgs(e.ExceptionObject as Exception); + + EventSink.InvokeServerCrashed(args); + + close = args.Close; + } + catch + { + // ignored + } + + if (!close) + { + Console.WriteLine("This exception is fatal, press return to exit"); + Console.ReadLine(); + } + + Kill(); + } + } + + private static void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + if (!Closing) + { + HandleClosed(); + } + } + + private static void Console_CancelKeyPressed(object sender, ConsoleCancelEventArgs e) + { + var keypress = e.SpecialKey switch + { + ConsoleSpecialKey.ControlBreak => "CTRL+BREAK", + _ => "CTRL+C" + }; + + logger.Information($"Detected {keypress} pressed."); + e.Cancel = true; + Kill(); + } + + public static void Kill(bool restart = false) + { + if (Closing) + { + return; + } + + HandleClosed(); + + if (restart) + { + if (IsWindows) + { + Process.Start("dotnet", Assembly.Location); + } + else + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = Assembly.Location, + UseShellExecute = true + } + }; + + process.Start(); + } + } + + Process.Kill(); + } + + private static void HandleClosed() + { + ClosingTokenSource.Cancel(); + + logger.Information("Shutting down"); + + World.WaitForWriteCompletion(); + + if (!_crashed) + { + EventSink.InvokeShutdown(); + } + } + + public static void Main(string[] args) + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + AppDomain.CurrentDomain.AssemblyResolve += AssemblyHandler.AssemblyResolver; + + LoopContext = new EventLoopContext(); + + SynchronizationContext.SetSynchronizationContext(LoopContext); + + foreach (var a in args) + { + if (a.InsensitiveEquals("-profile")) + { + Profiling = true; + } + } + + Thread = Thread.CurrentThread; + Process = Process.GetCurrentProcess(); + Assembly = Assembly.GetEntryAssembly(); + + if (Assembly == null) + { + throw new Exception("Assembly entry is missing."); + } + + if (Thread != null) + { + Thread.Name = "Core Thread"; + } + + if (BaseDirectory.Length > 0) + { + Directory.SetCurrentDirectory(BaseDirectory); + } + + Utility.PushColor(ConsoleColor.Green); + Console.WriteLine( + "ModernUO - [https://github.com/modernuo/modernuo] Version {0}.{1}.{2}.{3}", + Version.Major, + Version.Minor, + Version.Build, + Version.Revision + ); + Utility.PopColor(); + + Utility.PushColor(ConsoleColor.DarkGray); + Console.WriteLine(@"Copyright 2019-2022 ModernUO Development Team This program comes with ABSOLUTELY NO WARRANTY; This is free software, and you are welcome to redistribute it under certain conditions. You should have received a copy of the GNU General Public License along with this program. If not, see . ".TrimMultiline()); - Utility.PopColor(); + Utility.PopColor(); - ProcessorCount = Environment.ProcessorCount; + ProcessorCount = Environment.ProcessorCount; - if (ProcessorCount > 1) - { - MultiProcessor = true; - } - - Console.CancelKeyPress += Console_CancelKeyPressed; - - ServerConfiguration.Load(); - - logger.Information($"Running on {RuntimeInformation.FrameworkDescription}"); - - var s = Arguments; - - if (s.Length > 0) - { - logger.Information($"Running with arguments: {s}"); - } - - if (MultiProcessor) - { - logger.Information($"Optimizing for {ProcessorCount} processor{(ProcessorCount == 1 ? "" : "s")}"); - } - - if (GCSettings.IsServerGC) - { - logger.Information("Server garbage collection mode enabled"); - } - - logger.Information($"High resolution timing ({(Stopwatch.IsHighResolution ? "Supported" : "Unsupported")})"); - - var assemblyPath = Path.Join(BaseDirectory, AssembliesConfiguration); - - // Load UOContent.dll - var assemblyFiles = JsonConfig.Deserialize>(assemblyPath)?.ToArray(); - if (assemblyFiles == null) - { - throw new JsonException($"Failed to deserialize {assemblyPath}."); - } - - for (var i = 0; i < assemblyFiles.Length; i++) - { - assemblyFiles[i] = Path.Join(BaseDirectory, "Assemblies", assemblyFiles[i]); - } - - AssemblyHandler.LoadAssemblies(assemblyFiles); - - VerifySerialization(); - - Timer.Init(TickCount); - - AssemblyHandler.Invoke("Configure"); - - TileMatrixLoader.LoadTileMatrix(); - - RegionLoader.LoadRegions(); - World.Load(); - - AssemblyHandler.Invoke("Initialize"); - - TcpServer.Start(); - EventSink.InvokeServerStarted(); - RunEventLoop(); + if (ProcessorCount > 1) + { + MultiProcessor = true; } - public static void RunEventLoop() + Console.CancelKeyPress += Console_CancelKeyPressed; + + ServerConfiguration.Load(); + + logger.Information($"Running on {RuntimeInformation.FrameworkDescription}"); + + var s = Arguments; + + if (s.Length > 0) + { + logger.Information($"Running with arguments: {s}"); + } + + if (MultiProcessor) + { + logger.Information($"Optimizing for {ProcessorCount} processor{(ProcessorCount == 1 ? "" : "s")}"); + } + + if (GCSettings.IsServerGC) + { + logger.Information("Server garbage collection mode enabled"); + } + + logger.Information($"High resolution timing ({(Stopwatch.IsHighResolution ? "Supported" : "Unsupported")})"); + + var assemblyPath = Path.Join(BaseDirectory, AssembliesConfiguration); + + // Load UOContent.dll + var assemblyFiles = JsonConfig.Deserialize>(assemblyPath)?.ToArray(); + if (assemblyFiles == null) + { + throw new JsonException($"Failed to deserialize {assemblyPath}."); + } + + for (var i = 0; i < assemblyFiles.Length; i++) + { + assemblyFiles[i] = Path.Join(BaseDirectory, "Assemblies", assemblyFiles[i]); + } + + AssemblyHandler.LoadAssemblies(assemblyFiles); + + VerifySerialization(); + + Timer.Init(TickCount); + + AssemblyHandler.Invoke("Configure"); + + TileMatrixLoader.LoadTileMatrix(); + + RegionLoader.LoadRegions(); + World.Load(); + + AssemblyHandler.Invoke("Initialize"); + + TcpServer.Start(); + EventSink.InvokeServerStarted(); + RunEventLoop(); + } + + public static void RunEventLoop() + { + try { - try - { #if DEBUG - const bool idleCPU = true; + const bool idleCPU = true; #else var idleCPU = ServerConfiguration.GetOrUpdateSetting("core.enableIdleCPU", false); #endif - long last = TickCount; - const int interval = 100; - const float ticksPerSecond = 1000 * interval; + long last = TickCount; + const int interval = 100; + const float ticksPerSecond = 1000 * interval; - int sample = 0; + int sample = 0; - while (!Closing) + while (!Closing) + { + _tickCount = TickCount; + _now = DateTime.UtcNow; + + Mobile.ProcessDeltaQueue(); + Item.ProcessDeltaQueue(); + Timer.Slice(_tickCount); + + // Handle networking + TcpServer.Slice(); + NetState.Slice(); + + // Execute captured post-await methods (like Timer.Pause) + LoopContext.ExecuteTasks(); + + Timer.CheckTimerPool(); // Check for pool depletion so we can async refill it. + + _tickCount = 0; + _now = DateTime.MinValue; + + if (idleCPU && ++sample % interval == 0) { - _tickCount = TickCount; - _now = DateTime.UtcNow; + var now = TickCount; - Mobile.ProcessDeltaQueue(); - Item.ProcessDeltaQueue(); - Timer.Slice(_tickCount); + var cyclesPerSecond = ticksPerSecond / (now - last); + _cyclesPerSecond[_cycleIndex++ % _cyclesPerSecond.Length] = cyclesPerSecond; + last = now; - // Handle networking - TcpServer.Slice(); - NetState.Slice(); - - // Execute captured post-await methods (like Timer.Pause) - LoopContext.ExecuteTasks(); - - Timer.CheckTimerPool(); // Check for pool depletion so we can async refill it. - - _tickCount = 0; - _now = DateTime.MinValue; - - if (idleCPU && ++sample % interval == 0) + if (cyclesPerSecond > 80) { - var now = TickCount; - - var cyclesPerSecond = ticksPerSecond / (now - last); - _cyclesPerSecond[_cycleIndex++ % _cyclesPerSecond.Length] = cyclesPerSecond; - last = now; - - if (cyclesPerSecond > 80) - { - Thread.Sleep(2); - } + Thread.Sleep(2); } } } - catch (Exception e) - { - CurrentDomain_UnhandledException(null, new UnhandledExceptionEventArgs(e, true)); - } - } - - public static void VerifySerialization() - { - _itemCount = 0; - _mobileCount = 0; - - var callingAssembly = Assembly.GetCallingAssembly(); - - VerifySerialization(callingAssembly); - - foreach (var assembly in AssemblyHandler.Assemblies) - { - if (assembly != callingAssembly) - { - VerifySerialization(assembly); - } - } } - - private static void VerifyType(Type type) + catch (Exception e) { - if (!type.IsAssignableTo(typeof(ISerializable)) || type.IsInterface || type.IsAbstract) - { - return; - } - - if (type.IsSubclassOf(typeof(Item))) - { - Interlocked.Increment(ref _itemCount); - } - else if (type.IsSubclassOf(typeof(Mobile))) - { - Interlocked.Increment(ref _mobileCount); - } - - using var errors = ValueStringBuilder.CreateMT(); - - try - { - if (World.DirtyTrackingEnabled) - { - var manualDirtyCheckingAttribute = type.GetCustomAttribute(false); - var codeGennedAttribute = type.GetCustomAttribute(false); - - if (manualDirtyCheckingAttribute == null && codeGennedAttribute == null) - { - errors.AppendLine(" - No property tracking (dirty checking)"); - } - } - - if (type.GetConstructor(_serialTypeArray) == null) - { - errors.AppendLine(" - No serialization constructor"); - } - - const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.DeclaredOnly; - - var hasSerializeMethod = false; - var hasDeserializeMethod = false; - - foreach (var method in type.GetMethods(bindingFlags)) - { - if (method.Name == "Serialize") - { - hasSerializeMethod = true; - } - - if (method.Name == "Deserialize") - { - var parameters = method.GetParameters(); - if (parameters.Length == 1 && parameters[0].ParameterType == typeof(IGenericReader)) - { - hasDeserializeMethod = true; - } - } - } - - if (!hasSerializeMethod) - { - errors.AppendLine(" - No Serialize() method"); - } - - if (!hasDeserializeMethod) - { - errors.AppendLine(" - No Deserialize() method"); - } - - if (errors.Length > 0) - { - Utility.PushColor(ConsoleColor.Red); - Console.WriteLine($"{type}\n{errors.ToString()}"); - Utility.PopColor(); - } - } - catch (AmbiguousMatchException e) - { - // ignored - } - catch - { - Console.WriteLine("Warning: Exception in serialization verification of type {0}", type); - } + CurrentDomain_UnhandledException(null, new UnhandledExceptionEventArgs(e, true)); } + } - private static void VerifySerialization(Assembly assembly) + public static void VerifySerialization() + { + _itemCount = 0; + _mobileCount = 0; + + var callingAssembly = Assembly.GetCallingAssembly(); + + VerifySerialization(callingAssembly); + + foreach (var assembly in AssemblyHandler.Assemblies) { - if (assembly != null) + if (assembly != callingAssembly) { - Parallel.ForEach(assembly.GetTypes(), VerifyType); + VerifySerialization(assembly); } } } + + private static void VerifyType(Type type) + { + if (!type.IsAssignableTo(typeof(ISerializable)) || type.IsInterface || type.IsAbstract) + { + return; + } + + if (type.IsSubclassOf(typeof(Item))) + { + Interlocked.Increment(ref _itemCount); + } + else if (type.IsSubclassOf(typeof(Mobile))) + { + Interlocked.Increment(ref _mobileCount); + } + + using var errors = ValueStringBuilder.CreateMT(); + + try + { + if (World.DirtyTrackingEnabled) + { + var manualDirtyCheckingAttribute = type.GetCustomAttribute(false); + var codeGennedAttribute = type.GetCustomAttribute(false); + + if (manualDirtyCheckingAttribute == null && codeGennedAttribute == null) + { + errors.AppendLine(" - No property tracking (dirty checking)"); + } + } + + if (type.GetConstructor(_serialTypeArray) == null) + { + errors.AppendLine(" - No serialization constructor"); + } + + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.DeclaredOnly; + + var hasSerializeMethod = false; + var hasDeserializeMethod = false; + + foreach (var method in type.GetMethods(bindingFlags)) + { + if (method.Name == "Serialize") + { + hasSerializeMethod = true; + } + + if (method.Name == "Deserialize") + { + var parameters = method.GetParameters(); + if (parameters.Length == 1 && parameters[0].ParameterType == typeof(IGenericReader)) + { + hasDeserializeMethod = true; + } + } + } + + if (!hasSerializeMethod) + { + errors.AppendLine(" - No Serialize() method"); + } + + if (!hasDeserializeMethod) + { + errors.AppendLine(" - No Deserialize() method"); + } + + if (errors.Length > 0) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine($"{type}\n{errors.ToString()}"); + Utility.PopColor(); + } + } + catch (AmbiguousMatchException e) + { + // ignored + } + catch + { + Console.WriteLine("Warning: Exception in serialization verification of type {0}", type); + } + } + + private static void VerifySerialization(Assembly assembly) + { + if (assembly != null) + { + Parallel.ForEach(assembly.GetTypes(), VerifyType); + } + } } diff --git a/Projects/Server/Maps/MapLoader.cs b/Projects/Server/Maps/MapLoader.cs index a335d5799..d90c98af3 100644 --- a/Projects/Server/Maps/MapLoader.cs +++ b/Projects/Server/Maps/MapLoader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MapLoader.cs * * * @@ -22,125 +22,124 @@ using System.Text.Json.Serialization; using Server.Json; using Server.Logging; -namespace Server +namespace Server; + +public static class MapLoader { - public static class MapLoader + private static readonly ILogger logger = LogFactory.GetLogger(typeof(MapLoader)); + + /* Here we configure all maps. Some notes: + * + * 1) The first 32 maps are reserved for core use. + * 2) Map 127 is reserved for core use. + * 3) Map 255 is reserved for core use. + * 4) Changing or removing any predefined maps may cause server instability. + * + * Map definitions are modified in Data/Map Definitions/.json: + * - : An unreserved unique index for this map + * - : An identification number used in client communications. For any visible maps, this value must be from 0-5 + * - : A file identification number. For any visible maps, this value must be from 0-5 + * - , : Size of the map (in tiles) + * - : Season of the map. 0 = Spring, 1 = Summer, 2 = Fall, 3 = Winter, 4 = Desolation + * - : Reference name for the map, used in props gump, get/set commands, region loading, etc + * - : Rules and restrictions associated with the map. See documentation for details + */ + [CallPriority(2)] + public static void Configure() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(MapLoader)); + var failures = new List(); + var count = 0; - /* Here we configure all maps. Some notes: - * - * 1) The first 32 maps are reserved for core use. - * 2) Map 127 is reserved for core use. - * 3) Map 255 is reserved for core use. - * 4) Changing or removing any predefined maps may cause server instability. - * - * Map definitions are modified in Data/Map Definitions/.json: - * - : An unreserved unique index for this map - * - : An identification number used in client communications. For any visible maps, this value must be from 0-5 - * - : A file identification number. For any visible maps, this value must be from 0-5 - * - , : Size of the map (in tiles) - * - : Season of the map. 0 = Spring, 1 = Summer, 2 = Fall, 3 = Winter, 4 = Desolation - * - : Reference name for the map, used in props gump, get/set commands, region loading, etc - * - : Rules and restrictions associated with the map. See documentation for details - */ - [CallPriority(2)] - public static void Configure() + var path = Path.Combine(Core.BaseDirectory, "Data/map-definitions.json"); + + logger.Information("Loading Map Definitions"); + + var stopwatch = Stopwatch.StartNew(); + var maps = JsonConfig.Deserialize>(path); + if (maps == null) { - var failures = new List(); - var count = 0; + throw new JsonException($"Failed to deserialize {path}."); + } - var path = Path.Combine(Core.BaseDirectory, "Data/map-definitions.json"); - - logger.Information("Loading Map Definitions"); - - var stopwatch = Stopwatch.StartNew(); - var maps = JsonConfig.Deserialize>(path); - if (maps == null) + foreach (var def in maps) + { + try { - throw new JsonException($"Failed to deserialize {path}."); + RegisterMap(def); + count++; } - - foreach (var def in maps) + catch (Exception ex) { - try - { - RegisterMap(def); - count++; - } - catch (Exception ex) - { - logger.Debug(ex, "Failed to load map definition {MapDefName} ({MapDefId})", def.Name, def.Id); - failures.Add($"\tInvalid map definition {def.Name} ({def.Id})"); - } - } - - stopwatch.Stop(); - - if (failures.Count > 0) - { - logger.Warning( - "Map Definitions loaded with failures ({Count} maps, {FailureCount} failures) ({Duration:F2} seconds)", - count, - failures.Count, - stopwatch.Elapsed.TotalSeconds - ); - - logger.Warning("Map load failures: {Failure}", failures); - } - else - { - logger.Information( - "Map Definitions loaded successfully ({Count} maps, {FailureCount} failures) ({Duration:F2} seconds)", - count, - failures.Count, - stopwatch.Elapsed.TotalSeconds - ); + logger.Debug(ex, "Failed to load map definition {MapDefName} ({MapDefId})", def.Name, def.Id); + failures.Add($"\tInvalid map definition {def.Name} ({def.Id})"); } } - private static void RegisterMap(MapDefinition mapDefinition) + stopwatch.Stop(); + + if (failures.Count > 0) { - var newMap = new Map( - mapDefinition.Id, - mapDefinition.Index, - mapDefinition.FileIndex, - Math.Max(mapDefinition.Width, Map.SectorSize), - Math.Max(mapDefinition.Height, Map.SectorSize), - mapDefinition.Season, - mapDefinition.Name, - mapDefinition.Rules + logger.Warning( + "Map Definitions loaded with failures ({Count} maps, {FailureCount} failures) ({Duration:F2} seconds)", + count, + failures.Count, + stopwatch.Elapsed.TotalSeconds ); - Map.Maps[mapDefinition.Index] = newMap; - Map.AllMaps.Add(newMap); + logger.Warning("Map load failures: {Failure}", failures); } - - internal class MapDefinition + else { - [JsonPropertyName("index")] - public int Index { get; set; } - - [JsonPropertyName("id")] - public int Id { get; set; } - - [JsonPropertyName("fileIndex")] - public int FileIndex { get; set; } - - [JsonPropertyName("name")] - public string Name { get; set; } - - [JsonPropertyName("width")] - public int Width { get; set; } - - [JsonPropertyName("height")] - public int Height { get; set; } - - [JsonPropertyName("season")] - public int Season { get; set; } - - [JsonPropertyName("rules")] - public MapRules Rules { get; set; } + logger.Information( + "Map Definitions loaded successfully ({Count} maps, {FailureCount} failures) ({Duration:F2} seconds)", + count, + failures.Count, + stopwatch.Elapsed.TotalSeconds + ); } } + + private static void RegisterMap(MapDefinition mapDefinition) + { + var newMap = new Map( + mapDefinition.Id, + mapDefinition.Index, + mapDefinition.FileIndex, + Math.Max(mapDefinition.Width, Map.SectorSize), + Math.Max(mapDefinition.Height, Map.SectorSize), + mapDefinition.Season, + mapDefinition.Name, + mapDefinition.Rules + ); + + Map.Maps[mapDefinition.Index] = newMap; + Map.AllMaps.Add(newMap); + } + + internal class MapDefinition + { + [JsonPropertyName("index")] + public int Index { get; set; } + + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("fileIndex")] + public int FileIndex { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("width")] + public int Width { get; set; } + + [JsonPropertyName("height")] + public int Height { get; set; } + + [JsonPropertyName("season")] + public int Season { get; set; } + + [JsonPropertyName("rules")] + public MapRules Rules { get; set; } + } } diff --git a/Projects/Server/Menus/IMenu.cs b/Projects/Server/Menus/IMenu.cs index bb007d7f0..a3410560b 100644 --- a/Projects/Server/Menus/IMenu.cs +++ b/Projects/Server/Menus/IMenu.cs @@ -1,13 +1,12 @@ using Server.Network; -namespace Server.Menus +namespace Server.Menus; + +public interface IMenu { - public interface IMenu - { - int Serial { get; } - int EntryLength { get; } - void SendTo(NetState state); - void OnCancel(NetState state); - void OnResponse(NetState state, int index); - } + int Serial { get; } + int EntryLength { get; } + void SendTo(NetState state); + void OnCancel(NetState state); + void OnResponse(NetState state, int index); } diff --git a/Projects/Server/Menus/ItemListMenu.cs b/Projects/Server/Menus/ItemListMenu.cs index 2b9983c65..6c5422fa0 100644 --- a/Projects/Server/Menus/ItemListMenu.cs +++ b/Projects/Server/Menus/ItemListMenu.cs @@ -1,61 +1,60 @@ using Server.Network; -namespace Server.Menus.ItemLists +namespace Server.Menus.ItemLists; + +public class ItemListEntry { - public class ItemListEntry + public ItemListEntry(string name, int itemID, int hue = 0) { - public ItemListEntry(string name, int itemID, int hue = 0) - { - Name = name?.Trim() ?? ""; - ItemID = itemID; - Hue = hue; - } - - public string Name { get; } - - public int ItemID { get; } - - public int Hue { get; } + Name = name?.Trim() ?? ""; + ItemID = itemID; + Hue = hue; } - public class ItemListMenu : IMenu + public string Name { get; } + + public int ItemID { get; } + + public int Hue { get; } +} + +public class ItemListMenu : IMenu +{ + private static int m_NextSerial; + + public ItemListMenu(string question, ItemListEntry[] entries) { - private static int m_NextSerial; + Question = question.Trim(); + Entries = entries; - public ItemListMenu(string question, ItemListEntry[] entries) + do { - Question = question.Trim(); - Entries = entries; + Serial = m_NextSerial++; + Serial &= 0x7FFFFFFF; + } while (Serial == 0); - do - { - Serial = m_NextSerial++; - Serial &= 0x7FFFFFFF; - } while (Serial == 0); + Serial = (int)((uint)Serial | 0x80000000); + } - Serial = (int)((uint)Serial | 0x80000000); - } + public string Question { get; } - public string Question { get; } + public ItemListEntry[] Entries { get; set; } - public ItemListEntry[] Entries { get; set; } + public int Serial { get; } - public int Serial { get; } + public int EntryLength => Entries.Length; - public int EntryLength => Entries.Length; + public virtual void OnCancel(NetState state) + { + } - public virtual void OnCancel(NetState state) - { - } + public virtual void OnResponse(NetState state, int index) + { + } - public virtual void OnResponse(NetState state, int index) - { - } - - public void SendTo(NetState state) - { - state.AddMenu(this); - state.SendDisplayItemListMenu(this); - } + public void SendTo(NetState state) + { + state.AddMenu(this); + state.SendDisplayItemListMenu(this); } } diff --git a/Projects/Server/Menus/QuestionMenu.cs b/Projects/Server/Menus/QuestionMenu.cs index aa6beb7aa..a1a9acfec 100644 --- a/Projects/Server/Menus/QuestionMenu.cs +++ b/Projects/Server/Menus/QuestionMenu.cs @@ -1,43 +1,42 @@ using Server.Network; -namespace Server.Menus.Questions +namespace Server.Menus.Questions; + +public class QuestionMenu : IMenu { - public class QuestionMenu : IMenu + private static int m_NextSerial; + + public QuestionMenu(string question, string[] answers) { - private static int m_NextSerial; + Question = question?.Trim() ?? ""; + Answers = answers; - public QuestionMenu(string question, string[] answers) + do { - Question = question?.Trim() ?? ""; - Answers = answers; + Serial = ++m_NextSerial; + Serial &= 0x7FFFFFFF; + } while (Serial == 0); + } - do - { - Serial = ++m_NextSerial; - Serial &= 0x7FFFFFFF; - } while (Serial == 0); - } + public string Question { get; } - public string Question { get; } + public string[] Answers { get; } - public string[] Answers { get; } + public int Serial { get; } - public int Serial { get; } + public int EntryLength => Answers.Length; - public int EntryLength => Answers.Length; + public virtual void OnCancel(NetState state) + { + } - public virtual void OnCancel(NetState state) - { - } + public virtual void OnResponse(NetState state, int index) + { + } - public virtual void OnResponse(NetState state, int index) - { - } - - public void SendTo(NetState state) - { - state.AddMenu(this); - state.SendDisplayQuestionMenu(this); - } + public void SendTo(NetState state) + { + state.AddMenu(this); + state.SendDisplayQuestionMenu(this); } } diff --git a/Projects/Server/Mobiles/AggressorInfo.cs b/Projects/Server/Mobiles/AggressorInfo.cs index 1a4b72d52..f63ffbc4c 100644 --- a/Projects/Server/Mobiles/AggressorInfo.cs +++ b/Projects/Server/Mobiles/AggressorInfo.cs @@ -3,205 +3,204 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -namespace Server +namespace Server; + +public class AggressorInfo { - public class AggressorInfo + private static readonly Queue m_Pool = new(); + private Mobile m_Attacker, m_Defender; + private bool m_CanReportMurder; + private bool m_CriminalAggression; + private DateTime m_LastCombatTime; + + private bool m_Queued; + private bool m_Reported; + + private AggressorInfo(Mobile attacker, Mobile defender, bool criminal) { - private static readonly Queue m_Pool = new(); - private Mobile m_Attacker, m_Defender; - private bool m_CanReportMurder; - private bool m_CriminalAggression; - private DateTime m_LastCombatTime; + m_Attacker = attacker; + m_Defender = defender; - private bool m_Queued; - private bool m_Reported; + m_CanReportMurder = criminal; + m_CriminalAggression = criminal; - private AggressorInfo(Mobile attacker, Mobile defender, bool criminal) - { - m_Attacker = attacker; - m_Defender = defender; + Refresh(); + } - m_CanReportMurder = criminal; - m_CriminalAggression = criminal; + public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); - Refresh(); - } - - public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); - - public bool Expired - { - get - { - if (m_Queued) - { - DumpAccess(); - } - - return m_Attacker.Deleted || m_Defender.Deleted || Core.Now >= m_LastCombatTime + ExpireDelay; - } - } - - public bool CriminalAggression - { - get - { - if (m_Queued) - { - DumpAccess(); - } - - return m_CriminalAggression; - } - set - { - if (m_Queued) - { - DumpAccess(); - } - - m_CriminalAggression = value; - } - } - - public Mobile Attacker - { - get - { - if (m_Queued) - { - DumpAccess(); - } - - return m_Attacker; - } - } - - public Mobile Defender - { - get - { - if (m_Queued) - { - DumpAccess(); - } - - return m_Defender; - } - } - - public DateTime LastCombatTime - { - get - { - if (m_Queued) - { - DumpAccess(); - } - - return m_LastCombatTime; - } - } - - public bool Reported - { - get - { - if (m_Queued) - { - DumpAccess(); - } - - return m_Reported; - } - set - { - if (m_Queued) - { - DumpAccess(); - } - - m_Reported = value; - } - } - - public bool CanReportMurder - { - get - { - if (m_Queued) - { - DumpAccess(); - } - - return m_CanReportMurder; - } - set - { - if (m_Queued) - { - DumpAccess(); - } - - m_CanReportMurder = value; - } - } - - public static AggressorInfo Create(Mobile attacker, Mobile defender, bool criminal) - { - AggressorInfo info; - - if (m_Pool.Count > 0) - { - info = m_Pool.Dequeue(); - - info.m_Attacker = attacker; - info.m_Defender = defender; - - info.m_CanReportMurder = criminal; - info.m_CriminalAggression = criminal; - - info.m_Queued = false; - - info.Refresh(); - } - else - { - info = new AggressorInfo(attacker, defender, criminal); - } - - return info; - } - - public void Free() - { - if (m_Queued) - { - return; - } - - m_Queued = true; - m_Pool.Enqueue(this); - } - - public static void DumpAccess() - { - using var op = new StreamWriter("warnings.log", true); - op.WriteLine("Warning: Access to queued AggressorInfo:"); - op.WriteLine(new StackTrace()); - op.WriteLine(); - op.WriteLine(); - } - - public void Refresh() + public bool Expired + { + get { if (m_Queued) { DumpAccess(); } - m_LastCombatTime = Core.Now; - m_Reported = false; + return m_Attacker.Deleted || m_Defender.Deleted || Core.Now >= m_LastCombatTime + ExpireDelay; } } + + public bool CriminalAggression + { + get + { + if (m_Queued) + { + DumpAccess(); + } + + return m_CriminalAggression; + } + set + { + if (m_Queued) + { + DumpAccess(); + } + + m_CriminalAggression = value; + } + } + + public Mobile Attacker + { + get + { + if (m_Queued) + { + DumpAccess(); + } + + return m_Attacker; + } + } + + public Mobile Defender + { + get + { + if (m_Queued) + { + DumpAccess(); + } + + return m_Defender; + } + } + + public DateTime LastCombatTime + { + get + { + if (m_Queued) + { + DumpAccess(); + } + + return m_LastCombatTime; + } + } + + public bool Reported + { + get + { + if (m_Queued) + { + DumpAccess(); + } + + return m_Reported; + } + set + { + if (m_Queued) + { + DumpAccess(); + } + + m_Reported = value; + } + } + + public bool CanReportMurder + { + get + { + if (m_Queued) + { + DumpAccess(); + } + + return m_CanReportMurder; + } + set + { + if (m_Queued) + { + DumpAccess(); + } + + m_CanReportMurder = value; + } + } + + public static AggressorInfo Create(Mobile attacker, Mobile defender, bool criminal) + { + AggressorInfo info; + + if (m_Pool.Count > 0) + { + info = m_Pool.Dequeue(); + + info.m_Attacker = attacker; + info.m_Defender = defender; + + info.m_CanReportMurder = criminal; + info.m_CriminalAggression = criminal; + + info.m_Queued = false; + + info.Refresh(); + } + else + { + info = new AggressorInfo(attacker, defender, criminal); + } + + return info; + } + + public void Free() + { + if (m_Queued) + { + return; + } + + m_Queued = true; + m_Pool.Enqueue(this); + } + + public static void DumpAccess() + { + using var op = new StreamWriter("warnings.log", true); + op.WriteLine("Warning: Access to queued AggressorInfo:"); + op.WriteLine(new StackTrace()); + op.WriteLine(); + op.WriteLine(); + } + + public void Refresh() + { + if (m_Queued) + { + DumpAccess(); + } + + m_LastCombatTime = Core.Now; + m_Reported = false; + } } diff --git a/Projects/Server/Mobiles/BaseVendor.cs b/Projects/Server/Mobiles/BaseVendor.cs index 467708ee1..fe6230d65 100644 --- a/Projects/Server/Mobiles/BaseVendor.cs +++ b/Projects/Server/Mobiles/BaseVendor.cs @@ -1,97 +1,96 @@ using System.Collections.Generic; -namespace Server +namespace Server; + +public class BuyItemStateComparer : IComparer { - public class BuyItemStateComparer : IComparer + public int Compare(BuyItemState l, BuyItemState r) { - public int Compare(BuyItemState l, BuyItemState r) + if (l == null && r == null) { - if (l == null && r == null) - { - return 0; - } - - if (l == null) - { - return -1; - } - - if (r == null) - { - return 1; - } - - return l.MySerial.CompareTo(r.MySerial); - } - } - - public class BuyItemResponse - { - public BuyItemResponse(Serial serial, int amount) - { - Serial = serial; - Amount = amount; + return 0; } - public Serial Serial { get; } - - public int Amount { get; } - } - - public class SellItemResponse - { - public SellItemResponse(Item i, int amount) + if (l == null) { - Item = i; - Amount = amount; + return -1; } - public Item Item { get; } - - public int Amount { get; } - } - - public class SellItemState - { - public SellItemState(Item item, int price, string name) + if (r == null) { - Item = item; - Price = price; - Name = name; + return 1; } - public Item Item { get; } - - public int Price { get; } - - public string Name { get; } - } - - public class BuyItemState - { - public BuyItemState(string name, Serial cont, Serial serial, int price, int amount, int itemID, int hue) - { - Description = name; - ContainerSerial = cont; - MySerial = serial; - Price = price; - Amount = amount; - ItemID = itemID; - Hue = hue; - } - - public int Price { get; } - - public Serial MySerial { get; } - - public Serial ContainerSerial { get; } - - public int ItemID { get; } - - public int Amount { get; } - - public int Hue { get; } - - public string Description { get; } + return l.MySerial.CompareTo(r.MySerial); } } + +public class BuyItemResponse +{ + public BuyItemResponse(Serial serial, int amount) + { + Serial = serial; + Amount = amount; + } + + public Serial Serial { get; } + + public int Amount { get; } +} + +public class SellItemResponse +{ + public SellItemResponse(Item i, int amount) + { + Item = i; + Amount = amount; + } + + public Item Item { get; } + + public int Amount { get; } +} + +public class SellItemState +{ + public SellItemState(Item item, int price, string name) + { + Item = item; + Price = price; + Name = name; + } + + public Item Item { get; } + + public int Price { get; } + + public string Name { get; } +} + +public class BuyItemState +{ + public BuyItemState(string name, Serial cont, Serial serial, int price, int amount, int itemID, int hue) + { + Description = name; + ContainerSerial = cont; + MySerial = serial; + Price = price; + Amount = amount; + ItemID = itemID; + Hue = hue; + } + + public int Price { get; } + + public Serial MySerial { get; } + + public Serial ContainerSerial { get; } + + public int ItemID { get; } + + public int Amount { get; } + + public int Hue { get; } + + public string Description { get; } +} diff --git a/Projects/Server/Mobiles/IMount.cs b/Projects/Server/Mobiles/IMount.cs index 8590668a3..7c35188c6 100644 --- a/Projects/Server/Mobiles/IMount.cs +++ b/Projects/Server/Mobiles/IMount.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IMount.cs * * * @@ -13,16 +13,15 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server.Mobiles -{ - public interface IMount - { - Mobile Rider { get; set; } - void OnRiderDamaged(int amount, Mobile from, bool willKill); - } +namespace Server.Mobiles; - public interface IMountItem - { - IMount Mount { get; } - } +public interface IMount +{ + Mobile Rider { get; set; } + void OnRiderDamaged(int amount, Mobile from, bool willKill); +} + +public interface IMountItem +{ + IMount Mount { get; } } diff --git a/Projects/Server/Mobiles/IVendor.cs b/Projects/Server/Mobiles/IVendor.cs index 2eb842599..1bbd95d14 100644 --- a/Projects/Server/Mobiles/IVendor.cs +++ b/Projects/Server/Mobiles/IVendor.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IVendor.cs * * * diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index 7a8291fde..6425b9e4f 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -18,1207 +18,1207 @@ using Server.Targeting; using Server.Utilities; using CalcMoves = Server.Movement.Movement; -namespace Server +namespace Server; + +public delegate void TargetCallback(Mobile from, object targeted); + +public delegate void TargetStateCallback(Mobile from, object targeted, T state); + +public delegate void PromptCallback(Mobile from, string text); + +public delegate void PromptStateCallback(Mobile from, string text, T state); + +public class DamageEntry { - public delegate void TargetCallback(Mobile from, object targeted); + public DamageEntry(Mobile damager) => Damager = damager; - public delegate void TargetStateCallback(Mobile from, object targeted, T state); + public Mobile Damager { get; } - public delegate void PromptCallback(Mobile from, string text); + public int DamageGiven { get; set; } - public delegate void PromptStateCallback(Mobile from, string text, T state); + public DateTime LastDamage { get; set; } - public class DamageEntry + public bool HasExpired => Core.Now > LastDamage + ExpireDelay; + + public List Responsible { get; set; } + + public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); +} + +[Flags] +public enum StatType +{ + Str = 1, + Dex = 2, + Int = 4, + All = 7 +} + +public enum StatLockType : byte +{ + Up, + Down, + Locked +} + +[Flags] +[CustomEnum(new[] { "North", "Right", "East", "Down", "South", "Left", "West", "Up" })] +public enum Direction : byte +{ + North = 0x0, + Right = 0x1, + East = 0x2, + Down = 0x3, + South = 0x4, + Left = 0x5, + West = 0x6, + Up = 0x7, + + Mask = 0x7, + Running = 0x80, + ValueMask = 0x87 +} + +[Flags] +public enum MobileDelta +{ + None = 0x00000000, + Name = 0x00000001, + Flags = 0x00000002, + Hits = 0x00000004, + Mana = 0x00000008, + Stam = 0x00000010, + Stat = 0x00000020, + Noto = 0x00000040, + Gold = 0x00000080, + Weight = 0x00000100, + Direction = 0x00000200, + Hue = 0x00000400, + Body = 0x00000800, + Armor = 0x00001000, + StatCap = 0x00002000, + GhostUpdate = 0x00004000, + Followers = 0x00008000, + Properties = 0x00010000, + TithingPoints = 0x00020000, + Resistances = 0x00040000, + WeaponDamage = 0x00080000, + Hair = 0x00100000, + FacialHair = 0x00200000, + Race = 0x00400000, + HealthbarYellow = 0x00800000, + HealthbarPoison = 0x01000000, + + Attributes = 0x0000001C +} + +public enum Healthbar +{ + Normal, + Poison, + Yellow +} + +public enum AccessLevel +{ + Player, + Counselor, + GameMaster, + Seer, + Administrator, + Developer, + Owner +} + +public enum VisibleDamageType +{ + None, + Related, + Everyone, + Selective +} + +public enum ResistanceType +{ + Physical, + Fire, + Cold, + Poison, + Energy +} + +public enum ApplyPoisonResult +{ + Poisoned, + Immune, + HigherPoisonActive, + Cured +} + +public delegate bool SkillCheckTargetHandler( + Mobile from, SkillName skill, object target, double minSkill, + double maxSkill +); + +public delegate bool SkillCheckLocationHandler(Mobile from, SkillName skill, double minSkill, double maxSkill); + +public delegate bool SkillCheckDirectTargetHandler(Mobile from, SkillName skill, object target, double chance); + +public delegate bool SkillCheckDirectLocationHandler(Mobile from, SkillName skill, double chance); + +public delegate TimeSpan RegenRateHandler(Mobile from); + +public delegate bool AllowBeneficialHandler(Mobile from, Mobile target); + +public delegate bool AllowHarmfulHandler(Mobile from, Mobile target); + +public delegate Container CreateCorpseHandler( + Mobile from, HairInfo hair, FacialHairInfo facialhair, List initialContent, List equippedItems +); + +public delegate int AOSStatusHandler(Mobile from, int index); + +/// +/// Base class representing players, npcs, and creatures. +/// +public class Mobile : IHued, IComparable, ISpawnable, IObjectPropertyListEntity +{ + // Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds + private const int WarmodeCatchCount = 4; + + private static readonly ILogger logger = LogFactory.GetLogger(typeof(Mobile)); + + // TODO: Make these configurations + private static readonly TimeSpan WarmodeSpamCatch = TimeSpan.FromSeconds(Core.SE ? 1.0 : 0.5); + private static readonly TimeSpan WarmodeSpamDelay = TimeSpan.FromSeconds(Core.SE ? 4.0 : 2.0); + private static readonly TimeSpan ExpireCombatantDelay = TimeSpan.FromMinutes(1.0); + private static readonly TimeSpan ExpireAggressorsDelay = TimeSpan.FromSeconds(5.0); + + private static readonly List m_MoveList = new(); + private static readonly List m_MoveClientList = new(); + + private static readonly object m_GhostMutateContext = new(); + + private static readonly List m_Hears = new(); + private static readonly List m_OnSpeech = new(); + + private static readonly string[] m_AccessLevelNames = { - public DamageEntry(Mobile damager) => Damager = damager; + "a player", + "a counselor", + "a game master", + "a seer", + "an administrator", + "a developer", + "an owner" + }; - public Mobile Damager { get; } + private static readonly int[] m_InvalidBodies = + { + 32, + 95, + 156, + 197, + 198 + }; - public int DamageGiven { get; set; } + private static readonly Queue m_DeltaQueue = new(); - public DateTime LastDamage { get; set; } + private static readonly string[] m_GuildTypes = + { + "", + " (Chaos)", + " (Order)" + }; - public bool HasExpired => Core.Now > LastDamage + ExpireDelay; + private List _actions; + private AccessLevel m_AccessLevel; - public List Responsible { get; set; } + private TimerExecutionToken _autoManifestTimerToken; - public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); + private Container m_Backpack; + + private BankBox m_BankBox; + private Body m_Body; + private Body m_BodyMod; + private bool m_CanHearGhosts; + + private int m_ChangingCombatant; + private Mobile m_Combatant; + private TimerExecutionToken _combatTimerToken; + private ContextMenu m_ContextMenu; + private bool m_Criminal; + + private MobileDelta m_DeltaFlags; + private Direction m_Direction; + private bool m_DisplayGuildTitle; + + private TimerExecutionToken _expireAggrTimerToken; + private TimerExecutionToken _expireCombatantTimerToken; + private TimerExecutionToken _expireCriminalTimerToken; + private FacialHairInfo m_FacialHair; + private int m_Fame, m_Karma; + private bool m_Female, m_Warmode, m_Hidden, m_Blessed, m_Flying; + private int m_Followers, m_FollowersMax; + private bool m_Frozen; + private TimerExecutionToken _frozenTimerToken; + private BaseGuild m_Guild; + private string m_GuildTitle; + + private HairInfo m_Hair; + private int m_Hits, m_Stam, m_Mana; + + private Item m_Holding; + private int m_Hue; + + private int m_HueMod = -1; + private int m_Hunger; + + private bool m_InDeltaQueue; + private int m_Kills, m_ShortTermMurders; + private string m_Language; + private int m_LightLevel; + private Point3D m_Location; + private TimerExecutionToken _logoutTimerToken; + private Timer m_ManaTimer, m_HitsTimer, m_StamTimer; + + private Map m_Map; + + /* Logout: + * + * When a client logs into mobile x + * - if (x is Internalized ) move x to logout location and map + * + * When a client attached to a mobile disconnects + * - LogoutTimer is started + * - Delay is taken from Region.GetLogoutDelay to allow insta-logout regions. + * - OnTick : Location and map are stored, and mobile is internalized + * + * Some things to consider: + * - An internalized person getting killed (say, by poison). Where does the body go? + * - Regions now have a GetLogoutDelay( Mobile m ); virtual function (see above) + */ + + private Item m_MountItem; + private string m_Name; + + private string m_NameMod; + private NetState m_NetState; + private DateTime m_NextWarmodeChange; + private bool m_Paralyzed; + private TimerExecutionToken _paraTimerToken; + private bool m_Player; + private Poison m_Poison; + private Prompt m_Prompt; + private ObjectPropertyList m_PropertyList; + private Race m_Race; + private Region m_Region; + + private int m_SolidHueOverride = -1; + private ISpell m_Spell; + private int m_StatCap; + private int m_Str, m_Dex, m_Int; + + private StatLockType m_StrLock, m_DexLock, m_IntLock; + private Target m_Target; + private int m_TithingPoints; + private string m_Title; + private int m_TotalGold, m_TotalItems, m_TotalWeight; + private int m_VirtualArmor; + private int m_VirtualArmorMod; + private int m_WarmodeChanges; + private bool _warmodeSpamValue; + private IWeapon m_Weapon; + + private bool m_YellowHealthbar; + + public Mobile() + { + m_Region = Map.Internal.DefaultRegion; + Serial = World.NewMobile; + + DefaultMobileInit(); + + World.AddEntity(this); + SetTypeRef(GetType()); } - [Flags] - public enum StatType + public Mobile(Serial serial) { - Str = 1, - Dex = 2, - Int = 4, - All = 7 + m_Region = Map.Internal.DefaultRegion; + Serial = serial; + Aggressors = new List(); + Aggressed = new List(); + NextSkillTime = Core.TickCount; + DamageEntries = new List(); + + SetTypeRef(GetType()); } - public enum StatLockType : byte + public void SetTypeRef(Type type) { - Up, - Down, - Locked + TypeRef = World.MobileTypes.IndexOf(type); + + if (TypeRef == -1) + { + World.MobileTypes.Add(type); + TypeRef = World.MobileTypes.Count - 1; + } } - [Flags] - [CustomEnum(new[] { "North", "Right", "East", "Down", "South", "Left", "West", "Up" })] - public enum Direction : byte - { - North = 0x0, - Right = 0x1, - East = 0x2, - Down = 0x3, - South = 0x4, - Left = 0x5, - West = 0x6, - Up = 0x7, + public static bool DragEffects { get; set; } = true; - Mask = 0x7, - Running = 0x80, - ValueMask = 0x87 + [CommandProperty(AccessLevel.GameMaster)] + public Race Race + { + get => m_Race ??= Race.DefaultRace; + set + { + var oldRace = Race; + + m_Race = value ?? Race.DefaultRace; + + Body = m_Race.Body(this); + UpdateResistances(); + + Delta(MobileDelta.Race); + + OnRaceChange(oldRace); + } } - [Flags] - public enum MobileDelta - { - None = 0x00000000, - Name = 0x00000001, - Flags = 0x00000002, - Hits = 0x00000004, - Mana = 0x00000008, - Stam = 0x00000010, - Stat = 0x00000020, - Noto = 0x00000040, - Gold = 0x00000080, - Weight = 0x00000100, - Direction = 0x00000200, - Hue = 0x00000400, - Body = 0x00000800, - Armor = 0x00001000, - StatCap = 0x00002000, - GhostUpdate = 0x00004000, - Followers = 0x00008000, - Properties = 0x00010000, - TithingPoints = 0x00020000, - Resistances = 0x00040000, - WeaponDamage = 0x00080000, - Hair = 0x00100000, - FacialHair = 0x00200000, - Race = 0x00400000, - HealthbarYellow = 0x00800000, - HealthbarPoison = 0x01000000, + public virtual double RacialSkillBonus => 0; - Attributes = 0x0000001C + public int[] Resistances { get; private set; } + + public virtual int BasePhysicalResistance => 0; + public virtual int BaseFireResistance => 0; + public virtual int BaseColdResistance => 0; + public virtual int BasePoisonResistance => 0; + public virtual int BaseEnergyResistance => 0; + + [CommandProperty(AccessLevel.Counselor)] + public virtual int PhysicalResistance => GetResistance(ResistanceType.Physical); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int FireResistance => GetResistance(ResistanceType.Fire); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int ColdResistance => GetResistance(ResistanceType.Cold); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int PoisonResistance => GetResistance(ResistanceType.Poison); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int EnergyResistance => GetResistance(ResistanceType.Energy); + + public List ResistanceMods { get; set; } + + public static int MaxPlayerResistance { get; set; } = 70; + + public virtual bool NewGuildDisplay => false; + + public List Stabled { get; private set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public VirtueInfo Virtues { get; private set; } + + public object Party { get; set; } + + public HashSet SkillMods { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int VirtualArmorMod + { + get => m_VirtualArmorMod; + set + { + if (m_VirtualArmorMod != value) + { + m_VirtualArmorMod = value; + + Delta(MobileDelta.Armor); + } + } } - public enum Healthbar + [CommandProperty(AccessLevel.GameMaster)] + public int MeleeDamageAbsorb { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int MagicDamageAbsorb { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int SkillsTotal => Skills?.Total ?? 0; + + [CommandProperty(AccessLevel.GameMaster)] + public int SkillsCap { - Normal, - Poison, - Yellow + get => Skills?.Cap ?? 0; + set + { + if (Skills != null) + { + Skills.Cap = value; + } + } } - public enum AccessLevel + [CommandProperty(AccessLevel.GameMaster)] + public int BaseSoundID { get; set; } + + public long NextCombatTime { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int NameHue { get; set; } = -1; + + [CommandProperty(AccessLevel.GameMaster)] + public int Hunger { - Player, - Counselor, - GameMaster, - Seer, - Administrator, - Developer, - Owner + get => m_Hunger; + set + { + var oldValue = m_Hunger; + + if (oldValue != value) + { + m_Hunger = value; + + EventSink.InvokeHungerChanged(this, oldValue); + } + } } - public enum VisibleDamageType - { - None, - Related, - Everyone, - Selective - } + [CommandProperty(AccessLevel.GameMaster)] + public int Thirst { get; set; } - public enum ResistanceType - { - Physical, - Fire, - Cold, - Poison, - Energy - } - - public enum ApplyPoisonResult - { - Poisoned, - Immune, - HigherPoisonActive, - Cured - } - - public delegate bool SkillCheckTargetHandler( - Mobile from, SkillName skill, object target, double minSkill, - double maxSkill - ); - - public delegate bool SkillCheckLocationHandler(Mobile from, SkillName skill, double minSkill, double maxSkill); - - public delegate bool SkillCheckDirectTargetHandler(Mobile from, SkillName skill, object target, double chance); - - public delegate bool SkillCheckDirectLocationHandler(Mobile from, SkillName skill, double chance); - - public delegate TimeSpan RegenRateHandler(Mobile from); - - public delegate bool AllowBeneficialHandler(Mobile from, Mobile target); - - public delegate bool AllowHarmfulHandler(Mobile from, Mobile target); - - public delegate Container CreateCorpseHandler( - Mobile from, HairInfo hair, FacialHairInfo facialhair, List initialContent, List equippedItems - ); - - public delegate int AOSStatusHandler(Mobile from, int index); + [CommandProperty(AccessLevel.GameMaster)] + public int BAC { get; set; } /// - /// Base class representing players, npcs, and creatures. + /// Gets or sets the number of steps this player may take when hidden before being revealed. /// - public class Mobile : IHued, IComparable, ISpawnable, IObjectPropertyListEntity + [CommandProperty(AccessLevel.GameMaster)] + public int AllowedStealthSteps { get; set; } + + public Item Holding { - // Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds - private const int WarmodeCatchCount = 4; - - private static readonly ILogger logger = LogFactory.GetLogger(typeof(Mobile)); - - // TODO: Make these configurations - private static readonly TimeSpan WarmodeSpamCatch = TimeSpan.FromSeconds(Core.SE ? 1.0 : 0.5); - private static readonly TimeSpan WarmodeSpamDelay = TimeSpan.FromSeconds(Core.SE ? 4.0 : 2.0); - private static readonly TimeSpan ExpireCombatantDelay = TimeSpan.FromMinutes(1.0); - private static readonly TimeSpan ExpireAggressorsDelay = TimeSpan.FromSeconds(5.0); - - private static readonly List m_MoveList = new(); - private static readonly List m_MoveClientList = new(); - - private static readonly object m_GhostMutateContext = new(); - - private static readonly List m_Hears = new(); - private static readonly List m_OnSpeech = new(); - - private static readonly string[] m_AccessLevelNames = + get => m_Holding; + set { - "a player", - "a counselor", - "a game master", - "a seer", - "an administrator", - "a developer", - "an owner" - }; - - private static readonly int[] m_InvalidBodies = - { - 32, - 95, - 156, - 197, - 198 - }; - - private static readonly Queue m_DeltaQueue = new(); - - private static readonly string[] m_GuildTypes = - { - "", - " (Chaos)", - " (Order)" - }; - - private List _actions; - private AccessLevel m_AccessLevel; - - private TimerExecutionToken _autoManifestTimerToken; - - private Container m_Backpack; - - private BankBox m_BankBox; - private Body m_Body; - private Body m_BodyMod; - private bool m_CanHearGhosts; - - private int m_ChangingCombatant; - private Mobile m_Combatant; - private TimerExecutionToken _combatTimerToken; - private ContextMenu m_ContextMenu; - private bool m_Criminal; - - private MobileDelta m_DeltaFlags; - private Direction m_Direction; - private bool m_DisplayGuildTitle; - - private TimerExecutionToken _expireAggrTimerToken; - private TimerExecutionToken _expireCombatantTimerToken; - private TimerExecutionToken _expireCriminalTimerToken; - private FacialHairInfo m_FacialHair; - private int m_Fame, m_Karma; - private bool m_Female, m_Warmode, m_Hidden, m_Blessed, m_Flying; - private int m_Followers, m_FollowersMax; - private bool m_Frozen; - private TimerExecutionToken _frozenTimerToken; - private BaseGuild m_Guild; - private string m_GuildTitle; - - private HairInfo m_Hair; - private int m_Hits, m_Stam, m_Mana; - - private Item m_Holding; - private int m_Hue; - - private int m_HueMod = -1; - private int m_Hunger; - - private bool m_InDeltaQueue; - private int m_Kills, m_ShortTermMurders; - private string m_Language; - private int m_LightLevel; - private Point3D m_Location; - private TimerExecutionToken _logoutTimerToken; - private Timer m_ManaTimer, m_HitsTimer, m_StamTimer; - - private Map m_Map; - - /* Logout: - * - * When a client logs into mobile x - * - if (x is Internalized ) move x to logout location and map - * - * When a client attached to a mobile disconnects - * - LogoutTimer is started - * - Delay is taken from Region.GetLogoutDelay to allow insta-logout regions. - * - OnTick : Location and map are stored, and mobile is internalized - * - * Some things to consider: - * - An internalized person getting killed (say, by poison). Where does the body go? - * - Regions now have a GetLogoutDelay( Mobile m ); virtual function (see above) - */ - - private Item m_MountItem; - private string m_Name; - - private string m_NameMod; - private NetState m_NetState; - private DateTime m_NextWarmodeChange; - private bool m_Paralyzed; - private TimerExecutionToken _paraTimerToken; - private bool m_Player; - private Poison m_Poison; - private Prompt m_Prompt; - private ObjectPropertyList m_PropertyList; - private Race m_Race; - private Region m_Region; - - private int m_SolidHueOverride = -1; - private ISpell m_Spell; - private int m_StatCap; - private int m_Str, m_Dex, m_Int; - - private StatLockType m_StrLock, m_DexLock, m_IntLock; - private Target m_Target; - private int m_TithingPoints; - private string m_Title; - private int m_TotalGold, m_TotalItems, m_TotalWeight; - private int m_VirtualArmor; - private int m_VirtualArmorMod; - private int m_WarmodeChanges; - private bool _warmodeSpamValue; - private IWeapon m_Weapon; - - private bool m_YellowHealthbar; - - public Mobile() - { - m_Region = Map.Internal.DefaultRegion; - Serial = World.NewMobile; - - DefaultMobileInit(); - - World.AddEntity(this); - SetTypeRef(GetType()); - } - - public Mobile(Serial serial) - { - m_Region = Map.Internal.DefaultRegion; - Serial = serial; - Aggressors = new List(); - Aggressed = new List(); - NextSkillTime = Core.TickCount; - DamageEntries = new List(); - - SetTypeRef(GetType()); - } - - public void SetTypeRef(Type type) - { - TypeRef = World.MobileTypes.IndexOf(type); - - if (TypeRef == -1) + if (m_Holding != value) { - World.MobileTypes.Add(type); - TypeRef = World.MobileTypes.Count - 1; - } - } - - public static bool DragEffects { get; set; } = true; - - [CommandProperty(AccessLevel.GameMaster)] - public Race Race - { - get => m_Race ??= Race.DefaultRace; - set - { - var oldRace = Race; - - m_Race = value ?? Race.DefaultRace; - - Body = m_Race.Body(this); - UpdateResistances(); - - Delta(MobileDelta.Race); - - OnRaceChange(oldRace); - } - } - - public virtual double RacialSkillBonus => 0; - - public int[] Resistances { get; private set; } - - public virtual int BasePhysicalResistance => 0; - public virtual int BaseFireResistance => 0; - public virtual int BaseColdResistance => 0; - public virtual int BasePoisonResistance => 0; - public virtual int BaseEnergyResistance => 0; - - [CommandProperty(AccessLevel.Counselor)] - public virtual int PhysicalResistance => GetResistance(ResistanceType.Physical); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int FireResistance => GetResistance(ResistanceType.Fire); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int ColdResistance => GetResistance(ResistanceType.Cold); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int PoisonResistance => GetResistance(ResistanceType.Poison); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int EnergyResistance => GetResistance(ResistanceType.Energy); - - public List ResistanceMods { get; set; } - - public static int MaxPlayerResistance { get; set; } = 70; - - public virtual bool NewGuildDisplay => false; - - public List Stabled { get; private set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public VirtueInfo Virtues { get; private set; } - - public object Party { get; set; } - - public HashSet SkillMods { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int VirtualArmorMod - { - get => m_VirtualArmorMod; - set - { - if (m_VirtualArmorMod != value) + if (m_Holding != null) { - m_VirtualArmorMod = value; + UpdateTotal(m_Holding, TotalType.Weight, -(m_Holding.TotalWeight + m_Holding.PileWeight)); - Delta(MobileDelta.Armor); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int MeleeDamageAbsorb { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int MagicDamageAbsorb { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int SkillsTotal => Skills?.Total ?? 0; - - [CommandProperty(AccessLevel.GameMaster)] - public int SkillsCap - { - get => Skills?.Cap ?? 0; - set - { - if (Skills != null) - { - Skills.Cap = value; - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int BaseSoundID { get; set; } - - public long NextCombatTime { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int NameHue { get; set; } = -1; - - [CommandProperty(AccessLevel.GameMaster)] - public int Hunger - { - get => m_Hunger; - set - { - var oldValue = m_Hunger; - - if (oldValue != value) - { - m_Hunger = value; - - EventSink.InvokeHungerChanged(this, oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Thirst { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int BAC { get; set; } - - /// - /// Gets or sets the number of steps this player may take when hidden before being revealed. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int AllowedStealthSteps { get; set; } - - public Item Holding - { - get => m_Holding; - set - { - if (m_Holding != value) - { - if (m_Holding != null) + if (m_Holding.HeldBy == this) { - UpdateTotal(m_Holding, TotalType.Weight, -(m_Holding.TotalWeight + m_Holding.PileWeight)); - - if (m_Holding.HeldBy == this) - { - m_Holding.HeldBy = null; - } - } - - if (value != null && m_Holding != null) - { - DropHolding(); - } - - m_Holding = value; - - if (m_Holding != null) - { - UpdateTotal(m_Holding, TotalType.Weight, m_Holding.TotalWeight + m_Holding.PileWeight); - - m_Holding.HeldBy ??= this; + m_Holding.HeldBy = null; } } - } - } - public long LastMoveTime { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual bool Paralyzed - { - get => m_Paralyzed; - set - { - if (m_Paralyzed != value) + if (value != null && m_Holding != null) { - m_Paralyzed = value; - Delta(MobileDelta.Flags); + DropHolding(); + } - SendLocalizedMessage(m_Paralyzed ? 502381 : 502382); - _paraTimerToken.Cancel(); + m_Holding = value; + + if (m_Holding != null) + { + UpdateTotal(m_Holding, TotalType.Weight, m_Holding.TotalWeight + m_Holding.PileWeight); + + m_Holding.HeldBy ??= this; } } } + } - [CommandProperty(AccessLevel.GameMaster)] - public bool DisarmReady { get; set; } + public long LastMoveTime { get; set; } - [CommandProperty(AccessLevel.GameMaster)] - public bool StunReady { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Frozen + [CommandProperty(AccessLevel.GameMaster)] + public virtual bool Paralyzed + { + get => m_Paralyzed; + set { - get => m_Frozen; - set + if (m_Paralyzed != value) { - if (m_Frozen != value) - { - m_Frozen = value; - Delta(MobileDelta.Flags); - _frozenTimerToken.Cancel(); - } + m_Paralyzed = value; + Delta(MobileDelta.Flags); + + SendLocalizedMessage(m_Paralyzed ? 502381 : 502382); + _paraTimerToken.Cancel(); } } + } - /// - /// Gets or sets the lock state for the property. - /// - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public StatLockType StrLock + [CommandProperty(AccessLevel.GameMaster)] + public bool DisarmReady { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool StunReady { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Frozen + { + get => m_Frozen; + set { - get => m_StrLock; - set + if (m_Frozen != value) { - if (m_StrLock != value) - { - m_StrLock = value; - - m_NetState.SendStatLockInfo(this); - } + m_Frozen = value; + Delta(MobileDelta.Flags); + _frozenTimerToken.Cancel(); } } + } - /// - /// Gets or sets the lock state for the property. - /// - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public StatLockType DexLock + /// + /// Gets or sets the lock state for the property. + /// + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public StatLockType StrLock + { + get => m_StrLock; + set { - get => m_DexLock; - set + if (m_StrLock != value) { - if (m_DexLock != value) - { - m_DexLock = value; + m_StrLock = value; - m_NetState.SendStatLockInfo(this); - } + m_NetState.SendStatLockInfo(this); } } + } - /// - /// Gets or sets the lock state for the property. - /// - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public StatLockType IntLock + /// + /// Gets or sets the lock state for the property. + /// + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public StatLockType DexLock + { + get => m_DexLock; + set { - get => m_IntLock; - set + if (m_DexLock != value) { - if (m_IntLock != value) - { - m_IntLock = value; + m_DexLock = value; - m_NetState.SendStatLockInfo(this); - } + m_NetState.SendStatLockInfo(this); } } + } - public long NextActionTime { get; set; } - - public long NextActionMessage { get; set; } - - public static int ActionMessageDelay { get; set; } = 125; - - public static bool GlobalRegenThroughPoison { get; set; } = true; - - public virtual bool RegenThroughPoison => GlobalRegenThroughPoison; - - public virtual bool CanRegenHits => Alive && (RegenThroughPoison || !Poisoned); - public virtual bool CanRegenStam => Alive; - public virtual bool CanRegenMana => Alive; - - public long NextSkillTime { get; set; } - - public List Aggressors { get; private set; } - - public List Aggressed { get; private set; } - - public bool ChangingCombatant => m_ChangingCombatant > 0; - - private void ExpireCombatant() + /// + /// Gets or sets the lock state for the property. + /// + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public StatLockType IntLock + { + get => m_IntLock; + set { - Combatant = null; - _expireCombatantTimerToken.Cancel(); - } - - /// - /// Overridable. Gets or sets which Mobile that this Mobile is currently engaged in combat with. - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual Mobile Combatant - { - get => m_Combatant; - set + if (m_IntLock != value) { - if (Deleted) + m_IntLock = value; + + m_NetState.SendStatLockInfo(this); + } + } + } + + public long NextActionTime { get; set; } + + public long NextActionMessage { get; set; } + + public static int ActionMessageDelay { get; set; } = 125; + + public static bool GlobalRegenThroughPoison { get; set; } = true; + + public virtual bool RegenThroughPoison => GlobalRegenThroughPoison; + + public virtual bool CanRegenHits => Alive && (RegenThroughPoison || !Poisoned); + public virtual bool CanRegenStam => Alive; + public virtual bool CanRegenMana => Alive; + + public long NextSkillTime { get; set; } + + public List Aggressors { get; private set; } + + public List Aggressed { get; private set; } + + public bool ChangingCombatant => m_ChangingCombatant > 0; + + private void ExpireCombatant() + { + Combatant = null; + _expireCombatantTimerToken.Cancel(); + } + + /// + /// Overridable. Gets or sets which Mobile that this Mobile is currently engaged in combat with. + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual Mobile Combatant + { + get => m_Combatant; + set + { + if (Deleted) + { + return; + } + + if (m_Combatant != value && value != this) + { + var old = m_Combatant; + + ++m_ChangingCombatant; + m_Combatant = value; + + if (m_Combatant != null && !CanBeHarmful(m_Combatant, false) || + !Region.OnCombatantChange(this, old, m_Combatant)) { + m_Combatant = old; + --m_ChangingCombatant; return; } - if (m_Combatant != value && value != this) + if (m_Combatant == null) { - var old = m_Combatant; - - ++m_ChangingCombatant; - m_Combatant = value; - - if (m_Combatant != null && !CanBeHarmful(m_Combatant, false) || - !Region.OnCombatantChange(this, old, m_Combatant)) + m_NetState.SendChangeCombatant(Serial.Zero); + _expireCombatantTimerToken.Cancel(); + _combatTimerToken.Cancel(); + } + else + { + m_NetState.SendChangeCombatant(m_Combatant.Serial); + if (!_expireCombatantTimerToken.Running) { - m_Combatant = old; - --m_ChangingCombatant; - return; + Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken); } - if (m_Combatant == null) + if (!_combatTimerToken.Running) { - m_NetState.SendChangeCombatant(Serial.Zero); - _expireCombatantTimerToken.Cancel(); - _combatTimerToken.Cancel(); + Timer.StartTimer(TimeSpan.FromSeconds(0.01), 0, CheckCombatTime, out _combatTimerToken); + } + + if (CanBeHarmful(m_Combatant, false)) + { + DoHarmful(m_Combatant); // due to reflection, might make m_Combatant null + m_Combatant?.PlaySound(m_Combatant.GetAngerSound()); + } + } + + OnCombatantChange(); + --m_ChangingCombatant; + } + } + } + + private void CheckCombatTime() + { + if (Core.TickCount - NextCombatTime < 0) + { + return; + } + + var combatant = Combatant; + + // If no combatant, wrong map, one of us is a ghost, or cannot see, or deleted, then stop combat + if (combatant?.Deleted != false || Deleted || combatant.m_Map != m_Map || + !combatant.Alive || !Alive || !CanSee(combatant) || combatant.IsDeadBondedPet || + IsDeadBondedPet) + { + Combatant = null; + return; + } + + var weapon = Weapon; + + if (weapon == null || !InRange(combatant, weapon.MaxRange)) + { + return; + } + + if (InLOS(combatant)) + { + weapon.OnBeforeSwing(this, combatant); + RevealingAction(); + NextCombatTime = + Core.TickCount + (int)weapon.OnSwing(this, combatant).TotalMilliseconds; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalGold => GetTotal(TotalType.Gold); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalItems => GetTotal(TotalType.Items); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalWeight => GetTotal(TotalType.Weight); + + [CommandProperty(AccessLevel.GameMaster)] + public int TithingPoints + { + get => m_TithingPoints; + set + { + if (m_TithingPoints != value) + { + m_TithingPoints = value; + + Delta(MobileDelta.TithingPoints); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Followers + { + get => m_Followers; + set + { + if (m_Followers != value) + { + m_Followers = value; + + Delta(MobileDelta.Followers); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int FollowersMax + { + get => m_FollowersMax; + set + { + if (m_FollowersMax != value) + { + m_FollowersMax = value; + + Delta(MobileDelta.Followers); + } + } + } + + public bool TargetLocked { get; set; } + + public Target Target + { + get => m_Target; + set + { + var oldTarget = m_Target; + var newTarget = value; + + if (oldTarget == newTarget) + { + return; + } + + m_Target = null; + + if (oldTarget != null && newTarget != null) + { + oldTarget.Cancel(this, TargetCancelType.Overridden); + } + + m_Target = newTarget; + + if (newTarget != null && !TargetLocked) + { + newTarget.SendTargetTo(m_NetState); + } + + OnTargetChange(); + } + } + + public ContextMenu ContextMenu + { + get => m_ContextMenu; + set + { + m_ContextMenu = value; + m_NetState.SendDisplayContextMenu(m_ContextMenu); + } + } + + public bool Pushing { get; set; } + + public virtual bool IsDeadBondedPet => false; + + public ISpell Spell + { + get => m_Spell; + set + { + if (m_Spell != null && value != null) + { + logger.Warning("Spell has been overwritten."); + } + + m_Spell = value; + } + } + + [CommandProperty(AccessLevel.Administrator)] + public bool AutoPageNotify { get; set; } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] + public IAccount Account { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int VirtualArmor + { + get => m_VirtualArmor; + set + { + if (m_VirtualArmor != value) + { + m_VirtualArmor = value; + + Delta(MobileDelta.Armor); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual double ArmorRating => 0.0; + + /// + /// Overridable. Returns true if the player is alive, false if otherwise. By default, this is computed by: + /// !Deleted && (!Player || !Body.IsGhost) + /// + [CommandProperty(AccessLevel.Counselor)] + public virtual bool Alive => !Deleted && (!m_Player || !m_Body.IsGhost); + + public static CreateCorpseHandler CreateCorpseHandler { get; set; } + + public virtual bool RetainPackLocsOnDeath => Core.AOS; + + [CommandProperty(AccessLevel.GameMaster)] + public Container Corpse { get; set; } + + public static char[] GhostChars { get; set; } + public static bool NoSpeechLOS { get; set; } + + public static TimeSpan AutoManifestTimeout { get; set; } = TimeSpan.FromSeconds(5.0); + + public static bool InsuranceEnabled { get; set; } + + public static int ActionDelay { get; set; } = 500; + + public static VisibleDamageType VisibleDamageType { get; set; } + + public List DamageEntries { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public Mobile LastKiller { get; set; } + + public static bool DefaultShowVisibleDamage { get; set; } + + public static bool DefaultCanSeeVisibleDamage { get; set; } + + public virtual bool ShowVisibleDamage => DefaultShowVisibleDamage; + public virtual bool CanSeeVisibleDamage => DefaultCanSeeVisibleDamage; + + [CommandProperty(AccessLevel.GameMaster)] + public bool Squelched { get; set; } + + public virtual bool ShouldCheckStatTimers => true; + + [CommandProperty(AccessLevel.GameMaster)] + public int LightLevel + { + get => m_LightLevel; + set + { + if (m_LightLevel != value) + { + m_LightLevel = value; + + CheckLightLevels(false); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public string Profile { get; set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public bool ProfileLocked { get; set; } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Player + { + get => m_Player; + set + { + m_Player = value; + InvalidateProperties(); + CheckStatTimers(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public string Title + { + get => m_Title; + set + { + m_Title = value; + InvalidateProperties(); + } + } + + public List Items { get; private set; } + + public virtual int MaxWeight => int.MaxValue; + + public static IWeapon DefaultWeapon { get; set; } + + [CommandProperty(AccessLevel.Counselor)] + public Skills Skills { get; private set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.Administrator)] + public AccessLevel AccessLevel + { + get => m_AccessLevel; + set + { + var oldValue = m_AccessLevel; + + if (oldValue != value) + { + m_AccessLevel = value; + Delta(MobileDelta.Noto); + InvalidateProperties(); + + SendMessage("Your access level has been changed. You are now {0}.", GetAccessLevelName(value)); + + ClearScreen(); + SendEverything(); + + OnAccessLevelChanged(oldValue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Fame + { + get => m_Fame; + set + { + var oldValue = m_Fame; + + if (oldValue != value) + { + m_Fame = value; + + if (ShowFameTitle && (m_Player || m_Body.IsHuman) && oldValue >= 10000 != value >= 10000) + { + InvalidateProperties(); + } + + OnFameChange(oldValue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Karma + { + get => m_Karma; + set + { + var old = m_Karma; + + if (old != value) + { + m_Karma = value; + OnKarmaChange(old); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Blessed + { + get => m_Blessed; + set + { + if (m_Blessed != value) + { + m_Blessed = value; + Delta(MobileDelta.HealthbarYellow); + } + } + } + + public virtual int Luck => 0; + + [Hue] + [CommandProperty(AccessLevel.GameMaster)] + public int HueMod + { + get => m_HueMod; + set + { + if (m_HueMod != value) + { + m_HueMod = value; + + Delta(MobileDelta.Hue); + } + } + } + + [Hue] + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Hue + { + get => m_HueMod != -1 ? m_HueMod : m_Hue; + set + { + var oldHue = m_Hue; + + if (oldHue != value) + { + m_Hue = value; + + Delta(MobileDelta.Hue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Direction Direction + { + get => m_Direction; + set + { + if (m_Direction != value) + { + m_Direction = value; + Delta(MobileDelta.Direction); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Female + { + get => m_Female; + set + { + if (m_Female != value) + { + m_Female = value; + Delta(MobileDelta.Flags); + OnGenderChanged(!m_Female); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Flying + { + get => m_Flying; + set + { + if (m_Flying != value) + { + m_Flying = value; + Delta(MobileDelta.Flags); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Warmode + { + get => m_Warmode; + set + { + if (Deleted) + { + return; + } + + if (m_Warmode != value) + { + _autoManifestTimerToken.Cancel(); + + m_Warmode = value; + Delta(MobileDelta.Flags); + + m_NetState.SendSetWarMode(value); + + if (!m_Warmode) + { + Combatant = null; + } + + if (!Alive) + { + if (value) + { + Delta(MobileDelta.GhostUpdate); } else { - m_NetState.SendChangeCombatant(m_Combatant.Serial); - if (!_expireCombatantTimerToken.Running) - { - Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken); - } - - if (!_combatTimerToken.Running) - { - Timer.StartTimer(TimeSpan.FromSeconds(0.01), 0, CheckCombatTime, out _combatTimerToken); - } - - if (CanBeHarmful(m_Combatant, false)) - { - DoHarmful(m_Combatant); // due to reflection, might make m_Combatant null - m_Combatant?.PlaySound(m_Combatant.GetAngerSound()); - } + SendRemovePacket(false); } - - OnCombatantChange(); - --m_ChangingCombatant; } + + OnWarmodeChanged(); } } + } - private void CheckCombatTime() + [CommandProperty(AccessLevel.GameMaster)] + public bool Hidden + { + get => m_Hidden; + set { - if (Core.TickCount - NextCombatTime < 0) + if (m_Hidden != value) + { + m_Hidden = value; + // Delta( MobileDelta.Flags ); + + OnHiddenChanged(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] + public NetState NetState + { + get + { + if (m_NetState?.Connection == null) + { + m_NetState = null; + } + + return m_NetState; + } + set + { + if (m_NetState == value) { return; } - var combatant = Combatant; - - // If no combatant, wrong map, one of us is a ghost, or cannot see, or deleted, then stop combat - if (combatant?.Deleted != false || Deleted || combatant.m_Map != m_Map || - !combatant.Alive || !Alive || !CanSee(combatant) || combatant.IsDeadBondedPet || - IsDeadBondedPet) - { - Combatant = null; - return; - } - - var weapon = Weapon; - - if (weapon == null || !InRange(combatant, weapon.MaxRange)) - { - return; - } - - if (InLOS(combatant)) - { - weapon.OnBeforeSwing(this, combatant); - RevealingAction(); - NextCombatTime = - Core.TickCount + (int)weapon.OnSwing(this, combatant).TotalMilliseconds; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalGold => GetTotal(TotalType.Gold); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalItems => GetTotal(TotalType.Items); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalWeight => GetTotal(TotalType.Weight); - - [CommandProperty(AccessLevel.GameMaster)] - public int TithingPoints - { - get => m_TithingPoints; - set - { - if (m_TithingPoints != value) - { - m_TithingPoints = value; - - Delta(MobileDelta.TithingPoints); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Followers - { - get => m_Followers; - set - { - if (m_Followers != value) - { - m_Followers = value; - - Delta(MobileDelta.Followers); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int FollowersMax - { - get => m_FollowersMax; - set - { - if (m_FollowersMax != value) - { - m_FollowersMax = value; - - Delta(MobileDelta.Followers); - } - } - } - - public bool TargetLocked { get; set; } - - public Target Target - { - get => m_Target; - set - { - var oldTarget = m_Target; - var newTarget = value; - - if (oldTarget == newTarget) - { - return; - } - - m_Target = null; - - if (oldTarget != null && newTarget != null) - { - oldTarget.Cancel(this, TargetCancelType.Overridden); - } - - m_Target = newTarget; - - if (newTarget != null && !TargetLocked) - { - newTarget.SendTargetTo(m_NetState); - } - - OnTargetChange(); - } - } - - public ContextMenu ContextMenu - { - get => m_ContextMenu; - set - { - m_ContextMenu = value; - m_NetState.SendDisplayContextMenu(m_ContextMenu); - } - } - - public bool Pushing { get; set; } - - public virtual bool IsDeadBondedPet => false; - - public ISpell Spell - { - get => m_Spell; - set - { - if (m_Spell != null && value != null) - { - logger.Warning("Spell has been overwritten."); - } - - m_Spell = value; - } - } - - [CommandProperty(AccessLevel.Administrator)] - public bool AutoPageNotify { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] - public IAccount Account { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int VirtualArmor - { - get => m_VirtualArmor; - set - { - if (m_VirtualArmor != value) - { - m_VirtualArmor = value; - - Delta(MobileDelta.Armor); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual double ArmorRating => 0.0; - - /// - /// Overridable. Returns true if the player is alive, false if otherwise. By default, this is computed by: - /// !Deleted && (!Player || !Body.IsGhost) - /// - [CommandProperty(AccessLevel.Counselor)] - public virtual bool Alive => !Deleted && (!m_Player || !m_Body.IsGhost); - - public static CreateCorpseHandler CreateCorpseHandler { get; set; } - - public virtual bool RetainPackLocsOnDeath => Core.AOS; - - [CommandProperty(AccessLevel.GameMaster)] - public Container Corpse { get; set; } - - public static char[] GhostChars { get; set; } - public static bool NoSpeechLOS { get; set; } - - public static TimeSpan AutoManifestTimeout { get; set; } = TimeSpan.FromSeconds(5.0); - - public static bool InsuranceEnabled { get; set; } - - public static int ActionDelay { get; set; } = 500; - - public static VisibleDamageType VisibleDamageType { get; set; } - - public List DamageEntries { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public Mobile LastKiller { get; set; } - - public static bool DefaultShowVisibleDamage { get; set; } - - public static bool DefaultCanSeeVisibleDamage { get; set; } - - public virtual bool ShowVisibleDamage => DefaultShowVisibleDamage; - public virtual bool CanSeeVisibleDamage => DefaultCanSeeVisibleDamage; - - [CommandProperty(AccessLevel.GameMaster)] - public bool Squelched { get; set; } - - public virtual bool ShouldCheckStatTimers => true; - - [CommandProperty(AccessLevel.GameMaster)] - public int LightLevel - { - get => m_LightLevel; - set - { - if (m_LightLevel != value) - { - m_LightLevel = value; - - CheckLightLevels(false); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public string Profile { get; set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public bool ProfileLocked { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Player - { - get => m_Player; - set - { - m_Player = value; - InvalidateProperties(); - CheckStatTimers(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public string Title - { - get => m_Title; - set - { - m_Title = value; - InvalidateProperties(); - } - } - - public List Items { get; private set; } - - public virtual int MaxWeight => int.MaxValue; - - public static IWeapon DefaultWeapon { get; set; } - - [CommandProperty(AccessLevel.Counselor)] - public Skills Skills { get; private set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.Administrator)] - public AccessLevel AccessLevel - { - get => m_AccessLevel; - set - { - var oldValue = m_AccessLevel; - - if (oldValue != value) - { - m_AccessLevel = value; - Delta(MobileDelta.Noto); - InvalidateProperties(); - - SendMessage("Your access level has been changed. You are now {0}.", GetAccessLevelName(value)); - - ClearScreen(); - SendEverything(); - - OnAccessLevelChanged(oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Fame - { - get => m_Fame; - set - { - var oldValue = m_Fame; - - if (oldValue != value) - { - m_Fame = value; - - if (ShowFameTitle && (m_Player || m_Body.IsHuman) && oldValue >= 10000 != value >= 10000) - { - InvalidateProperties(); - } - - OnFameChange(oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Karma - { - get => m_Karma; - set - { - var old = m_Karma; - - if (old != value) - { - m_Karma = value; - OnKarmaChange(old); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Blessed - { - get => m_Blessed; - set - { - if (m_Blessed != value) - { - m_Blessed = value; - Delta(MobileDelta.HealthbarYellow); - } - } - } - - public virtual int Luck => 0; - - [Hue] - [CommandProperty(AccessLevel.GameMaster)] - public int HueMod - { - get => m_HueMod; - set - { - if (m_HueMod != value) - { - m_HueMod = value; - - Delta(MobileDelta.Hue); - } - } - } - - [Hue] - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Hue - { - get => m_HueMod != -1 ? m_HueMod : m_Hue; - set - { - var oldHue = m_Hue; - - if (oldHue != value) - { - m_Hue = value; - - Delta(MobileDelta.Hue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Direction Direction - { - get => m_Direction; - set - { - if (m_Direction != value) - { - m_Direction = value; - Delta(MobileDelta.Direction); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Female - { - get => m_Female; - set - { - if (m_Female != value) - { - m_Female = value; - Delta(MobileDelta.Flags); - OnGenderChanged(!m_Female); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Flying - { - get => m_Flying; - set - { - if (m_Flying != value) - { - m_Flying = value; - Delta(MobileDelta.Flags); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Warmode - { - get => m_Warmode; - set - { - if (Deleted) - { - return; - } - - if (m_Warmode != value) - { - _autoManifestTimerToken.Cancel(); - - m_Warmode = value; - Delta(MobileDelta.Flags); - - m_NetState.SendSetWarMode(value); - - if (!m_Warmode) - { - Combatant = null; - } - - if (!Alive) - { - if (value) - { - Delta(MobileDelta.GhostUpdate); - } - else - { - SendRemovePacket(false); - } - } - - OnWarmodeChanged(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Hidden - { - get => m_Hidden; - set - { - if (m_Hidden != value) - { - m_Hidden = value; - // Delta( MobileDelta.Flags ); - - OnHiddenChanged(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] - public NetState NetState - { - get - { - if (m_NetState?.Connection == null) - { - m_NetState = null; - } - - return m_NetState; - } - set - { - if (m_NetState == value) - { - return; - } - #if THREADGUARD if (Thread.CurrentThread != Core.Thread) { @@ -1230,1335 +1230,10 @@ namespace Server } #endif - m_Map?.OnClientChange(m_NetState, value, this); - m_Target?.Cancel(this, TargetCancelType.Disconnected); - m_Spell?.OnConnectionChanged(); - m_NetState?.CancelAllTrades(); - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - { - box.Close(); - } - - OnBeforeDisconnected(); - EventSink.InvokeBeforeDisconnected(this); - - m_NetState = value; - _logoutTimerToken.Cancel(); - - if (m_NetState == null) - { - OnDisconnected(); - EventSink.InvokeDisconnected(this); - - // Disconnected, start the logout timer - Timer.StartTimer(GetLogoutDelay(), Logout, out _logoutTimerToken); - } - else - { - OnConnected(); - EventSink.InvokeConnected(this); - - if (m_Map == Map.Internal && LogoutMap != null) - { - Map = LogoutMap; - Location = LogoutLocation; - } - } - - for (var i = Items.Count - 1; i >= 0; --i) - { - if (i >= Items.Count) - { - continue; - } - - var item = Items[i]; - - if (item is SecureTradeContainer) - { - for (var j = item.Items.Count - 1; j >= 0; --j) - { - if (j < item.Items.Count) - { - item.Items[j].OnSecureTrade(this, this, this, false); - AddToBackpack(item.Items[j]); - } - } - - Timer.StartTimer(item.Delete); - } - } - - DropHolding(); - OnNetStateChanged(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public string Language - { - get => m_Language; - set => m_Language = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int SpeechHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int EmoteHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int WhisperHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int YellHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public string GuildTitle - { - get => m_GuildTitle; - set - { - var old = m_GuildTitle; - - if (old != value) - { - m_GuildTitle = value; - - if (m_Guild?.Disbanded == false && m_GuildTitle != null) - { - SendLocalizedMessage(1018026, true, m_GuildTitle); // Your guild title has changed : - } - - InvalidateProperties(); - - OnGuildTitleChange(old); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool DisplayGuildTitle - { - get => m_DisplayGuildTitle; - set - { - m_DisplayGuildTitle = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Mobile GuildFealty { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public string NameMod - { - get => m_NameMod; - set - { - if (m_NameMod != value) - { - m_NameMod = value; - Delta(MobileDelta.Name); - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool YellowHealthbar - { - get => m_YellowHealthbar; - set - { - m_YellowHealthbar = value; - Delta(MobileDelta.HealthbarYellow); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public string RawName - { - get => m_Name; - set => Name = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual string Name - { - get => m_NameMod ?? m_Name; - set - { - if (m_Name != value) // I'm leaving out the && m_NameMod == null - { - var oldName = m_Name; - m_Name = value; - OnAfterNameChange(oldName, m_Name); - Delta(MobileDelta.Name); - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime LastStrGain { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime LastIntGain { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime LastDexGain { get; set; } - - public DateTime LastStatGain - { - get - { - var d = LastStrGain; - - if (LastIntGain > d) - { - d = LastIntGain; - } - - if (LastDexGain > d) - { - d = LastDexGain; - } - - return d; - } - set - { - LastStrGain = value; - LastIntGain = value; - LastDexGain = value; - } - } - - public BaseGuild Guild - { - get => m_Guild; - set - { - var old = m_Guild; - - if (old != value) - { - if (value == null) - { - GuildTitle = null; - } - - m_Guild = value; - - Delta(MobileDelta.Noto); - InvalidateProperties(); - - OnGuildChange(old); - } - } - } - - public Region WalkRegion { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Poisoned => m_Poison != null; - - [CommandProperty(AccessLevel.GameMaster)] - public bool IsBodyMod => m_BodyMod.BodyID != 0; - - [CommandProperty(AccessLevel.GameMaster)] - public Body BodyMod - { - get => m_BodyMod; - set - { - if (m_BodyMod != value) - { - m_BodyMod = value; - - Delta(MobileDelta.Body); - InvalidateProperties(); - - CheckStatTimers(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Body Body - { - get => IsBodyMod ? m_BodyMod : m_Body; - set - { - if (m_Body != value && !IsBodyMod) - { - m_Body = SafeBody(value); - - Delta(MobileDelta.Body); - InvalidateProperties(); - - CheckStatTimers(); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Point3D LogoutLocation { get; set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Map LogoutMap { get; set; } - - public Region Region => m_Region ?? (Map == null ? Map.Internal.DefaultRegion : Map.DefaultRegion); - - [CommandProperty(AccessLevel.GameMaster)] - public int SolidHueOverride - { - get => m_SolidHueOverride; - set - { - if (m_SolidHueOverride == value) - { - return; - } - - m_SolidHueOverride = value; - Delta(MobileDelta.Hue | MobileDelta.Body); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual IWeapon Weapon - { - get - { - if (m_Weapon is Item item && !item.Deleted && item.Parent == this && CanSee(item)) - { - return m_Weapon; - } - - m_Weapon = null; - - item = FindItemOnLayer(Layer.OneHanded) ?? FindItemOnLayer(Layer.TwoHanded); - - if (item is IWeapon weapon) - { - return m_Weapon = weapon; - } - - return GetDefaultWeapon(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public BankBox BankBox - { - get - { - if (m_BankBox?.Deleted == false && m_BankBox.Parent == this) - { - return m_BankBox; - } - - m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; - - if (m_BankBox == null) - { - AddItem(m_BankBox = new BankBox(this)); - } - - return m_BankBox; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Container Backpack - { - get - { - if (m_Backpack?.Deleted != false || m_Backpack.Parent != this) - { - m_Backpack = FindItemOnLayer(Layer.Backpack) as Container; - } - - return m_Backpack; - } - } - - public virtual bool KeepsItemsOnDeath => m_AccessLevel > AccessLevel.Player; - - public bool HasTrade => m_NetState?.Trades.Count > 0; - - public bool NoMoveHS { get; set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Kills - { - get => m_Kills; - set - { - var oldValue = m_Kills; - - if (m_Kills != value) - { - m_Kills = Math.Max(value, 0); - - if (oldValue >= 5 != m_Kills >= 5) - { - Delta(MobileDelta.Noto); - InvalidateProperties(); - } - - OnKillsChange(oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int ShortTermMurders - { - get => m_ShortTermMurders; - set - { - if (m_ShortTermMurders != value) - { - m_ShortTermMurders = Math.Max(value, 0); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public virtual bool Criminal - { - get => m_Criminal; - set - { - if (m_Criminal != value) - { - m_Criminal = value; - Delta(MobileDelta.Noto); - InvalidateProperties(); - } - - _expireCriminalTimerToken.Cancel(); - - if (m_Criminal) - { - Timer.StartTimer(ExpireCriminalDelay, ExpireCriminal, out _expireCriminalTimerToken); - } - } - } - - public static bool DisableDismountInWarmode { get; set; } - - public static int BodyWeight { get; set; } = 11; // 11 + 3 for the backpack - - [CommandProperty(AccessLevel.GameMaster)] - public IMount Mount - { - get - { - Item item = null; - - if (m_MountItem?.Deleted == false && m_MountItem.Parent == this) - { - item = m_MountItem; - } - - item ??= FindItemOnLayer(Layer.Mount); - - if (item is not IMountItem mountItem) - { - return null; - } - - m_MountItem = item; - return mountItem.Mount; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Mounted => Mount != null; - - public virtual bool CanTarget => true; - public virtual bool ClickTitle => true; - - public virtual bool PropertyTitle => OldPropertyTitles ? ClickTitle : true; - - public static bool DisableHiddenSelfClick { get; set; } = true; - - public static bool AsciiClickMessage { get; set; } = true; - - public static bool GuildClickMessage { get; set; } = true; - - public static bool OldPropertyTitles { get; set; } - - public virtual bool ShowFameTitle // (m_Player || m_Body.IsHuman) && m_Fame >= 10000; } - => true; - - /// - /// Gets or sets the maximum attainable value for , , and . - /// - [CommandProperty(AccessLevel.GameMaster)] - public int StatCap - { - get => m_StatCap; - set - { - if (m_StatCap != value) - { - m_StatCap = value; - - Delta(MobileDelta.StatCap); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Meditating { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool CanSwim { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool CantWalk { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool CanHearGhosts - { - get => m_CanHearGhosts || AccessLevel >= AccessLevel.Counselor; - set => m_CanHearGhosts = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int RawStatTotal => RawStr + RawDex + RawInt; - - public long NextSpellTime { get; set; } - - public static AllowBeneficialHandler AllowBeneficialHandler { get; set; } - - public static AllowHarmfulHandler AllowHarmfulHandler { get; set; } - - public static SkillCheckTargetHandler SkillCheckTargetHandler { get; set; } - - public static SkillCheckLocationHandler SkillCheckLocationHandler { get; set; } - - public static SkillCheckDirectTargetHandler SkillCheckDirectTargetHandler { get; set; } - - public static SkillCheckDirectLocationHandler SkillCheckDirectLocationHandler { get; set; } - - public static AOSStatusHandler AOSStatusHandler { get; set; } - - public static RegenRateHandler HitsRegenRateHandler { get; set; } - - public static TimeSpan DefaultHitsRate { get; set; } - - public static RegenRateHandler StamRegenRateHandler { get; set; } - - public static TimeSpan DefaultStamRate { get; set; } - - public static RegenRateHandler ManaRegenRateHandler { get; set; } - - public static TimeSpan DefaultManaRate { get; set; } - - public static TimeSpan ExpireCriminalDelay { get; set; } = TimeSpan.FromMinutes(2.0); - - public Prompt Prompt - { - get => m_Prompt; - set - { - var oldPrompt = m_Prompt; - var newPrompt = value; - - if (oldPrompt == newPrompt) - { - return; - } - - m_Prompt = null; - - // TODO: Cancel the prompt anyway? - if (newPrompt != null) - { - oldPrompt?.OnCancel(this); - } - - m_Prompt = newPrompt; - NetState.SendPrompt(newPrompt); - } - } - - /// - /// Gets a list of all StatMod's currently active for the Mobile. - /// - public HashSet StatMods { get; private set; } - - /// - /// Gets or sets the base, unmodified, strength of the Mobile. Ranges from 1 to 65000, inclusive. - /// - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public int RawStr - { - get => m_Str; - set - { - value = Math.Clamp(value, 1, 65000); - - if (m_Str != value) - { - var oldValue = m_Str; - - m_Str = value; - Delta(MobileDelta.Stat | MobileDelta.Hits); - - if (Hits < HitsMax) - { - m_HitsTimer ??= new HitsTimer(this); - m_HitsTimer.Start(); - } - else if (Hits > HitsMax) - { - Hits = HitsMax; - } - - OnRawStrChange(oldValue); - OnRawStatChange(StatType.Str, oldValue); - } - } - } - - /// - /// Gets or sets the effective strength of the Mobile. This is the sum of the plus any additional - /// modifiers. Any attempts to set this value when under the influence of a will result in no change. - /// It ranges from 1 to 65000, inclusive. - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Str - { - get => Math.Clamp(m_Str + GetStatOffset(StatType.Str), 1, 65000); - set - { - if (StatMods.Count == 0) - { - RawStr = value; - } - } - } - - /// - /// Gets or sets the base, unmodified, dexterity of the Mobile. Ranges from 1 to 65000, inclusive. - /// - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public int RawDex - { - get => m_Dex; - set - { - value = Math.Clamp(value, 1, 65000); - - if (m_Dex != value) - { - var oldValue = m_Dex; - - m_Dex = value; - Delta(MobileDelta.Stat | MobileDelta.Stam); - - if (Stam < StamMax) - { - m_StamTimer ??= new StamTimer(this); - m_StamTimer.Start(); - } - else if (Stam > StamMax) - { - Stam = StamMax; - } - - OnRawDexChange(oldValue); - OnRawStatChange(StatType.Dex, oldValue); - } - } - } - - /// - /// Gets or sets the effective dexterity of the Mobile. This is the sum of the plus any additional - /// modifiers. Any attempts to set this value when under the influence of a will result in no change. - /// It ranges from 1 to 65000, inclusive. - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Dex - { - get => Math.Clamp(m_Dex + GetStatOffset(StatType.Dex), 0, 65000); - set - { - if (StatMods.Count == 0) - { - RawDex = value; - } - } - } - - /// - /// Gets or sets the base, unmodified, intelligence of the Mobile. Ranges from 1 to 65000, inclusive. - /// - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public int RawInt - { - get => m_Int; - set - { - value = Math.Clamp(value, 1, 65000); - - if (m_Int != value) - { - var oldValue = m_Int; - - m_Int = value; - Delta(MobileDelta.Stat | MobileDelta.Mana); - - if (Mana < ManaMax) - { - m_ManaTimer ??= new ManaTimer(this); - m_ManaTimer.Start(); - } - else if (Mana > ManaMax) - { - Mana = ManaMax; - } - - OnRawIntChange(oldValue); - OnRawStatChange(StatType.Int, oldValue); - } - } - } - - /// - /// Gets or sets the effective intelligence of the Mobile. This is the sum of the plus any additional - /// modifiers. Any attempts to set this value when under the influence of a will result in no change. - /// It ranges from 1 to 65000, inclusive. - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Int - { - get => Math.Clamp(m_Int + GetStatOffset(StatType.Int), 0, 65000); - set - { - if (StatMods.Count == 0) - { - RawInt = value; - } - } - } - - /// - /// Gets or sets the current hit point of the Mobile. This value ranges from 0 to , inclusive. When - /// set - /// to the value of , the CanReportMurder flag of all - /// aggressors is reset to false, and the list of damage entries is cleared. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int Hits - { - get => m_Hits; - set - { - if (Deleted) - { - return; - } - - value = Math.Clamp(value, 0, HitsMax); - - if (value == HitsMax) - { - m_HitsTimer?.Stop(); - - for (var i = 0; i < Aggressors.Count; i++) // reset reports on full HP - { - Aggressors[i].CanReportMurder = false; - } - - if (DamageEntries.Count > 0) - { - DamageEntries.Clear(); // reset damage entries on full HP - } - } - else if (CanRegenHits) - { - m_HitsTimer ??= new HitsTimer(this); - m_HitsTimer.Start(); - } - else - { - m_HitsTimer?.Stop(); - } - - if (m_Hits != value) - { - var oldValue = m_Hits; - m_Hits = value; - Delta(MobileDelta.Hits); - OnHitsChange(oldValue); - } - } - } - - /// - /// Overridable. Gets the maximum hit point of the Mobile. By default, this returns: 50 + ( / 2) - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int HitsMax => 50 + Str / 2; - - /// - /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int Stam - { - get => m_Stam; - set - { - if (Deleted) - { - return; - } - - value = Math.Clamp(value, 0, StamMax); - - if (CanRegenStam && value < StamMax) - { - m_StamTimer ??= new StamTimer(this); - m_StamTimer.Start(); - } - else - { - m_StamTimer?.Stop(); - } - - if (m_Stam != value) - { - var oldValue = m_Stam; - m_Stam = value; - Delta(MobileDelta.Stam); - OnStamChange(oldValue); - } - } - } - - /// - /// Overridable. Gets the maximum stamina of the Mobile. By default, this returns: - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int StamMax => Dex; - - /// - /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int Mana - { - get => m_Mana; - set - { - if (Deleted) - { - return; - } - - value = Math.Clamp(value, 0, ManaMax); - - if (value == ManaMax) - { - m_ManaTimer?.Stop(); - - if (Meditating) - { - Meditating = false; - SendLocalizedMessage(501846); // You are at peace. - } - } - else if (CanRegenMana) - { - m_ManaTimer ??= new ManaTimer(this); - m_ManaTimer.Start(); - } - else - { - m_ManaTimer?.Stop(); - } - - if (m_Mana != value) - { - var oldValue = m_Mana; - m_Mana = value; - Delta(MobileDelta.Mana); - OnManaChange(oldValue); - } - } - } - - /// - /// Overridable. Gets the maximum mana of the Mobile. By default, this returns: - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int ManaMax => Int; - - public Timer PoisonTimer { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public Poison Poison - { - get => m_Poison; - set - { - m_Poison = value; - Delta(MobileDelta.HealthbarPoison); - - if (PoisonTimer != null) - { - PoisonTimer.Stop(); - PoisonTimer = null; - } - - if (m_Poison != null) - { - PoisonTimer = m_Poison.ConstructTimer(this); - - PoisonTimer?.Start(); - } - - CheckStatTimers(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int HairItemID - { - get => m_Hair?.ItemID ?? 0; - set - { - if (m_Hair == null && value > 0) - { - m_Hair = new HairInfo(value); - } - else if (value <= 0) - { - m_Hair = null; - } - else if (m_Hair != null) - { - m_Hair.ItemID = value; - } - - Delta(MobileDelta.Hair); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int FacialHairItemID - { - get => m_FacialHair?.ItemID ?? 0; - set - { - if (m_FacialHair == null && value > 0) - { - m_FacialHair = new FacialHairInfo(value); - } - else if (value <= 0) - { - m_FacialHair = null; - } - else if (m_FacialHair != null) - { - m_FacialHair.ItemID = value; - } - - Delta(MobileDelta.FacialHair); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int HairHue - { - get => m_Hair?.Hue ?? 0; - set - { - if (m_Hair != null) - { - m_Hair.Hue = value; - Delta(MobileDelta.Hair); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int FacialHairHue - { - get => m_FacialHair?.Hue ?? 0; - set - { - if (m_FacialHair != null) - { - m_FacialHair.Hue = value; - Delta(MobileDelta.FacialHair); - } - } - } - - public Item ShieldArmor => FindItemOnLayer(Layer.TwoHanded); - - public Item NeckArmor => FindItemOnLayer(Layer.Neck); - - public Item HandArmor => FindItemOnLayer(Layer.Gloves); - - public Item HeadArmor => FindItemOnLayer(Layer.Helm); - - public Item ArmsArmor => FindItemOnLayer(Layer.Arms); - - public Item LegsArmor => FindItemOnLayer(Layer.InnerLegs) ?? FindItemOnLayer(Layer.Pants); - - public Item ChestArmor => FindItemOnLayer(Layer.InnerTorso) ?? FindItemOnLayer(Layer.Shirt); - - public Item Talisman => FindItemOnLayer(Layer.Talisman); - - public int CompareTo(Mobile other) => other == null ? -1 : Serial.CompareTo(other.Serial); - - public virtual int HuedItemID => m_Female ? 0x2107 : 0x2106; - public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this)); - - public virtual void GetProperties(IPropertyList list) - { - AddNameProperties(list); - } - - [CommandProperty(AccessLevel.GameMaster, readOnly: true)] - public DateTime Created { get; set; } = Core.Now; - - [CommandProperty(AccessLevel.GameMaster)] - DateTime ISerializable.LastSerialized { get; set; } = Core.Now; - - long ISerializable.SavePosition { get; set; } = -1; - - BufferWriter ISerializable.SaveBuffer { get; set; } - - [CommandProperty(AccessLevel.Counselor)] - public Serial Serial { get; } - - public int TypeRef { get; private set; } - - public virtual void Serialize(IGenericWriter writer) - { - writer.Write(33); // version - - writer.WriteDeltaTime(LastStrGain); - writer.WriteDeltaTime(LastIntGain); - writer.WriteDeltaTime(LastDexGain); - - byte hairflag = 0x00; - - if (m_Hair != null) - { - hairflag |= 0x01; - } - - if (m_FacialHair != null) - { - hairflag |= 0x02; - } - - writer.Write(hairflag); - - if ((hairflag & 0x01) != 0) - { - m_Hair?.Serialize(writer); - } - - if ((hairflag & 0x02) != 0) - { - m_FacialHair?.Serialize(writer); - } - - writer.Write(Race); - - writer.Write(m_TithingPoints); - - writer.Write(Corpse); - - // writer.Write(CreationTime); - - Stabled.Tidy(); - writer.Write(Stabled); - - writer.Write(CantWalk); - - VirtueInfo.Serialize(writer, Virtues); - - writer.Write(Thirst); - writer.Write(BAC); - - writer.Write(m_ShortTermMurders); - // writer.Write( m_ShortTermElapse ); - // writer.Write( m_LongTermElapse ); - - // writer.Write( m_Followers ); - writer.Write(m_FollowersMax); - - writer.Write(MagicDamageAbsorb); - - writer.Write(GuildFealty); - - writer.Write(m_Guild); - - writer.Write(m_DisplayGuildTitle); - - writer.Write(CanSwim); - - writer.Write(Squelched); - - writer.Write(m_Holding); - - writer.Write(m_VirtualArmor); - - writer.Write(BaseSoundID); - - writer.Write(DisarmReady); - writer.Write(StunReady); - - writer.Write(m_StatCap); - - writer.Write(NameHue); - - writer.Write(m_Hunger); - - writer.Write(m_Location); - writer.Write(m_Body); - writer.Write(m_Name); - writer.Write(m_GuildTitle); - writer.Write(m_Criminal); - writer.Write(m_Kills); - writer.Write(SpeechHue); - writer.Write(EmoteHue); - writer.Write(WhisperHue); - writer.Write(YellHue); - writer.Write(m_Language); - writer.Write(m_Female); - writer.Write(m_Warmode); - writer.Write(m_Hidden); - writer.Write((byte)m_Direction); - writer.Write(m_Hue); - writer.Write(m_Str); - writer.Write(m_Dex); - writer.Write(m_Int); - writer.Write(m_Hits); - writer.Write(m_Stam); - writer.Write(m_Mana); - - writer.Write(m_Map); - - writer.Write(m_Blessed); - writer.Write(m_Fame); - writer.Write(m_Karma); - writer.Write((byte)m_AccessLevel); - Skills.Serialize(writer); - - writer.Write(Items); - - writer.Write(m_Player); - writer.Write(m_Title); - writer.Write(Profile); - writer.Write(ProfileLocked); - writer.Write(AutoPageNotify); - - writer.Write(LogoutLocation); - writer.Write(LogoutMap); - - writer.Write((byte)m_StrLock); - writer.Write((byte)m_DexLock); - writer.Write((byte)m_IntLock); - } - - public bool Deleted { get; private set; } - - public virtual void Delete() - { - if (Deleted) - { - return; - } - - if (m_NetState != null) - { - m_NetState.CancelAllTrades(); - m_NetState.Disconnect($"Player {this} has been deleted."); - } - - DropHolding(); - - Region.OnRegionChange(this, m_Region, null); - - m_Region = null; - // Is the above line REALLY needed? The old Region system did NOT have said line - // and worked fine, because of this a LOT of extra checks have to be done everywhere... - // I guess this should be there for Garbage collection purposes, but, still, is it /really/ needed? - - OnDelete(); - - for (var i = Items.Count - 1; i >= 0; --i) - { - if (i < Items.Count) - { - Items[i].OnParentDeleted(this); - } - } - - for (var i = 0; i < Stabled.Count; i++) - { - Stabled[i].Delete(); - } - - SendRemovePacket(); - - m_Guild?.OnDelete(this); - - Deleted = true; - - m_Map?.OnLeave(this); - m_Map = null; - - m_Hair = null; - m_FacialHair = null; - m_MountItem = null; - - World.RemoveEntity(this); - - OnAfterDelete(); - - m_PropertyList = null; - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Map Map - { - get => m_Map; - set - { - if (Deleted) - { - return; - } - - if (m_Map != value) - { - m_NetState?.ValidateAllTrades(); - - var oldMap = m_Map; - - if (m_Map != null) - { - m_Map.OnLeave(this); - - ClearScreen(); - SendRemovePacket(); - } - - for (var i = 0; i < Items.Count; ++i) - { - Items[i].Map = value; - } - - m_Map = value; - InternalMapChange(oldMap); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Point3D Location - { - get => m_Location; - set => SetLocation(value, true); - } - - private void InternalMapChange(Map oldMap) - { - var ns = m_NetState; - m_Map?.OnEnter(this); - UpdateRegion(); - - if (ns != null) - { - ns.Sequence = 0; - - if (m_Map != null) - { - ns.SendMapChange(Map); - - ns.SendMapPatches(); - - ns.SendSeasonChange((byte)GetSeason(), true); - - ns.SendMobileUpdate(this); - ns.SendServerChange(m_Location, m_Map); - } - - ns.SendMobileIncoming(this, this); - - ns.SendMobileUpdate(this); - CheckLightLevels(true); - ns.SendMobileUpdate(this); - } - - SendEverything(); - SendIncomingPacket(); - - ns.SendMobileIncoming(this, this); - ns.SendSupportedFeature(); - ns.SendMobileUpdate(this); - ns.SendMobileAttributes(this); - - OnMapChange(oldMap); - } - - public virtual void MoveToWorld(Point3D newLocation, Map map) - { - if (Deleted) - { - return; - } - - if (m_Map == map) - { - SetLocation(newLocation, true); - return; - } + m_Map?.OnClientChange(m_NetState, value, this); + m_Target?.Cancel(this, TargetCancelType.Disconnected); + m_Spell?.OnConnectionChanged(); + m_NetState?.CancelAllTrades(); var box = FindBankNoCreate(); @@ -2567,2904 +1242,4216 @@ namespace Server box.Close(); } - var oldLocation = m_Location; - var oldMap = m_Map; + OnBeforeDisconnected(); + EventSink.InvokeBeforeDisconnected(this); - if (oldMap != null) + m_NetState = value; + _logoutTimerToken.Cancel(); + + if (m_NetState == null) { - oldMap.OnLeave(this); + OnDisconnected(); + EventSink.InvokeDisconnected(this); - ClearScreen(); - SendRemovePacket(); + // Disconnected, start the logout timer + Timer.StartTimer(GetLogoutDelay(), Logout, out _logoutTimerToken); + } + else + { + OnConnected(); + EventSink.InvokeConnected(this); + + if (m_Map == Map.Internal && LogoutMap != null) + { + Map = LogoutMap; + Location = LogoutLocation; + } } - for (var i = 0; i < Items.Count; ++i) + for (var i = Items.Count - 1; i >= 0; --i) { - Items[i].Map = map; + if (i >= Items.Count) + { + continue; + } + + var item = Items[i]; + + if (item is SecureTradeContainer) + { + for (var j = item.Items.Count - 1; j >= 0; --j) + { + if (j < item.Items.Count) + { + item.Items[j].OnSecureTrade(this, this, this, false); + AddToBackpack(item.Items[j]); + } + } + + Timer.StartTimer(item.Delete); + } } - m_Map = map; - - m_Location = newLocation; - InternalMapChange(oldMap); - OnLocationChange(oldLocation); - - m_Region?.OnLocationChanged(this, oldLocation); + DropHolding(); + OnNetStateChanged(); } + } - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int X + [CommandProperty(AccessLevel.GameMaster)] + public string Language + { + get => m_Language; + set => m_Language = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int SpeechHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int EmoteHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int WhisperHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int YellHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public string GuildTitle + { + get => m_GuildTitle; + set { - get => m_Location.m_X; - set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); + var old = m_GuildTitle; + + if (old != value) + { + m_GuildTitle = value; + + if (m_Guild?.Disbanded == false && m_GuildTitle != null) + { + SendLocalizedMessage(1018026, true, m_GuildTitle); // Your guild title has changed : + } + + InvalidateProperties(); + + OnGuildTitleChange(old); + } } + } - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Y + [CommandProperty(AccessLevel.GameMaster)] + public bool DisplayGuildTitle + { + get => m_DisplayGuildTitle; + set { - get => m_Location.m_Y; - set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); + m_DisplayGuildTitle = value; + InvalidateProperties(); } + } - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Z + [CommandProperty(AccessLevel.GameMaster)] + public Mobile GuildFealty { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public string NameMod + { + get => m_NameMod; + set { - get => m_Location.m_Z; - set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); + if (m_NameMod != value) + { + m_NameMod = value; + Delta(MobileDelta.Name); + InvalidateProperties(); + } } + } - public virtual void ProcessDelta() + [CommandProperty(AccessLevel.GameMaster)] + public bool YellowHealthbar + { + get => m_YellowHealthbar; + set { - var delta = m_DeltaFlags; - if (delta == MobileDelta.None) + m_YellowHealthbar = value; + Delta(MobileDelta.HealthbarYellow); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public string RawName + { + get => m_Name; + set => Name = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual string Name + { + get => m_NameMod ?? m_Name; + set + { + if (m_Name != value) // I'm leaving out the && m_NameMod == null + { + var oldName = m_Name; + m_Name = value; + OnAfterNameChange(oldName, m_Name); + Delta(MobileDelta.Name); + InvalidateProperties(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public DateTime LastStrGain { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public DateTime LastIntGain { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public DateTime LastDexGain { get; set; } + + public DateTime LastStatGain + { + get + { + var d = LastStrGain; + + if (LastIntGain > d) + { + d = LastIntGain; + } + + if (LastDexGain > d) + { + d = LastDexGain; + } + + return d; + } + set + { + LastStrGain = value; + LastIntGain = value; + LastDexGain = value; + } + } + + public BaseGuild Guild + { + get => m_Guild; + set + { + var old = m_Guild; + + if (old != value) + { + if (value == null) + { + GuildTitle = null; + } + + m_Guild = value; + + Delta(MobileDelta.Noto); + InvalidateProperties(); + + OnGuildChange(old); + } + } + } + + public Region WalkRegion { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Poisoned => m_Poison != null; + + [CommandProperty(AccessLevel.GameMaster)] + public bool IsBodyMod => m_BodyMod.BodyID != 0; + + [CommandProperty(AccessLevel.GameMaster)] + public Body BodyMod + { + get => m_BodyMod; + set + { + if (m_BodyMod != value) + { + m_BodyMod = value; + + Delta(MobileDelta.Body); + InvalidateProperties(); + + CheckStatTimers(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Body Body + { + get => IsBodyMod ? m_BodyMod : m_Body; + set + { + if (m_Body != value && !IsBodyMod) + { + m_Body = SafeBody(value); + + Delta(MobileDelta.Body); + InvalidateProperties(); + + CheckStatTimers(); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Point3D LogoutLocation { get; set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Map LogoutMap { get; set; } + + public Region Region => m_Region ?? (Map == null ? Map.Internal.DefaultRegion : Map.DefaultRegion); + + [CommandProperty(AccessLevel.GameMaster)] + public int SolidHueOverride + { + get => m_SolidHueOverride; + set + { + if (m_SolidHueOverride == value) { return; } - var attrs = delta & MobileDelta.Attributes; + m_SolidHueOverride = value; + Delta(MobileDelta.Hue | MobileDelta.Body); + } + } - m_DeltaFlags = MobileDelta.None; - m_InDeltaQueue = false; - - bool sendHits = false, sendStam = false, sendMana = false, sendAll = false, sendAny = false; - bool sendIncoming = false, sendNonlocalIncoming = false; - bool sendUpdate = false, sendRemove = false; - bool sendPublicStats = false, sendPrivateStats = false; - bool sendMoving = false, sendNonlocalMoving = false; - var sendOPLUpdate = ObjectPropertyList.Enabled && (delta & MobileDelta.Properties) != 0; - - bool sendHair = false, sendFacialHair = false, removeHair = false, removeFacialHair = false; - - bool sendHealthbarPoison = false, sendHealthbarYellow = false; - - if (attrs != MobileDelta.None) + [CommandProperty(AccessLevel.GameMaster)] + public virtual IWeapon Weapon + { + get + { + if (m_Weapon is Item item && !item.Deleted && item.Parent == this && CanSee(item)) { - sendAny = true; + return m_Weapon; + } - if (attrs == MobileDelta.Attributes) + m_Weapon = null; + + item = FindItemOnLayer(Layer.OneHanded) ?? FindItemOnLayer(Layer.TwoHanded); + + if (item is IWeapon weapon) + { + return m_Weapon = weapon; + } + + return GetDefaultWeapon(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public BankBox BankBox + { + get + { + if (m_BankBox?.Deleted == false && m_BankBox.Parent == this) + { + return m_BankBox; + } + + m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; + + if (m_BankBox == null) + { + AddItem(m_BankBox = new BankBox(this)); + } + + return m_BankBox; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Container Backpack + { + get + { + if (m_Backpack?.Deleted != false || m_Backpack.Parent != this) + { + m_Backpack = FindItemOnLayer(Layer.Backpack) as Container; + } + + return m_Backpack; + } + } + + public virtual bool KeepsItemsOnDeath => m_AccessLevel > AccessLevel.Player; + + public bool HasTrade => m_NetState?.Trades.Count > 0; + + public bool NoMoveHS { get; set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Kills + { + get => m_Kills; + set + { + var oldValue = m_Kills; + + if (m_Kills != value) + { + m_Kills = Math.Max(value, 0); + + if (oldValue >= 5 != m_Kills >= 5) { - sendAll = true; + Delta(MobileDelta.Noto); + InvalidateProperties(); + } + + OnKillsChange(oldValue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int ShortTermMurders + { + get => m_ShortTermMurders; + set + { + if (m_ShortTermMurders != value) + { + m_ShortTermMurders = Math.Max(value, 0); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public virtual bool Criminal + { + get => m_Criminal; + set + { + if (m_Criminal != value) + { + m_Criminal = value; + Delta(MobileDelta.Noto); + InvalidateProperties(); + } + + _expireCriminalTimerToken.Cancel(); + + if (m_Criminal) + { + Timer.StartTimer(ExpireCriminalDelay, ExpireCriminal, out _expireCriminalTimerToken); + } + } + } + + public static bool DisableDismountInWarmode { get; set; } + + public static int BodyWeight { get; set; } = 11; // 11 + 3 for the backpack + + [CommandProperty(AccessLevel.GameMaster)] + public IMount Mount + { + get + { + Item item = null; + + if (m_MountItem?.Deleted == false && m_MountItem.Parent == this) + { + item = m_MountItem; + } + + item ??= FindItemOnLayer(Layer.Mount); + + if (item is not IMountItem mountItem) + { + return null; + } + + m_MountItem = item; + return mountItem.Mount; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Mounted => Mount != null; + + public virtual bool CanTarget => true; + public virtual bool ClickTitle => true; + + public virtual bool PropertyTitle => OldPropertyTitles ? ClickTitle : true; + + public static bool DisableHiddenSelfClick { get; set; } = true; + + public static bool AsciiClickMessage { get; set; } = true; + + public static bool GuildClickMessage { get; set; } = true; + + public static bool OldPropertyTitles { get; set; } + + public virtual bool ShowFameTitle // (m_Player || m_Body.IsHuman) && m_Fame >= 10000; } + => true; + + /// + /// Gets or sets the maximum attainable value for , , and . + /// + [CommandProperty(AccessLevel.GameMaster)] + public int StatCap + { + get => m_StatCap; + set + { + if (m_StatCap != value) + { + m_StatCap = value; + + Delta(MobileDelta.StatCap); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Meditating { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool CanSwim { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool CantWalk { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool CanHearGhosts + { + get => m_CanHearGhosts || AccessLevel >= AccessLevel.Counselor; + set => m_CanHearGhosts = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int RawStatTotal => RawStr + RawDex + RawInt; + + public long NextSpellTime { get; set; } + + public static AllowBeneficialHandler AllowBeneficialHandler { get; set; } + + public static AllowHarmfulHandler AllowHarmfulHandler { get; set; } + + public static SkillCheckTargetHandler SkillCheckTargetHandler { get; set; } + + public static SkillCheckLocationHandler SkillCheckLocationHandler { get; set; } + + public static SkillCheckDirectTargetHandler SkillCheckDirectTargetHandler { get; set; } + + public static SkillCheckDirectLocationHandler SkillCheckDirectLocationHandler { get; set; } + + public static AOSStatusHandler AOSStatusHandler { get; set; } + + public static RegenRateHandler HitsRegenRateHandler { get; set; } + + public static TimeSpan DefaultHitsRate { get; set; } + + public static RegenRateHandler StamRegenRateHandler { get; set; } + + public static TimeSpan DefaultStamRate { get; set; } + + public static RegenRateHandler ManaRegenRateHandler { get; set; } + + public static TimeSpan DefaultManaRate { get; set; } + + public static TimeSpan ExpireCriminalDelay { get; set; } = TimeSpan.FromMinutes(2.0); + + public Prompt Prompt + { + get => m_Prompt; + set + { + var oldPrompt = m_Prompt; + var newPrompt = value; + + if (oldPrompt == newPrompt) + { + return; + } + + m_Prompt = null; + + // TODO: Cancel the prompt anyway? + if (newPrompt != null) + { + oldPrompt?.OnCancel(this); + } + + m_Prompt = newPrompt; + NetState.SendPrompt(newPrompt); + } + } + + /// + /// Gets a list of all StatMod's currently active for the Mobile. + /// + public HashSet StatMods { get; private set; } + + /// + /// Gets or sets the base, unmodified, strength of the Mobile. Ranges from 1 to 65000, inclusive. + /// + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public int RawStr + { + get => m_Str; + set + { + value = Math.Clamp(value, 1, 65000); + + if (m_Str != value) + { + var oldValue = m_Str; + + m_Str = value; + Delta(MobileDelta.Stat | MobileDelta.Hits); + + if (Hits < HitsMax) + { + m_HitsTimer ??= new HitsTimer(this); + m_HitsTimer.Start(); + } + else if (Hits > HitsMax) + { + Hits = HitsMax; + } + + OnRawStrChange(oldValue); + OnRawStatChange(StatType.Str, oldValue); + } + } + } + + /// + /// Gets or sets the effective strength of the Mobile. This is the sum of the plus any additional + /// modifiers. Any attempts to set this value when under the influence of a will result in no change. + /// It ranges from 1 to 65000, inclusive. + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Str + { + get => Math.Clamp(m_Str + GetStatOffset(StatType.Str), 1, 65000); + set + { + if (StatMods.Count == 0) + { + RawStr = value; + } + } + } + + /// + /// Gets or sets the base, unmodified, dexterity of the Mobile. Ranges from 1 to 65000, inclusive. + /// + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public int RawDex + { + get => m_Dex; + set + { + value = Math.Clamp(value, 1, 65000); + + if (m_Dex != value) + { + var oldValue = m_Dex; + + m_Dex = value; + Delta(MobileDelta.Stat | MobileDelta.Stam); + + if (Stam < StamMax) + { + m_StamTimer ??= new StamTimer(this); + m_StamTimer.Start(); + } + else if (Stam > StamMax) + { + Stam = StamMax; + } + + OnRawDexChange(oldValue); + OnRawStatChange(StatType.Dex, oldValue); + } + } + } + + /// + /// Gets or sets the effective dexterity of the Mobile. This is the sum of the plus any additional + /// modifiers. Any attempts to set this value when under the influence of a will result in no change. + /// It ranges from 1 to 65000, inclusive. + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Dex + { + get => Math.Clamp(m_Dex + GetStatOffset(StatType.Dex), 0, 65000); + set + { + if (StatMods.Count == 0) + { + RawDex = value; + } + } + } + + /// + /// Gets or sets the base, unmodified, intelligence of the Mobile. Ranges from 1 to 65000, inclusive. + /// + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public int RawInt + { + get => m_Int; + set + { + value = Math.Clamp(value, 1, 65000); + + if (m_Int != value) + { + var oldValue = m_Int; + + m_Int = value; + Delta(MobileDelta.Stat | MobileDelta.Mana); + + if (Mana < ManaMax) + { + m_ManaTimer ??= new ManaTimer(this); + m_ManaTimer.Start(); + } + else if (Mana > ManaMax) + { + Mana = ManaMax; + } + + OnRawIntChange(oldValue); + OnRawStatChange(StatType.Int, oldValue); + } + } + } + + /// + /// Gets or sets the effective intelligence of the Mobile. This is the sum of the plus any additional + /// modifiers. Any attempts to set this value when under the influence of a will result in no change. + /// It ranges from 1 to 65000, inclusive. + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Int + { + get => Math.Clamp(m_Int + GetStatOffset(StatType.Int), 0, 65000); + set + { + if (StatMods.Count == 0) + { + RawInt = value; + } + } + } + + /// + /// Gets or sets the current hit point of the Mobile. This value ranges from 0 to , inclusive. When + /// set + /// to the value of , the CanReportMurder flag of all + /// aggressors is reset to false, and the list of damage entries is cleared. + /// + [CommandProperty(AccessLevel.GameMaster)] + public int Hits + { + get => m_Hits; + set + { + if (Deleted) + { + return; + } + + value = Math.Clamp(value, 0, HitsMax); + + if (value == HitsMax) + { + m_HitsTimer?.Stop(); + + for (var i = 0; i < Aggressors.Count; i++) // reset reports on full HP + { + Aggressors[i].CanReportMurder = false; + } + + if (DamageEntries.Count > 0) + { + DamageEntries.Clear(); // reset damage entries on full HP + } + } + else if (CanRegenHits) + { + m_HitsTimer ??= new HitsTimer(this); + m_HitsTimer.Start(); + } + else + { + m_HitsTimer?.Stop(); + } + + if (m_Hits != value) + { + var oldValue = m_Hits; + m_Hits = value; + Delta(MobileDelta.Hits); + OnHitsChange(oldValue); + } + } + } + + /// + /// Overridable. Gets the maximum hit point of the Mobile. By default, this returns: 50 + ( / 2) + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int HitsMax => 50 + Str / 2; + + /// + /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. + /// + [CommandProperty(AccessLevel.GameMaster)] + public int Stam + { + get => m_Stam; + set + { + if (Deleted) + { + return; + } + + value = Math.Clamp(value, 0, StamMax); + + if (CanRegenStam && value < StamMax) + { + m_StamTimer ??= new StamTimer(this); + m_StamTimer.Start(); + } + else + { + m_StamTimer?.Stop(); + } + + if (m_Stam != value) + { + var oldValue = m_Stam; + m_Stam = value; + Delta(MobileDelta.Stam); + OnStamChange(oldValue); + } + } + } + + /// + /// Overridable. Gets the maximum stamina of the Mobile. By default, this returns: + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int StamMax => Dex; + + /// + /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. + /// + [CommandProperty(AccessLevel.GameMaster)] + public int Mana + { + get => m_Mana; + set + { + if (Deleted) + { + return; + } + + value = Math.Clamp(value, 0, ManaMax); + + if (value == ManaMax) + { + m_ManaTimer?.Stop(); + + if (Meditating) + { + Meditating = false; + SendLocalizedMessage(501846); // You are at peace. + } + } + else if (CanRegenMana) + { + m_ManaTimer ??= new ManaTimer(this); + m_ManaTimer.Start(); + } + else + { + m_ManaTimer?.Stop(); + } + + if (m_Mana != value) + { + var oldValue = m_Mana; + m_Mana = value; + Delta(MobileDelta.Mana); + OnManaChange(oldValue); + } + } + } + + /// + /// Overridable. Gets the maximum mana of the Mobile. By default, this returns: + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int ManaMax => Int; + + public Timer PoisonTimer { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public Poison Poison + { + get => m_Poison; + set + { + m_Poison = value; + Delta(MobileDelta.HealthbarPoison); + + if (PoisonTimer != null) + { + PoisonTimer.Stop(); + PoisonTimer = null; + } + + if (m_Poison != null) + { + PoisonTimer = m_Poison.ConstructTimer(this); + + PoisonTimer?.Start(); + } + + CheckStatTimers(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int HairItemID + { + get => m_Hair?.ItemID ?? 0; + set + { + if (m_Hair == null && value > 0) + { + m_Hair = new HairInfo(value); + } + else if (value <= 0) + { + m_Hair = null; + } + else if (m_Hair != null) + { + m_Hair.ItemID = value; + } + + Delta(MobileDelta.Hair); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int FacialHairItemID + { + get => m_FacialHair?.ItemID ?? 0; + set + { + if (m_FacialHair == null && value > 0) + { + m_FacialHair = new FacialHairInfo(value); + } + else if (value <= 0) + { + m_FacialHair = null; + } + else if (m_FacialHair != null) + { + m_FacialHair.ItemID = value; + } + + Delta(MobileDelta.FacialHair); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int HairHue + { + get => m_Hair?.Hue ?? 0; + set + { + if (m_Hair != null) + { + m_Hair.Hue = value; + Delta(MobileDelta.Hair); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int FacialHairHue + { + get => m_FacialHair?.Hue ?? 0; + set + { + if (m_FacialHair != null) + { + m_FacialHair.Hue = value; + Delta(MobileDelta.FacialHair); + } + } + } + + public Item ShieldArmor => FindItemOnLayer(Layer.TwoHanded); + + public Item NeckArmor => FindItemOnLayer(Layer.Neck); + + public Item HandArmor => FindItemOnLayer(Layer.Gloves); + + public Item HeadArmor => FindItemOnLayer(Layer.Helm); + + public Item ArmsArmor => FindItemOnLayer(Layer.Arms); + + public Item LegsArmor => FindItemOnLayer(Layer.InnerLegs) ?? FindItemOnLayer(Layer.Pants); + + public Item ChestArmor => FindItemOnLayer(Layer.InnerTorso) ?? FindItemOnLayer(Layer.Shirt); + + public Item Talisman => FindItemOnLayer(Layer.Talisman); + + public int CompareTo(Mobile other) => other == null ? -1 : Serial.CompareTo(other.Serial); + + public virtual int HuedItemID => m_Female ? 0x2107 : 0x2106; + public ObjectPropertyList PropertyList => m_PropertyList ??= InitializePropertyList(new ObjectPropertyList(this)); + + public virtual void GetProperties(IPropertyList list) + { + AddNameProperties(list); + } + + [CommandProperty(AccessLevel.GameMaster, readOnly: true)] + public DateTime Created { get; set; } = Core.Now; + + [CommandProperty(AccessLevel.GameMaster)] + DateTime ISerializable.LastSerialized { get; set; } = Core.Now; + + long ISerializable.SavePosition { get; set; } = -1; + + BufferWriter ISerializable.SaveBuffer { get; set; } + + [CommandProperty(AccessLevel.Counselor)] + public Serial Serial { get; } + + public int TypeRef { get; private set; } + + public virtual void Serialize(IGenericWriter writer) + { + writer.Write(33); // version + + writer.WriteDeltaTime(LastStrGain); + writer.WriteDeltaTime(LastIntGain); + writer.WriteDeltaTime(LastDexGain); + + byte hairflag = 0x00; + + if (m_Hair != null) + { + hairflag |= 0x01; + } + + if (m_FacialHair != null) + { + hairflag |= 0x02; + } + + writer.Write(hairflag); + + if ((hairflag & 0x01) != 0) + { + m_Hair?.Serialize(writer); + } + + if ((hairflag & 0x02) != 0) + { + m_FacialHair?.Serialize(writer); + } + + writer.Write(Race); + + writer.Write(m_TithingPoints); + + writer.Write(Corpse); + + // writer.Write(CreationTime); + + Stabled.Tidy(); + writer.Write(Stabled); + + writer.Write(CantWalk); + + VirtueInfo.Serialize(writer, Virtues); + + writer.Write(Thirst); + writer.Write(BAC); + + writer.Write(m_ShortTermMurders); + // writer.Write( m_ShortTermElapse ); + // writer.Write( m_LongTermElapse ); + + // writer.Write( m_Followers ); + writer.Write(m_FollowersMax); + + writer.Write(MagicDamageAbsorb); + + writer.Write(GuildFealty); + + writer.Write(m_Guild); + + writer.Write(m_DisplayGuildTitle); + + writer.Write(CanSwim); + + writer.Write(Squelched); + + writer.Write(m_Holding); + + writer.Write(m_VirtualArmor); + + writer.Write(BaseSoundID); + + writer.Write(DisarmReady); + writer.Write(StunReady); + + writer.Write(m_StatCap); + + writer.Write(NameHue); + + writer.Write(m_Hunger); + + writer.Write(m_Location); + writer.Write(m_Body); + writer.Write(m_Name); + writer.Write(m_GuildTitle); + writer.Write(m_Criminal); + writer.Write(m_Kills); + writer.Write(SpeechHue); + writer.Write(EmoteHue); + writer.Write(WhisperHue); + writer.Write(YellHue); + writer.Write(m_Language); + writer.Write(m_Female); + writer.Write(m_Warmode); + writer.Write(m_Hidden); + writer.Write((byte)m_Direction); + writer.Write(m_Hue); + writer.Write(m_Str); + writer.Write(m_Dex); + writer.Write(m_Int); + writer.Write(m_Hits); + writer.Write(m_Stam); + writer.Write(m_Mana); + + writer.Write(m_Map); + + writer.Write(m_Blessed); + writer.Write(m_Fame); + writer.Write(m_Karma); + writer.Write((byte)m_AccessLevel); + Skills.Serialize(writer); + + writer.Write(Items); + + writer.Write(m_Player); + writer.Write(m_Title); + writer.Write(Profile); + writer.Write(ProfileLocked); + writer.Write(AutoPageNotify); + + writer.Write(LogoutLocation); + writer.Write(LogoutMap); + + writer.Write((byte)m_StrLock); + writer.Write((byte)m_DexLock); + writer.Write((byte)m_IntLock); + } + + public bool Deleted { get; private set; } + + public virtual void Delete() + { + if (Deleted) + { + return; + } + + if (m_NetState != null) + { + m_NetState.CancelAllTrades(); + m_NetState.Disconnect($"Player {this} has been deleted."); + } + + DropHolding(); + + Region.OnRegionChange(this, m_Region, null); + + m_Region = null; + // Is the above line REALLY needed? The old Region system did NOT have said line + // and worked fine, because of this a LOT of extra checks have to be done everywhere... + // I guess this should be there for Garbage collection purposes, but, still, is it /really/ needed? + + OnDelete(); + + for (var i = Items.Count - 1; i >= 0; --i) + { + if (i < Items.Count) + { + Items[i].OnParentDeleted(this); + } + } + + for (var i = 0; i < Stabled.Count; i++) + { + Stabled[i].Delete(); + } + + SendRemovePacket(); + + m_Guild?.OnDelete(this); + + Deleted = true; + + m_Map?.OnLeave(this); + m_Map = null; + + m_Hair = null; + m_FacialHair = null; + m_MountItem = null; + + World.RemoveEntity(this); + + OnAfterDelete(); + + m_PropertyList = null; + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Map Map + { + get => m_Map; + set + { + if (Deleted) + { + return; + } + + if (m_Map != value) + { + m_NetState?.ValidateAllTrades(); + + var oldMap = m_Map; + + if (m_Map != null) + { + m_Map.OnLeave(this); + + ClearScreen(); + SendRemovePacket(); + } + + for (var i = 0; i < Items.Count; ++i) + { + Items[i].Map = value; + } + + m_Map = value; + InternalMapChange(oldMap); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Point3D Location + { + get => m_Location; + set => SetLocation(value, true); + } + + private void InternalMapChange(Map oldMap) + { + var ns = m_NetState; + m_Map?.OnEnter(this); + UpdateRegion(); + + if (ns != null) + { + ns.Sequence = 0; + + if (m_Map != null) + { + ns.SendMapChange(Map); + + ns.SendMapPatches(); + + ns.SendSeasonChange((byte)GetSeason(), true); + + ns.SendMobileUpdate(this); + ns.SendServerChange(m_Location, m_Map); + } + + ns.SendMobileIncoming(this, this); + + ns.SendMobileUpdate(this); + CheckLightLevels(true); + ns.SendMobileUpdate(this); + } + + SendEverything(); + SendIncomingPacket(); + + ns.SendMobileIncoming(this, this); + ns.SendSupportedFeature(); + ns.SendMobileUpdate(this); + ns.SendMobileAttributes(this); + + OnMapChange(oldMap); + } + + public virtual void MoveToWorld(Point3D newLocation, Map map) + { + if (Deleted) + { + return; + } + + if (m_Map == map) + { + SetLocation(newLocation, true); + return; + } + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + { + box.Close(); + } + + var oldLocation = m_Location; + var oldMap = m_Map; + + if (oldMap != null) + { + oldMap.OnLeave(this); + + ClearScreen(); + SendRemovePacket(); + } + + for (var i = 0; i < Items.Count; ++i) + { + Items[i].Map = map; + } + + m_Map = map; + + m_Location = newLocation; + InternalMapChange(oldMap); + OnLocationChange(oldLocation); + + m_Region?.OnLocationChanged(this, oldLocation); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int X + { + get => m_Location.m_X; + set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Y + { + get => m_Location.m_Y; + set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Z + { + get => m_Location.m_Z; + set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); + } + + public virtual void ProcessDelta() + { + var delta = m_DeltaFlags; + if (delta == MobileDelta.None) + { + return; + } + + var attrs = delta & MobileDelta.Attributes; + + m_DeltaFlags = MobileDelta.None; + m_InDeltaQueue = false; + + bool sendHits = false, sendStam = false, sendMana = false, sendAll = false, sendAny = false; + bool sendIncoming = false, sendNonlocalIncoming = false; + bool sendUpdate = false, sendRemove = false; + bool sendPublicStats = false, sendPrivateStats = false; + bool sendMoving = false, sendNonlocalMoving = false; + var sendOPLUpdate = ObjectPropertyList.Enabled && (delta & MobileDelta.Properties) != 0; + + bool sendHair = false, sendFacialHair = false, removeHair = false, removeFacialHair = false; + + bool sendHealthbarPoison = false, sendHealthbarYellow = false; + + if (attrs != MobileDelta.None) + { + sendAny = true; + + if (attrs == MobileDelta.Attributes) + { + sendAll = true; + } + else + { + sendHits = (attrs & MobileDelta.Hits) != 0; + sendStam = (attrs & MobileDelta.Stam) != 0; + sendMana = (attrs & MobileDelta.Mana) != 0; + } + } + + if ((delta & MobileDelta.GhostUpdate) != 0) + { + sendNonlocalIncoming = true; + } + + if ((delta & MobileDelta.Hue) != 0) + { + sendNonlocalIncoming = true; + sendUpdate = true; + sendRemove = true; + } + + if ((delta & MobileDelta.Direction) != 0) + { + sendNonlocalMoving = true; + sendUpdate = true; + } + + if ((delta & MobileDelta.Body) != 0) + { + sendUpdate = true; + sendIncoming = true; + } + + if ((delta & (MobileDelta.Flags | MobileDelta.Noto)) != 0) + { + sendMoving = true; + } + + if ((delta & MobileDelta.HealthbarPoison) != 0) + { + sendHealthbarPoison = true; + } + + if ((delta & MobileDelta.HealthbarYellow) != 0) + { + sendHealthbarYellow = true; + } + + if ((delta & MobileDelta.Name) != 0) + { + sendAll = false; + sendHits = false; + sendAny = sendStam || sendMana; + sendPublicStats = true; + } + + if ((delta & (MobileDelta.WeaponDamage | MobileDelta.Resistances | MobileDelta.Stat | + MobileDelta.Weight | MobileDelta.Gold | MobileDelta.Armor | MobileDelta.StatCap | + MobileDelta.Followers | MobileDelta.TithingPoints | MobileDelta.Race)) != 0) + { + sendPrivateStats = true; + } + + if ((delta & MobileDelta.Hair) != 0) + { + if (HairItemID <= 0) + { + removeHair = true; + } + + sendHair = true; + } + + if ((delta & MobileDelta.FacialHair) != 0) + { + if (FacialHairItemID <= 0) + { + removeFacialHair = true; + } + + sendFacialHair = true; + } + + var hairSerial = HairInfo.FakeSerial(Serial); + var hairLength = removeHair + ? OutgoingVirtualHairPackets.RemovePacketLength + : OutgoingVirtualHairPackets.EquipUpdatePacketLength; + + Span hairPacket = stackalloc byte[hairLength].InitializePacket(); + + var facialHairSerial = FacialHairInfo.FakeSerial(Serial); + var facialHairLength = removeFacialHair + ? OutgoingVirtualHairPackets.RemovePacketLength + : OutgoingVirtualHairPackets.EquipUpdatePacketLength; + + Span facialHairPacket = stackalloc byte[facialHairLength].InitializePacket(); + + const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; + const int width = OutgoingMobilePackets.MobileMovingPacketLength; + + var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width); + + var ourState = m_NetState; + + if (ourState != null) + { + if (sendUpdate) + { + ourState.Sequence = 0; + ourState.SendMobileUpdate(this); + } + + if (sendIncoming) + { + ourState.SendMobileIncoming(this, this); + } + + if (sendMoving || !ourState.StygianAbyss && (sendHealthbarPoison || sendHealthbarYellow)) + { + ourState.SendMobileMovingUsingCache(mobileMovingCache, this, this); + } + + if (ourState.StygianAbyss) + { + if (sendHealthbarPoison) + { + ourState.SendMobileHealthbar(this, Healthbar.Poison); + } + + if (sendHealthbarYellow) + { + ourState.SendMobileHealthbar(this, Healthbar.Yellow); + } + } + + if (sendPublicStats || sendPrivateStats) + { + ourState.SendMobileStatus(this); + } + else if (sendAll) + { + ourState.SendMobileAttributes(this); + } + else if (sendAny) + { + if (sendHits) + { + ourState.SendMobileHits(this); + } + + if (sendMana) + { + ourState.SendMobileMana(this); + } + + if (sendStam) + { + ourState.SendMobileStam(this); + } + } + + if (sendStam || sendMana) + { + if (Party is IParty ip) + { + if (sendMana) + { + ip.OnManaChanged(this); + } + + if (sendStam) + { + ip.OnStamChanged(this); + } + } + } + + if (sendHair) + { + if (removeHair) + { + OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, hairSerial); } else { - sendHits = (attrs & MobileDelta.Hits) != 0; - sendStam = (attrs & MobileDelta.Stam) != 0; - sendMana = (attrs & MobileDelta.Mana) != 0; + OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( + hairPacket, + this, + hairSerial, + HairItemID, + HairHue, + Layer.Hair + ); } + + ourState.Send(hairPacket); } - if ((delta & MobileDelta.GhostUpdate) != 0) + if (sendFacialHair) { - sendNonlocalIncoming = true; - } - - if ((delta & MobileDelta.Hue) != 0) - { - sendNonlocalIncoming = true; - sendUpdate = true; - sendRemove = true; - } - - if ((delta & MobileDelta.Direction) != 0) - { - sendNonlocalMoving = true; - sendUpdate = true; - } - - if ((delta & MobileDelta.Body) != 0) - { - sendUpdate = true; - sendIncoming = true; - } - - if ((delta & (MobileDelta.Flags | MobileDelta.Noto)) != 0) - { - sendMoving = true; - } - - if ((delta & MobileDelta.HealthbarPoison) != 0) - { - sendHealthbarPoison = true; - } - - if ((delta & MobileDelta.HealthbarYellow) != 0) - { - sendHealthbarYellow = true; - } - - if ((delta & MobileDelta.Name) != 0) - { - sendAll = false; - sendHits = false; - sendAny = sendStam || sendMana; - sendPublicStats = true; - } - - if ((delta & (MobileDelta.WeaponDamage | MobileDelta.Resistances | MobileDelta.Stat | - MobileDelta.Weight | MobileDelta.Gold | MobileDelta.Armor | MobileDelta.StatCap | - MobileDelta.Followers | MobileDelta.TithingPoints | MobileDelta.Race)) != 0) - { - sendPrivateStats = true; - } - - if ((delta & MobileDelta.Hair) != 0) - { - if (HairItemID <= 0) + if (removeFacialHair) { - removeHair = true; + OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, facialHairSerial); } - - sendHair = true; - } - - if ((delta & MobileDelta.FacialHair) != 0) - { - if (FacialHairItemID <= 0) + else { - removeFacialHair = true; + OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( + facialHairPacket, + this, + facialHairSerial, + FacialHairItemID, + FacialHairHue, + Layer.FacialHair + ); } - - sendFacialHair = true; + ourState.Send(facialHairPacket); } - var hairSerial = HairInfo.FakeSerial(Serial); - var hairLength = removeHair - ? OutgoingVirtualHairPackets.RemovePacketLength - : OutgoingVirtualHairPackets.EquipUpdatePacketLength; + if (sendOPLUpdate) + { + SendOPLPacketTo(ourState); + } + } - Span hairPacket = stackalloc byte[hairLength].InitializePacket(); + // TODO: Is it even valid to send packets to our state while we have a null map? Look into failing fast + if (m_Map == null) + { + return; + } - var facialHairSerial = FacialHairInfo.FakeSerial(Serial); - var facialHairLength = removeFacialHair - ? OutgoingVirtualHairPackets.RemovePacketLength - : OutgoingVirtualHairPackets.EquipUpdatePacketLength; + sendMoving = sendMoving || sendNonlocalMoving; + sendIncoming = sendIncoming || sendNonlocalIncoming; + sendHits = sendHits || sendAll; - Span facialHairPacket = stackalloc byte[facialHairLength].InitializePacket(); + if (!(sendRemove || sendIncoming || sendPublicStats || sendHits || sendMoving || + sendOPLUpdate || sendHair || sendFacialHair || sendHealthbarPoison || + sendHealthbarYellow)) + { + return; + } + + var eable = Map.GetClientsInRange(m_Location); + + Span statBufferTrue = stackalloc byte[OutgoingMobilePackets.MobileStatusCompactLength].InitializePacket(); + Span statBufferFalse = stackalloc byte[OutgoingMobilePackets.MobileStatusCompactLength].InitializePacket(); + Span hbpBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength].InitializePacket(); + Span hbyBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength].InitializePacket(); + Span deadBuffer = stackalloc byte[OutgoingMobilePackets.BondedStatusPacketLength].InitializePacket(); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); + Span hitsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributePacketLength].InitializePacket(); + + foreach (var state in eable) + { + var beholder = state.Mobile; + + if (beholder == this || !beholder.CanSee(this)) + { + continue; + } + + if (sendRemove) + { + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + state.Send(removeEntity); + } + + if (sendIncoming) + { + state.SendMobileIncoming(beholder, this); + + if (IsDeadBondedPet) + { + OutgoingMobilePackets.CreateBondedStatus(deadBuffer, Serial, true); + state.Send(deadBuffer); + } + } + + if (sendMoving || !state.StygianAbyss && (sendHealthbarPoison || sendHealthbarYellow)) + { + state.SendMobileMovingUsingCache(mobileMovingCache, beholder, this); + } + + if (state.StygianAbyss) + { + if (sendHealthbarPoison) + { + OutgoingMobilePackets.CreateMobileHealthbar(hbpBuffer, this, Healthbar.Poison); + state.Send(hbpBuffer); + } + + if (sendHealthbarYellow) + { + OutgoingMobilePackets.CreateMobileHealthbar(hbyBuffer, this, Healthbar.Yellow); + state.Send(hbyBuffer); + } + } + + if (sendPublicStats) + { + if (CanBeRenamedBy(beholder)) + { + OutgoingMobilePackets.CreateMobileStatusCompact(statBufferTrue, this, true); + state.Send(statBufferTrue); + } + else + { + OutgoingMobilePackets.CreateMobileStatusCompact(statBufferFalse, this, false); + state.Send(statBufferFalse); + } + } + else if (sendHits) + { + OutgoingMobilePackets.CreateMobileHits(hitsPacket, this, true); + state.Send(hitsPacket); + } + + if (sendHair) + { + if (removeHair) + { + OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, hairSerial); + } + else + { + OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( + hairPacket, + this, + hairSerial, + HairItemID, + HairHue, + Layer.Hair + ); + } + + state.Send(hairPacket); + } + + if (sendFacialHair) + { + if (removeFacialHair) + { + OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, facialHairSerial); + } + else + { + OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( + facialHairPacket, + this, + facialHairSerial, + FacialHairItemID, + FacialHairHue, + Layer.FacialHair + ); + } + state.Send(facialHairPacket); + } + + SendOPLPacketTo(state); + } + + eable.Free(); + } + + public ISpawner Spawner { get; set; } + + public virtual void OnBeforeSpawn(Point3D location, Map m) + { + } + + public virtual void OnAfterSpawn() + { + } + + public virtual bool InRange(Point2D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public virtual bool InRange(Point3D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public virtual bool InRange(IPoint2D p, int range) => + p.X >= Location.m_X - range + && p.X <= Location.m_X + range + && p.Y >= Location.m_Y - range + && p.Y <= Location.m_Y + range; + + protected virtual void OnRaceChange(Race oldRace) + { + } + + public virtual void ComputeLightLevels(out int global, out int personal) + { + ComputeBaseLightLevels(out global, out personal); + + m_Region?.AlterLightLevel(this, ref global, ref personal); + } + + public virtual void ComputeBaseLightLevels(out int global, out int personal) + { + global = 0; + personal = m_LightLevel; + } + + public virtual void CheckLightLevels(bool forceResend) + { + } + + public virtual void UpdateResistances() + { + var delta = false; + + if (Resistances == null) + { + Resistances = new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; + } + else + { + for (var i = 0; i < Resistances.Length; ++i) + { + if (Resistances[i] != int.MinValue) + { + Resistances[i] = int.MinValue; + delta = true; + } + } + } + + if (delta) + { + Delta(MobileDelta.Resistances); + } + } + + public virtual int GetResistance(ResistanceType type) + { + var v = (int)type; + + if (Resistances == null || Resistances.Length == 0 || v < 0 || v >= Resistances.Length) + { + return 0; + } + + var res = Resistances[v]; + + if (res == int.MinValue) + { + ComputeResistances(); + res = Resistances[v]; + } + + return res; + } + + public virtual void AddResistanceMod(ResistanceMod toAdd) + { + ResistanceMods ??= new List(); + + ResistanceMods.Add(toAdd); + UpdateResistances(); + } + + public virtual void RemoveResistanceMod(ResistanceMod toRemove) + { + if (ResistanceMods != null) + { + ResistanceMods.Remove(toRemove); + + if (ResistanceMods.Count == 0) + { + ResistanceMods = null; + } + } + + UpdateResistances(); + } + + public virtual void ComputeResistances() + { + Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; + + for (var i = 0; i < Resistances.Length; ++i) + { + Resistances[i] = 0; + } + + Resistances[0] += BasePhysicalResistance; + Resistances[1] += BaseFireResistance; + Resistances[2] += BaseColdResistance; + Resistances[3] += BasePoisonResistance; + Resistances[4] += BaseEnergyResistance; + + for (var i = 0; i < ResistanceMods?.Count; ++i) + { + var mod = ResistanceMods[i]; + var v = (int)mod.Type; + + if (v >= 0 && v < Resistances.Length) + { + Resistances[v] += mod.Offset; + } + } + + for (var i = 0; i < Items.Count; ++i) + { + var item = Items[i]; + + if (item.CheckPropertyConflict(this)) + { + continue; + } + + Resistances[0] += item.PhysicalResistance; + Resistances[1] += item.FireResistance; + Resistances[2] += item.ColdResistance; + Resistances[3] += item.PoisonResistance; + Resistances[4] += item.EnergyResistance; + } + + for (var i = 0; i < Resistances.Length; ++i) + { + var min = GetMinResistance((ResistanceType)i); + var max = GetMaxResistance((ResistanceType)i); + + if (max < min) + { + max = min; + } + + if (Resistances[i] > max) + { + Resistances[i] = max; + } + else if (Resistances[i] < min) + { + Resistances[i] = min; + } + } + } + + public virtual int GetMinResistance(ResistanceType type) => int.MinValue; + + public virtual int GetMaxResistance(ResistanceType type) => m_Player ? MaxPlayerResistance : int.MaxValue; + + public int GetAOSStatus(int index) => AOSStatusHandler?.Invoke(this, index) ?? 0; + + public virtual void SendPropertiesTo(NetState ns) + { + ns?.Send(PropertyList.Buffer); + } + + public virtual void OnAosSingleClick(Mobile from) + { + var opl = PropertyList; + + if (opl.Header > 0) + { + int hue; + + if (NameHue != -1) + { + hue = NameHue; + } + else if (m_AccessLevel > AccessLevel.Player) + { + hue = 11; + } + else + { + hue = Notoriety.GetHue(Notoriety.Compute(from, this)); + } + + from.NetState.SendMessageLocalized(Serial, Body, MessageType.Label, hue, 3, opl.Header, Name, opl.HeaderArgs); + } + } + + public virtual string ApplyNameSuffix(string suffix) => suffix; + + public virtual void AddNameProperties(IPropertyList list) + { + string prefix; + + if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) + { + prefix = m_Female ? "Lady " : "Lord "; + } + else + { + prefix = " "; + } + + var guild = m_Guild; + var hasTitle = PropertyTitle && !string.IsNullOrEmpty(Title); + var hasGuild = guild != null && (m_Player || m_DisplayGuildTitle); + + string suffix = hasTitle switch + { + true when hasGuild => $" {Title} [{Utility.FixHtmlFormattable(guild.Abbreviation)}]", + true => $" {Title}", + false when hasGuild => $" [{Utility.FixHtmlFormattable(guild.Abbreviation)}]", + _ => " " + }; + + list.Add(1050045, $"{prefix}\t{Name ?? " "}\t{ApplyNameSuffix(suffix)}"); // ~1_PREFIX~~2_NAME~~3_SUFFIX~ + + if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) + { + var type = guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length ? m_GuildTypes[(int)guild.Type] : ""; + + var guildTitle = GuildTitle?.Trim() ?? ""; + + if (guildTitle.Length > 0) + { + if (NewGuildDisplay) + { + list.Add($"{Utility.FixHtmlFormattable(guildTitle)}, {Utility.FixHtmlFormattable(guild.Name)}"); + } + else + { + list.Add($"{Utility.FixHtmlFormattable(guildTitle)}, {Utility.FixHtmlFormattable(guild.Name)} Guild{type}"); + } + } + else + { + list.Add(Utility.FixHtml(guild.Name)); + } + } + } + + public virtual void GetChildProperties(IPropertyList list, Item item) + { + } + + public virtual void GetChildNameProperties(IPropertyList list, Item item) + { + } + + private void UpdateAggrExpire() + { + if (Deleted || Aggressors.Count == 0 && Aggressed.Count == 0) + { + StopAggrExpire(); + } + else if (!_expireAggrTimerToken.Running) + { + Timer.StartTimer(ExpireAggressorsDelay, ExpireAggressorsDelay, ExpireAggr, out _expireAggrTimerToken); + } + } + + private void ExpireAggr() + { + if (Deleted || Aggressors.Count == 0 && Aggressed.Count == 0) + { + StopAggrExpire(); + } + else + { + CheckAggrExpire(); + } + } + + private void StopAggrExpire() + { + _expireAggrTimerToken.Cancel(); + } + + private void CheckAggrExpire() + { + for (var i = Aggressors.Count - 1; i >= 0; --i) + { + if (i >= Aggressors.Count) + { + continue; + } + + var info = Aggressors[i]; + + if (info.Expired) + { + var attacker = info.Attacker; + attacker.RemoveAggressed(this); + + Aggressors.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(attacker) && Utility.InUpdateRange(m_Location, attacker.m_Location)) + { + m_NetState.SendMobileIncoming(this, attacker); + } + } + } + + for (var i = Aggressed.Count - 1; i >= 0; --i) + { + if (i >= Aggressed.Count) + { + continue; + } + + var info = Aggressed[i]; + + if (info.Expired) + { + var defender = info.Defender; + defender.RemoveAggressor(this); + + Aggressed.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(defender) && Utility.InUpdateRange(m_Location, defender.m_Location)) + { + m_NetState.SendMobileIncoming(this, defender); + } + } + } + + UpdateAggrExpire(); + } + + /// + /// Overridable. Virtual event invoked when changes in some way. + /// + public virtual void OnSkillInvalidated(Skill skill) + { + } + + public virtual void UpdateSkillMods() + { + ValidateSkillMods(); + + foreach (var mod in SkillMods) + { + var sk = Skills[mod.Skill]; + sk?.Update(); + } + } + + public virtual void ValidateSkillMods() + { + using var queue = PooledRefQueue.Create(8); + foreach (var mod in SkillMods) + { + if (!mod.CheckCondition()) + { + queue.Enqueue(mod); + } + } + + while (queue.Count > 0) + { + InternalRemoveSkillMod(queue.Dequeue()); + } + } + + public virtual void AddSkillMod(SkillMod mod) + { + if (mod == null) + { + return; + } + + ValidateSkillMods(); + + if (SkillMods.Add(mod)) + { + mod.Owner = this; + + var sk = Skills[mod.Skill]; + sk?.Update(); + } + } + + public virtual void RemoveSkillMod(SkillMod mod) + { + if (mod == null) + { + return; + } + + InternalRemoveSkillMod(mod); + ValidateSkillMods(); + } + + private void InternalRemoveSkillMod(SkillMod mod) + { + if (SkillMods.Remove(mod)) + { + mod.Owner = null; + + var sk = Skills[mod.Skill]; + sk?.Update(); + } + } + + /// + /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Mobile. + /// Seemingly no longer functional in newer clients. + /// + public virtual void OnHelpRequest(Mobile from) + { + } + + public void DelayChangeWarmode(bool value) + { + if (m_WarmodeChanges > WarmodeCatchCount) + { + _warmodeSpamValue = value; + m_WarmodeChanges++; + return; + } + + if (m_Warmode == value) + { + return; + } + + var now = Core.Now; + var next = m_NextWarmodeChange; + + if (now > next || m_WarmodeChanges == 0) + { + m_WarmodeChanges = 1; + m_NextWarmodeChange = now + WarmodeSpamCatch; + } + else if (m_WarmodeChanges++ == WarmodeCatchCount) + { + Timer.StartTimer(WarmodeSpamDelay, WarmodeSpamTimeout); + return; + } + + Warmode = value; + } + + private void WarmodeSpamTimeout() + { + Warmode = _warmodeSpamValue; + m_WarmodeChanges = 0; + } + + public bool InLOS(Mobile target) => + !Deleted && m_Map != null && + (target == this || m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); + + public bool InLOS(object target) => + !Deleted && m_Map != null && + (target == this || m_AccessLevel > AccessLevel.Player || target is Item item && item.RootParent == this + || m_Map.LineOfSight(this, target)); + + public bool InLOS(Point3D target) => + !Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); + + public bool BeginAction() => BeginAction(typeof(T)); + + public bool BeginAction(object toLock) + { + if (_actions == null) + { + _actions = new List { toLock }; + return true; + } + + if (!_actions.Contains(toLock)) + { + _actions.Add(toLock); + return true; + } + + return false; + } + + public bool CanBeginAction() => CanBeginAction(typeof(T)); + + public bool CanBeginAction(object toLock) => _actions?.Contains(toLock) != true; + + public void EndAction() => EndAction(typeof(T)); + + public void EndAction(object toLock) + { + if (_actions != null) + { + _actions.Remove(toLock); + + if (_actions.Count == 0) + { + _actions = null; + } + } + } + + public virtual TimeSpan GetLogoutDelay() => Region.GetLogoutDelay(this); + + private void ExpireParalyzed() + { + Paralyzed = false; + } + + public virtual void Paralyze(TimeSpan duration) + { + if (!m_Paralyzed) + { + Paralyzed = true; + Timer.StartTimer(duration, ExpireParalyzed, out _paraTimerToken); + } + } + + private void ExpireFrozen() + { + Frozen = false; + } + + public virtual void Freeze(TimeSpan duration) + { + if (!m_Frozen) + { + Frozen = true; + Timer.StartTimer(duration, ExpireFrozen, out _frozenTimerToken); + } + } + + public override string ToString() => $"{Serial} \"{Name}\""; + + public virtual void SendSkillMessage() + { + if (NextActionMessage - Core.TickCount >= 0) + { + return; + } + + NextActionMessage = Core.TickCount + ActionMessageDelay; + + SendLocalizedMessage(500118); // You must wait a few moments to use another skill. + } + + public virtual void SendActionMessage() + { + if (NextActionMessage - Core.TickCount >= 0) + { + return; + } + + NextActionMessage = Core.TickCount + ActionMessageDelay; + + SendLocalizedMessage(500119); // You must wait to perform another action. + } + + public virtual void ClearHands() + { + ClearHand(FindItemOnLayer(Layer.OneHanded)); + ClearHand(FindItemOnLayer(Layer.TwoHanded)); + } + + public virtual void ClearHand(Item item) + { + if (item?.Movable == true && !item.AllowEquippedCast(this)) + { + var pack = Backpack; + + if (pack == null) + { + AddToBackpack(item); + } + else + { + pack.DropItem(item); + } + } + } + + public virtual void Attack(Mobile m) + { + if (CheckAttack(m)) + { + Combatant = m; + } + } + + public virtual bool CheckAttack(Mobile m) => Utility.InUpdateRange(Location, m.Location) && CanSee(m) && InLOS(m); + + /// + /// Overridable. Virtual event invoked after the property has changed. + /// + /// + public virtual void OnCombatantChange() + { + } + + public double GetDistanceToSqrt(Point3D p) + { + var xDelta = m_Location.m_X - p.m_X; + var yDelta = m_Location.m_Y - p.m_Y; + + return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + } + + public double GetDistanceToSqrt(Mobile m) + { + var xDelta = m_Location.m_X - m.m_Location.m_X; + var yDelta = m_Location.m_Y - m.m_Location.m_Y; + + return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + } + + public double GetDistanceToSqrt(IPoint2D p) + { + var xDelta = m_Location.m_X - p.X; + var yDelta = m_Location.m_Y - p.Y; + + return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + } + + public virtual void AggressiveAction(Mobile aggressor) => AggressiveAction(aggressor, false); + + public virtual void AggressiveAction(Mobile aggressor, bool criminal) + { + if (aggressor == this) + { + return; + } + + var args = AggressiveActionEventArgs.Create(this, aggressor, criminal); + + EventSink.InvokeAggressiveAction(args); + + args.Free(); + + if (Combatant == aggressor) + { + _expireCombatantTimerToken.Cancel(); + Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken); + } + + var addAggressor = true; + + var list = Aggressors; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Attacker == aggressor) + { + info.Refresh(); + info.CriminalAggression = criminal; + info.CanReportMurder = criminal; + + addAggressor = false; + } + } + + list = aggressor.Aggressors; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Attacker == this) + { + info.Refresh(); + + addAggressor = false; + } + } + + var addAggressed = true; + + list = Aggressed; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Defender == aggressor) + { + info.Refresh(); + + addAggressed = false; + } + } + + list = aggressor.Aggressed; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Defender == this) + { + info.Refresh(); + info.CriminalAggression = criminal; + info.CanReportMurder = criminal; + + addAggressed = false; + } + } + + var setCombatant = false; + + if (addAggressor) + { + Aggressors.Add( + AggressorInfo.Create( + aggressor, + this, + criminal + ) + ); + + if (CanSee(aggressor)) + { + m_NetState.SendMobileIncoming(this, aggressor); + } + + if (Combatant == null) + { + setCombatant = true; + } + + UpdateAggrExpire(); + } + + if (addAggressed) + { + aggressor.Aggressed.Add( + AggressorInfo.Create( + aggressor, + this, + criminal + ) + ); + + if (CanSee(aggressor)) + { + m_NetState.SendMobileIncoming(this, aggressor); + } + + if (Combatant == null) + { + setCombatant = true; + } + + UpdateAggrExpire(); + } + + if (setCombatant) + { + Combatant = aggressor; + } + + Region.OnAggressed(aggressor, this, criminal); + } + + public void RemoveAggressed(Mobile aggressed) + { + if (Deleted) + { + return; + } + + var list = Aggressed; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Defender == aggressed) + { + Aggressed.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(aggressed)) + { + m_NetState.SendMobileIncoming(this, aggressed); + } + + break; + } + } + + UpdateAggrExpire(); + } + + public void RemoveAggressor(Mobile aggressor) + { + if (Deleted) + { + return; + } + + var list = Aggressors; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Attacker == aggressor) + { + Aggressors.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(aggressor)) + { + m_NetState.SendMobileIncoming(this, aggressor); + } + + break; + } + } + + UpdateAggrExpire(); + } + + public virtual int GetTotal(TotalType type) => + type switch + { + TotalType.Gold => m_TotalGold, + TotalType.Items => m_TotalItems, + TotalType.Weight => m_TotalWeight, + _ => 0 + }; + + public virtual void UpdateTotal(Item sender, TotalType type, int delta) + { + if (delta == 0 || sender.IsVirtualItem) + { + return; + } + + switch (type) + { + default: + { + m_TotalGold += delta; + Delta(MobileDelta.Gold); + break; + } + + case TotalType.Items: + { + m_TotalItems += delta; + break; + } + + case TotalType.Weight: + { + m_TotalWeight += delta; + Delta(MobileDelta.Weight); + OnWeightChange(m_TotalWeight - delta); + break; + } + } + } + + public virtual void UpdateTotals() + { + if (Items == null) + { + return; + } + + var oldWeight = m_TotalWeight; + + m_TotalGold = 0; + m_TotalItems = 0; + m_TotalWeight = 0; + + for (var i = 0; i < Items.Count; ++i) + { + var item = Items[i]; + + item.UpdateTotals(); + + if (item.IsVirtualItem) + { + continue; + } + + m_TotalGold += item.TotalGold; + m_TotalItems += item.TotalItems + 1; + m_TotalWeight += item.TotalWeight + item.PileWeight; + } + + if (m_Holding != null) + { + m_TotalWeight += m_Holding.TotalWeight + m_Holding.PileWeight; + } + + if (m_TotalWeight != oldWeight) + { + OnWeightChange(oldWeight); + } + } + + public void ClearTarget() => m_Target = null; + + public Target BeginTarget(int range, bool allowGround, TargetFlags flags, TargetCallback callback) => + Target = new SimpleTarget(range, flags, allowGround, callback); + + public Target BeginTarget( + int range, bool allowGround, TargetFlags flags, TargetStateCallback callback, + T state + ) => + Target = new SimpleStateTarget(range, flags, allowGround, callback, state); + + /// + /// Overridable. Virtual event invoked after the Target property has changed. + /// + protected virtual void OnTargetChange() + { + } + + public virtual bool CheckContextMenuDisplay(IEntity target) => true; + + private bool InternalOnMove(Direction d) + { + if (!OnMove(d)) + { + return false; + } + + var e = MovementEventArgs.Create(this, d); + + EventSink.InvokeMovement(e); + + var ret = !e.Blocked; + + e.Free(); + + return ret; + } + + /// + /// Overridable. Event invoked before the Mobile moves. + /// + /// True if the move is allowed, false if not. + protected virtual bool OnMove(Direction d) + { + if (m_Hidden && m_AccessLevel == AccessLevel.Player) + { + if (AllowedStealthSteps-- <= 0 || (d & Direction.Running) != 0 || Mounted) + { + RevealingAction(); + } + } + + return true; + } + + public virtual bool CheckMovement(Direction d, out int newZ) => Movement.Movement.CheckMovement(this, d, out newZ); + + private bool CanMove(Direction d, Point3D oldLocation, ref Point3D newLocation) + { + if (m_Spell?.OnCasterMoving(d) == false) + { + return false; + } + + if (m_Paralyzed || m_Frozen) + { + SendLocalizedMessage(500111); // You are frozen and can not move. + + return false; + } + + if (!CheckMovement(d, out var newZ)) + { + return false; + } + + int x = oldLocation.m_X, y = oldLocation.m_Y; + int oldX = x, oldY = y; + var oldZ = oldLocation.m_Z; + + switch (d & Direction.Mask) + { + case Direction.North: + --y; + break; + case Direction.Right: + ++x; + --y; + break; + case Direction.East: + ++x; + break; + case Direction.Down: + ++x; + ++y; + break; + case Direction.South: + ++y; + break; + case Direction.Left: + --x; + ++y; + break; + case Direction.West: + --x; + break; + case Direction.Up: + --x; + --y; + break; + } + + newLocation.m_X = x; + newLocation.m_Y = y; + newLocation.m_Z = newZ; + + Pushing = false; + + var map = m_Map; + + if (map == null || !Region.CanMove(this, d, newLocation, oldLocation, map)) + { + return false; + } + + var oldSector = map.GetSector(oldX, oldY); + var newSector = map.GetSector(x, y); + + if (oldSector != newSector) + { + for (var i = 0; i < oldSector.Mobiles.Count; ++i) + { + var m = oldSector.Mobiles[i]; + + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && + !m.OnMoveOff(this)) + { + return false; + } + } + + for (var i = 0; i < oldSector.Items.Count; ++i) + { + var item = oldSector.Items[i]; + + if (item.AtWorldPoint(oldX, oldY) && + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && + !item.OnMoveOff(this)) + { + return false; + } + } + + for (var i = 0; i < newSector.Mobiles.Count; ++i) + { + var m = newSector.Mobiles[i]; + + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + { + return false; + } + } + + for (var i = 0; i < newSector.Items.Count; ++i) + { + var item = newSector.Items[i]; + + if (item.AtWorldPoint(x, y) && + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && + !item.OnMoveOver(this)) + { + return false; + } + } + } + else + { + for (var i = 0; i < oldSector.Mobiles.Count; ++i) + { + var m = oldSector.Mobiles[i]; + + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && + !m.OnMoveOff(this)) + { + return false; + } + + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + { + return false; + } + } + + for (var i = 0; i < oldSector.Items.Count; ++i) + { + var item = oldSector.Items[i]; + + if (item.AtWorldPoint(oldX, oldY) && + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && + !item.OnMoveOff(this)) + { + return false; + } + + if (item.AtWorldPoint(x, y) && + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && + !item.OnMoveOver(this)) + { + return false; + } + } + } + + if (!InternalOnMove(d)) + { + return false; + } + + if ( + CalcMoves.EnableFastwalkPrevention && + AccessLevel < CalcMoves.FastwalkExemptionLevel && + m_NetState?.AddStep(d) == false + ) + { + var fw = new FastWalkEventArgs(m_NetState); + EventSink.InvokeFastWalk(fw); + + if (fw.Blocked) + { + return false; + } + } + + LastMoveTime = Core.TickCount; + + return true; + } + + public virtual bool Move(Direction d) + { + if (Deleted) + { + return false; + } + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + { + box.Close(); + } + + var oldLocation = m_Location; + Point3D newLocation = oldLocation; + + if ((m_Direction & Direction.Mask) == (d & Direction.Mask)) + { + // We are actually moving (not just a direction change) + if (!CanMove(d, oldLocation, ref newLocation)) + { + return false; + } + + DisruptiveAction(); + } + + m_NetState?.SendMovementAck(m_NetState.Sequence, this); + + SetLocation(newLocation, false); + SetDirection(d); + + if (m_Map != null) + { + var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); + + foreach (var o in eable) + { + if (o == this) + { + continue; + } + + if (o is Mobile mob) + { + if (mob.NetState != null) + { + m_MoveClientList.Add(mob); + } + + m_MoveList.Add(mob); + } + else if (o is Item item && item.HandlesOnMovement) + { + m_MoveList.Add(item); + } + } + + eable.Free(); const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; const int width = OutgoingMobilePackets.MobileMovingPacketLength; var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width); - var ourState = m_NetState; - - if (ourState != null) + foreach (var m in m_MoveClientList) { - if (sendUpdate) + var ns = m.NetState; + + if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) { - ourState.Sequence = 0; - ourState.SendMobileUpdate(this); - } - - if (sendIncoming) - { - ourState.SendMobileIncoming(this, this); - } - - if (sendMoving || !ourState.StygianAbyss && (sendHealthbarPoison || sendHealthbarYellow)) - { - ourState.SendMobileMovingUsingCache(mobileMovingCache, this, this); - } - - if (ourState.StygianAbyss) - { - if (sendHealthbarPoison) - { - ourState.SendMobileHealthbar(this, Healthbar.Poison); - } - - if (sendHealthbarYellow) - { - ourState.SendMobileHealthbar(this, Healthbar.Yellow); - } - } - - if (sendPublicStats || sendPrivateStats) - { - ourState.SendMobileStatus(this); - } - else if (sendAll) - { - ourState.SendMobileAttributes(this); - } - else if (sendAny) - { - if (sendHits) - { - ourState.SendMobileHits(this); - } - - if (sendMana) - { - ourState.SendMobileMana(this); - } - - if (sendStam) - { - ourState.SendMobileStam(this); - } - } - - if (sendStam || sendMana) - { - if (Party is IParty ip) - { - if (sendMana) - { - ip.OnManaChanged(this); - } - - if (sendStam) - { - ip.OnStamChanged(this); - } - } - } - - if (sendHair) - { - if (removeHair) - { - OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, hairSerial); - } - else - { - OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( - hairPacket, - this, - hairSerial, - HairItemID, - HairHue, - Layer.Hair - ); - } - - ourState.Send(hairPacket); - } - - if (sendFacialHair) - { - if (removeFacialHair) - { - OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, facialHairSerial); - } - else - { - OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( - facialHairPacket, - this, - facialHairSerial, - FacialHairItemID, - FacialHairHue, - Layer.FacialHair - ); - } - ourState.Send(facialHairPacket); - } - - if (sendOPLUpdate) - { - SendOPLPacketTo(ourState); + ns.SendMobileMovingUsingCache(mobileMovingCache, m, this); } } - // TODO: Is it even valid to send packets to our state while we have a null map? Look into failing fast - if (m_Map == null) + for (var i = 0; i < m_MoveList.Count; ++i) + { + var o = m_MoveList[i]; + + if (o is Mobile mobile) + { + mobile.OnMovement(this, oldLocation); + } + else if (o is Item item) + { + item.OnMovement(this, oldLocation); + } + } + + if (m_MoveList.Count > 0) + { + m_MoveList.Clear(); + } + + if (m_MoveClientList.Count > 0) + { + m_MoveClientList.Clear(); + } + } + + OnAfterMove(oldLocation); + return true; + } + + public virtual void OnAfterMove(Point3D oldLocation) + { + } + + public int ComputeMovementSpeed() => ComputeMovementSpeed(Direction, false); + + public virtual int ComputeMovementSpeed(Direction dir, bool checkTurning = true) + { + if (Mounted) + { + return (dir & Direction.Running) != 0 ? CalcMoves.RunMountDelay : CalcMoves.WalkMountDelay; + } + + return (dir & Direction.Running) != 0 ? CalcMoves.RunFootDelay : CalcMoves.WalkFootDelay; + } + + /// + /// Overridable. Virtual event invoked when a Mobile moves off this Mobile. + /// + /// True if the move is allowed, false if not. + public virtual bool OnMoveOff(Mobile m) => true; + + /// + /// Overridable. Event invoked when a Mobile moves over this Mobile. + /// + /// True if the move is allowed, false if not. + public virtual bool OnMoveOver(Mobile m) => m_Map == null || Deleted || m.CheckShove(this); + + public virtual bool CheckShove(Mobile shoved) + { + if ((m_Map.Rules & MapRules.FreeMovement) == 0) + { + if (!shoved.Alive || !Alive || shoved.IsDeadBondedPet || IsDeadBondedPet) + { + return true; + } + + if (shoved.m_Hidden && shoved.m_AccessLevel > AccessLevel.Player) + { + return true; + } + + if (!Pushing) + { + Pushing = true; + + int number; + + if (AccessLevel > AccessLevel.Player) + { + number = shoved.m_Hidden ? 1019041 : 1019040; + } + else + { + if (Stam == StamMax) + { + number = shoved.m_Hidden ? 1019043 : 1019042; + Stam -= 10; + + RevealingAction(); + } + else + { + return false; + } + } + + SendLocalizedMessage(number); + } + } + + return true; + } + + /// + /// Overridable. Virtual event invoked when the Mobile sees another Mobile, , move. + /// + public virtual void OnMovement(Mobile m, Point3D oldLocation) + { + } + + public virtual void CriminalAction(bool message) + { + if (Deleted) + { + return; + } + + Criminal = true; + + Region.OnCriminalAction(this, message); + } + + public virtual bool IsSnoop(Mobile from) => from != this; + + /// + /// Overridable. Any call to will silently fail if this method returns false. + /// + /// + public virtual bool CheckResurrect() => true; + + /// + /// Overridable. Event invoked before the Mobile is resurrected. + /// + /// + public virtual void OnBeforeResurrect() + { + } + + /// + /// Overridable. Event invoked after the Mobile is resurrected. + /// + /// + public virtual void OnAfterResurrect() + { + } + + public virtual void Resurrect() + { + if (!Alive) + { + if (!Region.OnResurrect(this)) { return; } - sendMoving = sendMoving || sendNonlocalMoving; - sendIncoming = sendIncoming || sendNonlocalIncoming; - sendHits = sendHits || sendAll; - - if (!(sendRemove || sendIncoming || sendPublicStats || sendHits || sendMoving || - sendOPLUpdate || sendHair || sendFacialHair || sendHealthbarPoison || - sendHealthbarYellow)) + if (!CheckResurrect()) { return; } - var eable = Map.GetClientsInRange(m_Location); + OnBeforeResurrect(); - Span statBufferTrue = stackalloc byte[OutgoingMobilePackets.MobileStatusCompactLength].InitializePacket(); - Span statBufferFalse = stackalloc byte[OutgoingMobilePackets.MobileStatusCompactLength].InitializePacket(); - Span hbpBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength].InitializePacket(); - Span hbyBuffer = stackalloc byte[OutgoingMobilePackets.MobileHealthbarPacketLength].InitializePacket(); - Span deadBuffer = stackalloc byte[OutgoingMobilePackets.BondedStatusPacketLength].InitializePacket(); - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - Span hitsPacket = stackalloc byte[OutgoingMobilePackets.MobileAttributePacketLength].InitializePacket(); + var box = FindBankNoCreate(); - foreach (var state in eable) + if (box?.Opened == true) { - var beholder = state.Mobile; + box.Close(); + } - if (beholder == this || !beholder.CanSee(this)) + Poison = null; + + Warmode = false; + + Hits = 10; + Stam = StamMax; + Mana = 0; + + BodyMod = 0; + Body = Race.AliveBody(this); + + ProcessDeltaQueue(); + + for (var i = Items.Count - 1; i >= 0; --i) + { + if (i >= Items.Count) { continue; } - if (sendRemove) + var item = Items[i]; + + if (item.ItemID == 0x204E) { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - state.Send(removeEntity); + item.Delete(); + } + } + + SendIncomingPacket(); + SendIncomingPacket(); + + OnAfterResurrect(); + } + } + + public void DropHolding() + { + var holding = m_Holding; + + if (holding != null) + { + if (!holding.Deleted && holding.HeldBy == this && holding.Map == Map.Internal) + { + AddToBackpack(holding); + } + + Holding = null; + holding.ClearBounce(); + } + } + + /// + /// Overridable. Virtual event invoked before the Mobile is deleted. + /// + public virtual void OnDelete() + { + Spawner?.Remove(this); + Spawner = null; + } + + public virtual bool CheckSpellCast(ISpell spell) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile casts a . + /// + /// + public virtual void OnSpellCast(ISpell spell) + { + } + + /// + /// Overridable. Virtual event invoked after changes. + /// + public virtual void OnWeightChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when the or property of + /// changes. + /// + public virtual void OnSkillChange(SkillName skill, double oldBase) + { + } + + /// + /// Overridable. Invoked after the mobile is deleted. When overridden, be sure to call the base method. + /// + public virtual void OnAfterDelete() + { + StopAggrExpire(); + + CheckAggrExpire(); + + PoisonTimer?.Stop(); + m_HitsTimer?.Stop(); + m_StamTimer?.Stop(); + m_ManaTimer?.Stop(); + _combatTimerToken.Cancel(); + _expireCombatantTimerToken.Cancel(); + _logoutTimerToken.Cancel(); + _expireCriminalTimerToken.Cancel(); + _paraTimerToken.Cancel(); + _frozenTimerToken.Cancel(); + _autoManifestTimerToken.Cancel(); + } + + public virtual bool AllowSkillUse(SkillName name) => true; + + public virtual bool UseSkill(SkillName name) => Skills.UseSkill(this, name); + + public virtual bool UseSkill(int skillID) => Skills.UseSkill(this, skillID); + + public virtual DeathMoveResult GetParentMoveResultFor(Item item) => item.OnParentDeath(this); + + public virtual DeathMoveResult GetInventoryMoveResultFor(Item item) => item.OnInventoryDeath(this); + + public virtual void Kill() + { + if (!CanBeDamaged()) + { + return; + } + + if (!Alive || IsDeadBondedPet) + { + return; + } + + if (Deleted) + { + return; + } + + if (!Region.OnBeforeDeath(this)) + { + return; + } + + if (!OnBeforeDeath()) + { + return; + } + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + { + box.Close(); + } + + m_NetState?.CancelAllTrades(); + + m_Spell?.OnCasterKilled(); + // m_Spell.Disturb( DisturbType.Kill ); + + m_Target?.Cancel(this, TargetCancelType.Canceled); + + DisruptiveAction(); + + Warmode = false; + + DropHolding(); + + Hits = 0; + Stam = 0; + Mana = 0; + + Poison = null; + Combatant = null; + + if (Paralyzed) + { + Paralyzed = false; + } + + if (Frozen) + { + Frozen = false; + } + + var content = new List(); + var equip = new List(); + var moveToPack = new List(); + + var itemsCopy = new List(Items); + + var pack = Backpack; + + for (var i = 0; i < itemsCopy.Count; ++i) + { + var item = itemsCopy[i]; + + if (item == pack) + { + continue; + } + + var res = GetParentMoveResultFor(item); + + switch (res) + { + case DeathMoveResult.MoveToCorpse: + { + content.Add(item); + equip.Add(item); + break; + } + case DeathMoveResult.MoveToBackpack: + { + moveToPack.Add(item); + break; + } + } + } + + if (pack != null) + { + var packCopy = new List(pack.Items); + + for (var i = 0; i < packCopy.Count; ++i) + { + var item = packCopy[i]; + + var res = GetInventoryMoveResultFor(item); + + if (res == DeathMoveResult.MoveToCorpse) + { + content.Add(item); + } + else + { + moveToPack.Add(item); + } + } + + for (var i = 0; i < moveToPack.Count; ++i) + { + var item = moveToPack[i]; + + if (RetainPackLocsOnDeath && item.Parent == pack) + { + continue; } - if (sendIncoming) - { - state.SendMobileIncoming(beholder, this); + pack.DropItem(item); + } + } - if (IsDeadBondedPet) + HairInfo hair = null; + if (m_Hair != null) + { + hair = new HairInfo(m_Hair.ItemID, m_Hair.Hue); + } + + FacialHairInfo facialhair = null; + if (m_FacialHair != null) + { + facialhair = new FacialHairInfo(m_FacialHair.ItemID, m_FacialHair.Hue); + } + + var c = CreateCorpseHandler?.Invoke(this, hair, facialhair, content, equip); + + if (m_Map != null) + { + var eable = m_Map.GetClientsInRange(m_Location); + var corpseSerial = c?.Serial ?? Serial.Zero; + + Span deathAnimation = stackalloc byte[OutgoingMobilePackets.DeathAnimationPacketLength].InitializePacket(); + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); + + foreach (var state in eable) + { + if (state != m_NetState) + { + OutgoingMobilePackets.CreateDeathAnimation(deathAnimation, Serial, corpseSerial); + state.Send(deathAnimation); + + if (!state.Mobile.CanSee(this)) { - OutgoingMobilePackets.CreateBondedStatus(deadBuffer, Serial, true); - state.Send(deadBuffer); + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + state.Send(removeEntity); } } - - if (sendMoving || !state.StygianAbyss && (sendHealthbarPoison || sendHealthbarYellow)) - { - state.SendMobileMovingUsingCache(mobileMovingCache, beholder, this); - } - - if (state.StygianAbyss) - { - if (sendHealthbarPoison) - { - OutgoingMobilePackets.CreateMobileHealthbar(hbpBuffer, this, Healthbar.Poison); - state.Send(hbpBuffer); - } - - if (sendHealthbarYellow) - { - OutgoingMobilePackets.CreateMobileHealthbar(hbyBuffer, this, Healthbar.Yellow); - state.Send(hbyBuffer); - } - } - - if (sendPublicStats) - { - if (CanBeRenamedBy(beholder)) - { - OutgoingMobilePackets.CreateMobileStatusCompact(statBufferTrue, this, true); - state.Send(statBufferTrue); - } - else - { - OutgoingMobilePackets.CreateMobileStatusCompact(statBufferFalse, this, false); - state.Send(statBufferFalse); - } - } - else if (sendHits) - { - OutgoingMobilePackets.CreateMobileHits(hitsPacket, this, true); - state.Send(hitsPacket); - } - - if (sendHair) - { - if (removeHair) - { - OutgoingVirtualHairPackets.CreateRemoveHairPacket(hairPacket, hairSerial); - } - else - { - OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( - hairPacket, - this, - hairSerial, - HairItemID, - HairHue, - Layer.Hair - ); - } - - state.Send(hairPacket); - } - - if (sendFacialHair) - { - if (removeFacialHair) - { - OutgoingVirtualHairPackets.CreateRemoveHairPacket(facialHairPacket, facialHairSerial); - } - else - { - OutgoingVirtualHairPackets.CreateHairEquipUpdatePacket( - facialHairPacket, - this, - facialHairSerial, - FacialHairItemID, - FacialHairHue, - Layer.FacialHair - ); - } - state.Send(facialHairPacket); - } - - SendOPLPacketTo(state); } eable.Free(); } - public ISpawner Spawner { get; set; } + Region.OnDeath(this); + OnDeath(c); + } - public virtual void OnBeforeSpawn(Point3D location, Map m) - { - } - - public virtual void OnAfterSpawn() - { - } - - public virtual bool InRange(Point2D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public virtual bool InRange(Point3D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public virtual bool InRange(IPoint2D p, int range) => - p.X >= Location.m_X - range - && p.X <= Location.m_X + range - && p.Y >= Location.m_Y - range - && p.Y <= Location.m_Y + range; - - protected virtual void OnRaceChange(Race oldRace) - { - } - - public virtual void ComputeLightLevels(out int global, out int personal) - { - ComputeBaseLightLevels(out global, out personal); - - m_Region?.AlterLightLevel(this, ref global, ref personal); - } - - public virtual void ComputeBaseLightLevels(out int global, out int personal) - { - global = 0; - personal = m_LightLevel; - } - - public virtual void CheckLightLevels(bool forceResend) - { - } - - public virtual void UpdateResistances() - { - var delta = false; - - if (Resistances == null) - { - Resistances = new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; - } - else - { - for (var i = 0; i < Resistances.Length; ++i) - { - if (Resistances[i] != int.MinValue) - { - Resistances[i] = int.MinValue; - delta = true; - } - } - } - - if (delta) - { - Delta(MobileDelta.Resistances); - } - } - - public virtual int GetResistance(ResistanceType type) - { - var v = (int)type; - - if (Resistances == null || Resistances.Length == 0 || v < 0 || v >= Resistances.Length) - { - return 0; - } - - var res = Resistances[v]; - - if (res == int.MinValue) - { - ComputeResistances(); - res = Resistances[v]; - } - - return res; - } - - public virtual void AddResistanceMod(ResistanceMod toAdd) - { - ResistanceMods ??= new List(); - - ResistanceMods.Add(toAdd); - UpdateResistances(); - } - - public virtual void RemoveResistanceMod(ResistanceMod toRemove) - { - if (ResistanceMods != null) - { - ResistanceMods.Remove(toRemove); - - if (ResistanceMods.Count == 0) - { - ResistanceMods = null; - } - } - - UpdateResistances(); - } - - public virtual void ComputeResistances() - { - Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; - - for (var i = 0; i < Resistances.Length; ++i) - { - Resistances[i] = 0; - } - - Resistances[0] += BasePhysicalResistance; - Resistances[1] += BaseFireResistance; - Resistances[2] += BaseColdResistance; - Resistances[3] += BasePoisonResistance; - Resistances[4] += BaseEnergyResistance; - - for (var i = 0; i < ResistanceMods?.Count; ++i) - { - var mod = ResistanceMods[i]; - var v = (int)mod.Type; - - if (v >= 0 && v < Resistances.Length) - { - Resistances[v] += mod.Offset; - } - } - - for (var i = 0; i < Items.Count; ++i) - { - var item = Items[i]; - - if (item.CheckPropertyConflict(this)) - { - continue; - } - - Resistances[0] += item.PhysicalResistance; - Resistances[1] += item.FireResistance; - Resistances[2] += item.ColdResistance; - Resistances[3] += item.PoisonResistance; - Resistances[4] += item.EnergyResistance; - } - - for (var i = 0; i < Resistances.Length; ++i) - { - var min = GetMinResistance((ResistanceType)i); - var max = GetMaxResistance((ResistanceType)i); - - if (max < min) - { - max = min; - } - - if (Resistances[i] > max) - { - Resistances[i] = max; - } - else if (Resistances[i] < min) - { - Resistances[i] = min; - } - } - } - - public virtual int GetMinResistance(ResistanceType type) => int.MinValue; - - public virtual int GetMaxResistance(ResistanceType type) => m_Player ? MaxPlayerResistance : int.MaxValue; - - public int GetAOSStatus(int index) => AOSStatusHandler?.Invoke(this, index) ?? 0; - - public virtual void SendPropertiesTo(NetState ns) - { - ns?.Send(PropertyList.Buffer); - } - - public virtual void OnAosSingleClick(Mobile from) - { - var opl = PropertyList; - - if (opl.Header > 0) - { - int hue; - - if (NameHue != -1) - { - hue = NameHue; - } - else if (m_AccessLevel > AccessLevel.Player) - { - hue = 11; - } - else - { - hue = Notoriety.GetHue(Notoriety.Compute(from, this)); - } - - from.NetState.SendMessageLocalized(Serial, Body, MessageType.Label, hue, 3, opl.Header, Name, opl.HeaderArgs); - } - } - - public virtual string ApplyNameSuffix(string suffix) => suffix; - - public virtual void AddNameProperties(IPropertyList list) - { - string prefix; - - if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) - { - prefix = m_Female ? "Lady " : "Lord "; - } - else - { - prefix = " "; - } - - var guild = m_Guild; - var hasTitle = PropertyTitle && !string.IsNullOrEmpty(Title); - var hasGuild = guild != null && (m_Player || m_DisplayGuildTitle); - - string suffix = hasTitle switch - { - true when hasGuild => $" {Title} [{Utility.FixHtmlFormattable(guild.Abbreviation)}]", - true => $" {Title}", - false when hasGuild => $" [{Utility.FixHtmlFormattable(guild.Abbreviation)}]", - _ => " " - }; - - list.Add(1050045, $"{prefix}\t{Name ?? " "}\t{ApplyNameSuffix(suffix)}"); // ~1_PREFIX~~2_NAME~~3_SUFFIX~ - - if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) - { - var type = guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length ? m_GuildTypes[(int)guild.Type] : ""; - - var guildTitle = GuildTitle?.Trim() ?? ""; - - if (guildTitle.Length > 0) - { - if (NewGuildDisplay) - { - list.Add($"{Utility.FixHtmlFormattable(guildTitle)}, {Utility.FixHtmlFormattable(guild.Name)}"); - } - else - { - list.Add($"{Utility.FixHtmlFormattable(guildTitle)}, {Utility.FixHtmlFormattable(guild.Name)} Guild{type}"); - } - } - else - { - list.Add(Utility.FixHtml(guild.Name)); - } - } - } - - public virtual void GetChildProperties(IPropertyList list, Item item) - { - } - - public virtual void GetChildNameProperties(IPropertyList list, Item item) - { - } - - private void UpdateAggrExpire() - { - if (Deleted || Aggressors.Count == 0 && Aggressed.Count == 0) - { - StopAggrExpire(); - } - else if (!_expireAggrTimerToken.Running) - { - Timer.StartTimer(ExpireAggressorsDelay, ExpireAggressorsDelay, ExpireAggr, out _expireAggrTimerToken); - } - } - - private void ExpireAggr() - { - if (Deleted || Aggressors.Count == 0 && Aggressed.Count == 0) - { - StopAggrExpire(); - } - else - { - CheckAggrExpire(); - } - } - - private void StopAggrExpire() - { - _expireAggrTimerToken.Cancel(); - } - - private void CheckAggrExpire() - { - for (var i = Aggressors.Count - 1; i >= 0; --i) - { - if (i >= Aggressors.Count) - { - continue; - } - - var info = Aggressors[i]; - - if (info.Expired) - { - var attacker = info.Attacker; - attacker.RemoveAggressed(this); - - Aggressors.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(attacker) && Utility.InUpdateRange(m_Location, attacker.m_Location)) - { - m_NetState.SendMobileIncoming(this, attacker); - } - } - } - - for (var i = Aggressed.Count - 1; i >= 0; --i) - { - if (i >= Aggressed.Count) - { - continue; - } - - var info = Aggressed[i]; - - if (info.Expired) - { - var defender = info.Defender; - defender.RemoveAggressor(this); - - Aggressed.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(defender) && Utility.InUpdateRange(m_Location, defender.m_Location)) - { - m_NetState.SendMobileIncoming(this, defender); - } - } - } - - UpdateAggrExpire(); - } - - /// - /// Overridable. Virtual event invoked when changes in some way. - /// - public virtual void OnSkillInvalidated(Skill skill) - { - } - - public virtual void UpdateSkillMods() - { - ValidateSkillMods(); + /// + /// Overridable. Event invoked before the Mobile is killed. + /// + /// + /// + /// True to continue with death, false to override it. + public virtual bool OnBeforeDeath() => true; - foreach (var mod in SkillMods) - { - var sk = Skills[mod.Skill]; - sk?.Update(); - } - } - - public virtual void ValidateSkillMods() - { - using var queue = PooledRefQueue.Create(8); - foreach (var mod in SkillMods) - { - if (!mod.CheckCondition()) - { - queue.Enqueue(mod); - } - } - - while (queue.Count > 0) - { - InternalRemoveSkillMod(queue.Dequeue()); - } - } - - public virtual void AddSkillMod(SkillMod mod) - { - if (mod == null) - { - return; - } - - ValidateSkillMods(); - - if (SkillMods.Add(mod)) - { - mod.Owner = this; - - var sk = Skills[mod.Skill]; - sk?.Update(); - } - } - - public virtual void RemoveSkillMod(SkillMod mod) - { - if (mod == null) - { - return; - } - - InternalRemoveSkillMod(mod); - ValidateSkillMods(); - } - - private void InternalRemoveSkillMod(SkillMod mod) - { - if (SkillMods.Remove(mod)) - { - mod.Owner = null; - - var sk = Skills[mod.Skill]; - sk?.Update(); - } - } - - /// - /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Mobile. - /// Seemingly no longer functional in newer clients. - /// - public virtual void OnHelpRequest(Mobile from) - { - } - - public void DelayChangeWarmode(bool value) - { - if (m_WarmodeChanges > WarmodeCatchCount) - { - _warmodeSpamValue = value; - m_WarmodeChanges++; - return; - } - - if (m_Warmode == value) - { - return; - } - - var now = Core.Now; - var next = m_NextWarmodeChange; - - if (now > next || m_WarmodeChanges == 0) - { - m_WarmodeChanges = 1; - m_NextWarmodeChange = now + WarmodeSpamCatch; - } - else if (m_WarmodeChanges++ == WarmodeCatchCount) - { - Timer.StartTimer(WarmodeSpamDelay, WarmodeSpamTimeout); - return; - } - - Warmode = value; - } - - private void WarmodeSpamTimeout() - { - Warmode = _warmodeSpamValue; - m_WarmodeChanges = 0; - } - - public bool InLOS(Mobile target) => - !Deleted && m_Map != null && - (target == this || m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); - - public bool InLOS(object target) => - !Deleted && m_Map != null && - (target == this || m_AccessLevel > AccessLevel.Player || target is Item item && item.RootParent == this - || m_Map.LineOfSight(this, target)); - - public bool InLOS(Point3D target) => - !Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); - - public bool BeginAction() => BeginAction(typeof(T)); - - public bool BeginAction(object toLock) - { - if (_actions == null) - { - _actions = new List { toLock }; - return true; - } - - if (!_actions.Contains(toLock)) - { - _actions.Add(toLock); - return true; - } - - return false; - } - - public bool CanBeginAction() => CanBeginAction(typeof(T)); + /// + /// Overridable. Event invoked after the Mobile is killed. Primarily, this method is responsible for + /// deleting an NPC or turning a PC into a ghost. + /// + /// + /// + public virtual void OnDeath(Container c) + { + var sound = GetDeathSound(); - public bool CanBeginAction(object toLock) => _actions?.Contains(toLock) != true; - - public void EndAction() => EndAction(typeof(T)); - - public void EndAction(object toLock) - { - if (_actions != null) - { - _actions.Remove(toLock); - - if (_actions.Count == 0) - { - _actions = null; - } - } - } - - public virtual TimeSpan GetLogoutDelay() => Region.GetLogoutDelay(this); - - private void ExpireParalyzed() + if (sound >= 0) { - Paralyzed = false; + Effects.PlaySound(this, sound); } - public virtual void Paralyze(TimeSpan duration) + if (!m_Player) { - if (!m_Paralyzed) - { - Paralyzed = true; - Timer.StartTimer(duration, ExpireParalyzed, out _paraTimerToken); - } + Delete(); } - - private void ExpireFrozen() - { - Frozen = false; - } - - public virtual void Freeze(TimeSpan duration) + else { - if (!m_Frozen) - { - Frozen = true; - Timer.StartTimer(duration, ExpireFrozen, out _frozenTimerToken); - } - } - - public override string ToString() => $"{Serial} \"{Name}\""; - - public virtual void SendSkillMessage() - { - if (NextActionMessage - Core.TickCount >= 0) - { - return; - } - - NextActionMessage = Core.TickCount + ActionMessageDelay; - - SendLocalizedMessage(500118); // You must wait a few moments to use another skill. - } - - public virtual void SendActionMessage() - { - if (NextActionMessage - Core.TickCount >= 0) - { - return; - } - - NextActionMessage = Core.TickCount + ActionMessageDelay; - - SendLocalizedMessage(500119); // You must wait to perform another action. - } - - public virtual void ClearHands() - { - ClearHand(FindItemOnLayer(Layer.OneHanded)); - ClearHand(FindItemOnLayer(Layer.TwoHanded)); - } - - public virtual void ClearHand(Item item) - { - if (item?.Movable == true && !item.AllowEquippedCast(this)) - { - var pack = Backpack; - - if (pack == null) - { - AddToBackpack(item); - } - else - { - pack.DropItem(item); - } - } - } - - public virtual void Attack(Mobile m) - { - if (CheckAttack(m)) - { - Combatant = m; - } - } - - public virtual bool CheckAttack(Mobile m) => Utility.InUpdateRange(Location, m.Location) && CanSee(m) && InLOS(m); - - /// - /// Overridable. Virtual event invoked after the property has changed. - /// - /// - public virtual void OnCombatantChange() - { - } - - public double GetDistanceToSqrt(Point3D p) - { - var xDelta = m_Location.m_X - p.m_X; - var yDelta = m_Location.m_Y - p.m_Y; - - return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); - } - - public double GetDistanceToSqrt(Mobile m) - { - var xDelta = m_Location.m_X - m.m_Location.m_X; - var yDelta = m_Location.m_Y - m.m_Location.m_Y; - - return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); - } - - public double GetDistanceToSqrt(IPoint2D p) - { - var xDelta = m_Location.m_X - p.X; - var yDelta = m_Location.m_Y - p.Y; - - return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); - } - - public virtual void AggressiveAction(Mobile aggressor) => AggressiveAction(aggressor, false); - - public virtual void AggressiveAction(Mobile aggressor, bool criminal) - { - if (aggressor == this) - { - return; - } - - var args = AggressiveActionEventArgs.Create(this, aggressor, criminal); - - EventSink.InvokeAggressiveAction(args); - - args.Free(); - - if (Combatant == aggressor) - { - _expireCombatantTimerToken.Cancel(); - Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken); - } - - var addAggressor = true; - - var list = Aggressors; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Attacker == aggressor) - { - info.Refresh(); - info.CriminalAggression = criminal; - info.CanReportMurder = criminal; - - addAggressor = false; - } - } - - list = aggressor.Aggressors; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Attacker == this) - { - info.Refresh(); - - addAggressor = false; - } - } - - var addAggressed = true; - - list = Aggressed; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Defender == aggressor) - { - info.Refresh(); - - addAggressed = false; - } - } - - list = aggressor.Aggressed; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Defender == this) - { - info.Refresh(); - info.CriminalAggression = criminal; - info.CanReportMurder = criminal; - - addAggressed = false; - } - } - - var setCombatant = false; - - if (addAggressor) - { - Aggressors.Add( - AggressorInfo.Create( - aggressor, - this, - criminal - ) - ); - - if (CanSee(aggressor)) - { - m_NetState.SendMobileIncoming(this, aggressor); - } - - if (Combatant == null) - { - setCombatant = true; - } - - UpdateAggrExpire(); - } - - if (addAggressed) - { - aggressor.Aggressed.Add( - AggressorInfo.Create( - aggressor, - this, - criminal - ) - ); - - if (CanSee(aggressor)) - { - m_NetState.SendMobileIncoming(this, aggressor); - } - - if (Combatant == null) - { - setCombatant = true; - } - - UpdateAggrExpire(); - } - - if (setCombatant) - { - Combatant = aggressor; - } - - Region.OnAggressed(aggressor, this, criminal); - } - - public void RemoveAggressed(Mobile aggressed) - { - if (Deleted) - { - return; - } - - var list = Aggressed; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Defender == aggressed) - { - Aggressed.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(aggressed)) - { - m_NetState.SendMobileIncoming(this, aggressed); - } - - break; - } - } - - UpdateAggrExpire(); - } - - public void RemoveAggressor(Mobile aggressor) - { - if (Deleted) - { - return; - } - - var list = Aggressors; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Attacker == aggressor) - { - Aggressors.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(aggressor)) - { - m_NetState.SendMobileIncoming(this, aggressor); - } - - break; - } - } - - UpdateAggrExpire(); - } - - public virtual int GetTotal(TotalType type) => - type switch - { - TotalType.Gold => m_TotalGold, - TotalType.Items => m_TotalItems, - TotalType.Weight => m_TotalWeight, - _ => 0 - }; - - public virtual void UpdateTotal(Item sender, TotalType type, int delta) - { - if (delta == 0 || sender.IsVirtualItem) - { - return; - } - - switch (type) - { - default: - { - m_TotalGold += delta; - Delta(MobileDelta.Gold); - break; - } - - case TotalType.Items: - { - m_TotalItems += delta; - break; - } - - case TotalType.Weight: - { - m_TotalWeight += delta; - Delta(MobileDelta.Weight); - OnWeightChange(m_TotalWeight - delta); - break; - } - } - } - - public virtual void UpdateTotals() - { - if (Items == null) - { - return; - } - - var oldWeight = m_TotalWeight; - - m_TotalGold = 0; - m_TotalItems = 0; - m_TotalWeight = 0; - - for (var i = 0; i < Items.Count; ++i) - { - var item = Items[i]; - - item.UpdateTotals(); - - if (item.IsVirtualItem) - { - continue; - } - - m_TotalGold += item.TotalGold; - m_TotalItems += item.TotalItems + 1; - m_TotalWeight += item.TotalWeight + item.PileWeight; - } - - if (m_Holding != null) - { - m_TotalWeight += m_Holding.TotalWeight + m_Holding.PileWeight; - } - - if (m_TotalWeight != oldWeight) - { - OnWeightChange(oldWeight); - } - } - - public void ClearTarget() => m_Target = null; - - public Target BeginTarget(int range, bool allowGround, TargetFlags flags, TargetCallback callback) => - Target = new SimpleTarget(range, flags, allowGround, callback); - - public Target BeginTarget( - int range, bool allowGround, TargetFlags flags, TargetStateCallback callback, - T state - ) => - Target = new SimpleStateTarget(range, flags, allowGround, callback, state); - - /// - /// Overridable. Virtual event invoked after the Target property has changed. - /// - protected virtual void OnTargetChange() - { - } - - public virtual bool CheckContextMenuDisplay(IEntity target) => true; - - private bool InternalOnMove(Direction d) - { - if (!OnMove(d)) - { - return false; - } - - var e = MovementEventArgs.Create(this, d); - - EventSink.InvokeMovement(e); - - var ret = !e.Blocked; - - e.Free(); - - return ret; - } - - /// - /// Overridable. Event invoked before the Mobile moves. - /// - /// True if the move is allowed, false if not. - protected virtual bool OnMove(Direction d) - { - if (m_Hidden && m_AccessLevel == AccessLevel.Player) - { - if (AllowedStealthSteps-- <= 0 || (d & Direction.Running) != 0 || Mounted) - { - RevealingAction(); - } - } - - return true; - } - - public virtual bool CheckMovement(Direction d, out int newZ) => Movement.Movement.CheckMovement(this, d, out newZ); - - private bool CanMove(Direction d, Point3D oldLocation, ref Point3D newLocation) - { - if (m_Spell?.OnCasterMoving(d) == false) - { - return false; - } - - if (m_Paralyzed || m_Frozen) - { - SendLocalizedMessage(500111); // You are frozen and can not move. - - return false; - } - - if (!CheckMovement(d, out var newZ)) - { - return false; - } - - int x = oldLocation.m_X, y = oldLocation.m_Y; - int oldX = x, oldY = y; - var oldZ = oldLocation.m_Z; - - switch (d & Direction.Mask) - { - case Direction.North: - --y; - break; - case Direction.Right: - ++x; - --y; - break; - case Direction.East: - ++x; - break; - case Direction.Down: - ++x; - ++y; - break; - case Direction.South: - ++y; - break; - case Direction.Left: - --x; - ++y; - break; - case Direction.West: - --x; - break; - case Direction.Up: - --x; - --y; - break; - } - - newLocation.m_X = x; - newLocation.m_Y = y; - newLocation.m_Z = newZ; - - Pushing = false; - - var map = m_Map; - - if (map == null || !Region.CanMove(this, d, newLocation, oldLocation, map)) - { - return false; - } - - var oldSector = map.GetSector(oldX, oldY); - var newSector = map.GetSector(x, y); - - if (oldSector != newSector) - { - for (var i = 0; i < oldSector.Mobiles.Count; ++i) - { - var m = oldSector.Mobiles[i]; - - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) - { - return false; - } - } - - for (var i = 0; i < oldSector.Items.Count; ++i) - { - var item = oldSector.Items[i]; - - if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) - { - return false; - } - } - - for (var i = 0; i < newSector.Mobiles.Count; ++i) - { - var m = newSector.Mobiles[i]; - - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) - { - return false; - } - } - - for (var i = 0; i < newSector.Items.Count; ++i) - { - var item = newSector.Items[i]; - - if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) - { - return false; - } - } - } - else - { - for (var i = 0; i < oldSector.Mobiles.Count; ++i) - { - var m = oldSector.Mobiles[i]; - - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) - { - return false; - } - - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) - { - return false; - } - } - - for (var i = 0; i < oldSector.Items.Count; ++i) - { - var item = oldSector.Items[i]; - - if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) - { - return false; - } - - if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) - { - return false; - } - } - } - - if (!InternalOnMove(d)) - { - return false; - } - - if ( - CalcMoves.EnableFastwalkPrevention && - AccessLevel < CalcMoves.FastwalkExemptionLevel && - m_NetState?.AddStep(d) == false - ) - { - var fw = new FastWalkEventArgs(m_NetState); - EventSink.InvokeFastWalk(fw); - - if (fw.Blocked) - { - return false; - } - } - - LastMoveTime = Core.TickCount; - - return true; - } - - public virtual bool Move(Direction d) - { - if (Deleted) - { - return false; - } - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - { - box.Close(); - } - - var oldLocation = m_Location; - Point3D newLocation = oldLocation; - - if ((m_Direction & Direction.Mask) == (d & Direction.Mask)) - { - // We are actually moving (not just a direction change) - if (!CanMove(d, oldLocation, ref newLocation)) - { - return false; - } - - DisruptiveAction(); - } - - m_NetState?.SendMovementAck(m_NetState.Sequence, this); - - SetLocation(newLocation, false); - SetDirection(d); - - if (m_Map != null) - { - var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); - - foreach (var o in eable) - { - if (o == this) - { - continue; - } - - if (o is Mobile mob) - { - if (mob.NetState != null) - { - m_MoveClientList.Add(mob); - } - - m_MoveList.Add(mob); - } - else if (o is Item item && item.HandlesOnMovement) - { - m_MoveList.Add(item); - } - } - - eable.Free(); - - const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength; - const int width = OutgoingMobilePackets.MobileMovingPacketLength; - - var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width); - - foreach (var m in m_MoveClientList) - { - var ns = m.NetState; - - if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) - { - ns.SendMobileMovingUsingCache(mobileMovingCache, m, this); - } - } - - for (var i = 0; i < m_MoveList.Count; ++i) - { - var o = m_MoveList[i]; - - if (o is Mobile mobile) - { - mobile.OnMovement(this, oldLocation); - } - else if (o is Item item) - { - item.OnMovement(this, oldLocation); - } - } - - if (m_MoveList.Count > 0) - { - m_MoveList.Clear(); - } - - if (m_MoveClientList.Count > 0) - { - m_MoveClientList.Clear(); - } - } - - OnAfterMove(oldLocation); - return true; - } - - public virtual void OnAfterMove(Point3D oldLocation) - { - } - - public int ComputeMovementSpeed() => ComputeMovementSpeed(Direction, false); - - public virtual int ComputeMovementSpeed(Direction dir, bool checkTurning = true) - { - if (Mounted) - { - return (dir & Direction.Running) != 0 ? CalcMoves.RunMountDelay : CalcMoves.WalkMountDelay; - } - - return (dir & Direction.Running) != 0 ? CalcMoves.RunFootDelay : CalcMoves.WalkFootDelay; - } - - /// - /// Overridable. Virtual event invoked when a Mobile moves off this Mobile. - /// - /// True if the move is allowed, false if not. - public virtual bool OnMoveOff(Mobile m) => true; - - /// - /// Overridable. Event invoked when a Mobile moves over this Mobile. - /// - /// True if the move is allowed, false if not. - public virtual bool OnMoveOver(Mobile m) => m_Map == null || Deleted || m.CheckShove(this); - - public virtual bool CheckShove(Mobile shoved) - { - if ((m_Map.Rules & MapRules.FreeMovement) == 0) - { - if (!shoved.Alive || !Alive || shoved.IsDeadBondedPet || IsDeadBondedPet) - { - return true; - } - - if (shoved.m_Hidden && shoved.m_AccessLevel > AccessLevel.Player) - { - return true; - } - - if (!Pushing) - { - Pushing = true; - - int number; - - if (AccessLevel > AccessLevel.Player) - { - number = shoved.m_Hidden ? 1019041 : 1019040; - } - else - { - if (Stam == StamMax) - { - number = shoved.m_Hidden ? 1019043 : 1019042; - Stam -= 10; - - RevealingAction(); - } - else - { - return false; - } - } - - SendLocalizedMessage(number); - } - } - - return true; - } - - /// - /// Overridable. Virtual event invoked when the Mobile sees another Mobile, , move. - /// - public virtual void OnMovement(Mobile m, Point3D oldLocation) - { - } - - public virtual void CriminalAction(bool message) - { - if (Deleted) - { - return; - } - - Criminal = true; - - Region.OnCriminalAction(this, message); - } - - public virtual bool IsSnoop(Mobile from) => from != this; - - /// - /// Overridable. Any call to will silently fail if this method returns false. - /// - /// - public virtual bool CheckResurrect() => true; - - /// - /// Overridable. Event invoked before the Mobile is resurrected. - /// - /// - public virtual void OnBeforeResurrect() - { - } - - /// - /// Overridable. Event invoked after the Mobile is resurrected. - /// - /// - public virtual void OnAfterResurrect() - { - } - - public virtual void Resurrect() - { - if (!Alive) - { - if (!Region.OnResurrect(this)) - { - return; - } - - if (!CheckResurrect()) - { - return; - } - - OnBeforeResurrect(); - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - { - box.Close(); - } - - Poison = null; - - Warmode = false; - - Hits = 10; - Stam = StamMax; - Mana = 0; - - BodyMod = 0; - Body = Race.AliveBody(this); - - ProcessDeltaQueue(); - - for (var i = Items.Count - 1; i >= 0; --i) - { - if (i >= Items.Count) - { - continue; - } - - var item = Items[i]; - - if (item.ItemID == 0x204E) - { - item.Delete(); - } - } - - SendIncomingPacket(); - SendIncomingPacket(); - - OnAfterResurrect(); - } - } - - public void DropHolding() - { - var holding = m_Holding; - - if (holding != null) - { - if (!holding.Deleted && holding.HeldBy == this && holding.Map == Map.Internal) - { - AddToBackpack(holding); - } - - Holding = null; - holding.ClearBounce(); - } - } - - /// - /// Overridable. Virtual event invoked before the Mobile is deleted. - /// - public virtual void OnDelete() - { - Spawner?.Remove(this); - Spawner = null; - } - - public virtual bool CheckSpellCast(ISpell spell) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile casts a . - /// - /// - public virtual void OnSpellCast(ISpell spell) - { - } - - /// - /// Overridable. Virtual event invoked after changes. - /// - public virtual void OnWeightChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when the or property of - /// changes. - /// - public virtual void OnSkillChange(SkillName skill, double oldBase) - { - } - - /// - /// Overridable. Invoked after the mobile is deleted. When overridden, be sure to call the base method. - /// - public virtual void OnAfterDelete() - { - StopAggrExpire(); - - CheckAggrExpire(); - - PoisonTimer?.Stop(); - m_HitsTimer?.Stop(); - m_StamTimer?.Stop(); - m_ManaTimer?.Stop(); - _combatTimerToken.Cancel(); - _expireCombatantTimerToken.Cancel(); - _logoutTimerToken.Cancel(); - _expireCriminalTimerToken.Cancel(); - _paraTimerToken.Cancel(); - _frozenTimerToken.Cancel(); - _autoManifestTimerToken.Cancel(); - } - - public virtual bool AllowSkillUse(SkillName name) => true; - - public virtual bool UseSkill(SkillName name) => Skills.UseSkill(this, name); - - public virtual bool UseSkill(int skillID) => Skills.UseSkill(this, skillID); - - public virtual DeathMoveResult GetParentMoveResultFor(Item item) => item.OnParentDeath(this); - - public virtual DeathMoveResult GetInventoryMoveResultFor(Item item) => item.OnInventoryDeath(this); - - public virtual void Kill() - { - if (!CanBeDamaged()) - { - return; - } - - if (!Alive || IsDeadBondedPet) - { - return; - } - - if (Deleted) - { - return; - } - - if (!Region.OnBeforeDeath(this)) - { - return; - } - - if (!OnBeforeDeath()) - { - return; - } - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - { - box.Close(); - } - - m_NetState?.CancelAllTrades(); - - m_Spell?.OnCasterKilled(); - // m_Spell.Disturb( DisturbType.Kill ); - - m_Target?.Cancel(this, TargetCancelType.Canceled); - - DisruptiveAction(); + m_NetState.SendDeathStatus(true); Warmode = false; - DropHolding(); + BodyMod = 0; + // Body = this.Female ? 0x193 : 0x192; + Body = Race.GhostBody(this); + + var deathShroud = new Item(0x204E) { Movable = false, Layer = Layer.OuterTorso }; + + AddItem(deathShroud); + + Items.Remove(deathShroud); + Items.Insert(0, deathShroud); + + Poison = null; + Combatant = null; Hits = 0; Stam = 0; Mana = 0; - Poison = null; - Combatant = null; + EventSink.InvokePlayerDeath(this); - if (Paralyzed) + ProcessDeltaQueue(); + + m_NetState.SendDeathStatus(false); + + CheckStatTimers(); + } + } + + public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => true; + + public virtual void Use(Item item) + { + if (item?.Deleted != false || item.QuestItem || Deleted) + { + return; + } + + DisruptiveAction(); + + if (m_Spell?.OnCasterUsingObject(item) == false) + { + return; + } + + var root = item.RootParent; + var okay = false; + + if (!Utility.InUpdateRange(Location, item.GetWorldLocation())) + { + item.OnDoubleClickOutOfRange(this); + } + else if (!CanSee(item)) + { + item.OnDoubleClickCantSee(this); + } + else if (!item.IsAccessibleTo(this)) + { + var reg = Region.Find(item.GetWorldLocation(), item.Map); + + if (reg?.SendInaccessibleMessage(item, this) != true) { - Paralyzed = false; + item.OnDoubleClickNotAccessible(this); + } + } + else if (!CheckAlive(false)) + { + item.OnDoubleClickDead(this); + } + else if (item.InSecureTrade) + { + item.OnDoubleClickSecureTrade(this); + } + else if (!AllowItemUse(item)) + { + } + else if (!item.CheckItemUse(this, item)) + { + } + else if (root is Mobile mobile && mobile.IsSnoop(this)) + { + item.OnSnoop(this); + } + else if (Region.OnDoubleClick(this, item)) + { + okay = true; + } + + if (okay) + { + // TODO: Is this correct? + if (!item.Deleted) + { + item.OnItemUsed(this, item); } - if (Frozen) + // TODO: Is this correct? + if (!item.Deleted) { - Frozen = false; + item.OnDoubleClick(this); } + } + } - var content = new List(); - var equip = new List(); - var moveToPack = new List(); + public virtual void Use(Mobile m) + { + if (m?.Deleted != false || Deleted) + { + return; + } - var itemsCopy = new List(Items); + DisruptiveAction(); - var pack = Backpack; + if (m_Spell?.OnCasterUsingObject(m) == false) + { + return; + } - for (var i = 0; i < itemsCopy.Count; ++i) + if (!Utility.InUpdateRange(Location, m.Location)) + { + m.OnDoubleClickOutOfRange(this); + } + else if (!CanSee(m)) + { + m.OnDoubleClickCantSee(this); + } + else if (!CheckAlive(false)) + { + m.OnDoubleClickDead(this); + } + else if (Region.OnDoubleClick(this, m) && !m.Deleted) + { + m.OnDoubleClick(this); + } + } + + public virtual void Lift(Item item, int amount, out bool rejected, out LRReason reject) + { + rejected = true; + reject = LRReason.Inspecific; + + if (item == null) + { + return; + } + + var from = this; + var state = m_NetState; + + if (from.AccessLevel >= AccessLevel.GameMaster || Core.TickCount - from.NextActionTime >= 0) + { + if (from.CheckAlive()) { - var item = itemsCopy[i]; + from.DisruptiveAction(); - if (item == pack) + if (from.Holding != null) { - continue; + reject = LRReason.AreHolding; } - - var res = GetParentMoveResultFor(item); - - switch (res) + else if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(item.GetWorldLocation(), 2)) { - case DeathMoveResult.MoveToCorpse: - { - content.Add(item); - equip.Add(item); - break; - } - case DeathMoveResult.MoveToBackpack: - { - moveToPack.Add(item); - break; - } + reject = LRReason.OutOfRange; } - } - - if (pack != null) - { - var packCopy = new List(pack.Items); - - for (var i = 0; i < packCopy.Count; ++i) + else if (!from.CanSee(item) || !from.InLOS(item)) { - var item = packCopy[i]; - - var res = GetInventoryMoveResultFor(item); - - if (res == DeathMoveResult.MoveToCorpse) + reject = LRReason.OutOfSight; + } + else if (!item.VerifyMove(from)) + { + reject = LRReason.CannotLift; + } + else if (!item.IsAccessibleTo(from)) + { + reject = LRReason.CannotLift; + } + else if (item.Nontransferable && amount != item.Amount) + { + if (item.QuestItem) { - content.Add(item); + from.SendLocalizedMessage(1074868); // Stacks of quest items cannot be unstacked. + } + + reject = LRReason.CannotLift; + } + else if (!item.CheckLift(from, item, ref reject)) + { + } + else + { + var root = item.RootParent; + + if (root is Mobile mobile && !mobile.CheckNonlocalLift(from, item)) + { + reject = LRReason.TryToSteal; + } + else if (!from.OnDragLift(item) || !item.OnDragLift(from)) + { + reject = LRReason.Inspecific; + } + else if (!from.CheckAlive()) + { + reject = LRReason.Inspecific; } else { - moveToPack.Add(item); + item.SetLastMoved(); + + if (item.Spawner != null) + { + item.Spawner.Remove(item); + item.Spawner = null; + } + + var oldAmount = item.Amount; + + if (oldAmount <= 0) + { + logger.Error( + "Item {Type} ({Serial}) has amount of {OldAmount}, but must be at least 1", + item.GetType(), + item.Serial, + oldAmount + ); + } + else + { + amount = Math.Clamp(amount, 1, oldAmount); + + if (amount < oldAmount) + { + LiftItemDupe(item, amount); + } + } + + var map = from.Map; + + if (DragEffects && map != null && root is null or Item) + { + var eable = map.GetClientsInRange(from.Location); + var rootItem = root as Item; + + Span buffer = stackalloc byte[OutgoingPlayerPackets.DragEffectPacketLength].InitializePacket(); + + foreach (var ns in eable) + { + if (ns.Mobile != from && ns.Mobile.CanSee(from) && ns.Mobile.InLOS(from) && + ns.Mobile.CanSee(root)) + { + OutgoingPlayerPackets.CreateDragEffect( + buffer, + rootItem?.Serial ?? Serial.Zero, + rootItem?.Location ?? item.Location, + from.Serial, + from.Location, + item.ItemID, + item.Hue, + amount + ); + + ns.Send(buffer); + } + } + + eable.Free(); + } + + var fixLoc = item.Location; + var fixMap = item.Map; + var shouldFix = item.Parent == null; + + item.RecordBounce(); + item.OnItemLifted(from, item); + item.Internalize(); + + from.Holding = item; + + from.SendSound(item.GetLiftSound(from)); + + from.NextActionTime = Core.TickCount + ActionDelay; + + if (fixMap != null && shouldFix) + { + fixMap.FixColumn(fixLoc.m_X, fixLoc.m_Y); + } + + reject = LRReason.Inspecific; + rejected = false; } } + } + else + { + reject = LRReason.Inspecific; + } + } + else + { + SendActionMessage(); + reject = LRReason.Inspecific; + } - for (var i = 0; i < moveToPack.Count; ++i) + if (rejected && state != null) + { + state.SendLiftReject(reject); + + if (item.Deleted) + { + return; + } + + if (item.Parent is Item) + { + state.SendContainerContentUpdate(item); + } + else if (item.Parent is Mobile) + { + state.SendEquipUpdate(item); + } + else + { + item.SendInfoTo(state); + } + + if (item.Parent != null) + { + item.SendOPLPacketTo(state); + } + } + } + + public static T LiftItemDupe(T oldItem, int amount) where T : Item + { + T item; + try + { + item = oldItem.GetType().CreateInstance(); + } + catch (Exception e) + { + logger.Warning( + e, + "[{Serial} {Name}]: Item must have a zero parameter constructor to be separated from a stack.", + oldItem.Serial, + oldItem.GetType().Name + ); + + return null; + } + + var oldAmount = oldItem.Amount; + oldItem.Amount = amount; + + item.Visible = oldItem.Visible; + item.Movable = oldItem.Movable; + item.LootType = oldItem.LootType; + item.Direction = oldItem.Direction; + item.Hue = oldItem.Hue; + item.ItemID = oldItem.ItemID; + item.Location = oldItem.Location; + item.Layer = oldItem.Layer; + item.Name = oldItem.Name; + item.Weight = oldItem.Weight; + + item.Amount = oldAmount - amount; + item.Map = oldItem.Map; + + oldItem.OnAfterDuped(item); + + if (oldItem.Parent is Mobile parentMobile) + { + parentMobile.AddItem(item); + } + else if (oldItem.Parent is Item parentItem) + { + parentItem.AddItem(item); + } + + item.Delta(ItemDelta.Update); + + return item; + } + + public virtual void SendDropEffect(Item item) + { + if (!DragEffects || item.Deleted) + { + return; + } + + var map = m_Map; + var root = item.RootParent; + var rootItem = root as Item; + + if (map == null || root != null && rootItem == null) + { + return; + } + + var eable = map.GetClientsInRange(m_Location); + + Span buffer = stackalloc byte[OutgoingPlayerPackets.DragEffectPacketLength].InitializePacket(); + + foreach (var ns in eable) + { + if (ns.StygianAbyss || ns.Mobile == this || + !ns.Mobile.CanSee(this) || !ns.Mobile.InLOS(this) || !ns.Mobile.CanSee(root)) + { + continue; + } + + OutgoingPlayerPackets.CreateDragEffect( + buffer, + Serial, + Location, + rootItem?.Serial ?? Serial.Zero, + rootItem?.Location ?? item.Location, + item.ItemID, + item.Hue, + item.Amount + ); + + ns.Send(buffer); + } + + eable.Free(); + } + + public virtual bool Drop(Item to, Point3D loc) + { + var from = this; + var item = from.Holding; + + var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; + + from.Holding = null; + + if (!valid) + { + return false; + } + + var bounced = true; + + item.SetLastMoved(); + + if (to == null || !item.DropToItem(from, to, loc)) + { + item.Bounce(from); + } + else + { + bounced = false; + } + + item.ClearBounce(); + + if (!bounced) + { + SendDropEffect(item); + } + + return !bounced; + } + + public virtual bool Drop(Point3D loc) + { + var from = this; + var item = from.Holding; + + var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; + + from.Holding = null; + + if (!valid) + { + return false; + } + + var bounced = true; + + item.SetLastMoved(); + + if (!item.DropToWorld(from, loc)) + { + item.Bounce(from); + } + else + { + bounced = false; + } + + item.ClearBounce(); + + if (!bounced) + { + SendDropEffect(item); + } + + return !bounced; + } + + public virtual bool Drop(Mobile to, Point3D loc) + { + var from = this; + var item = from.Holding; + + var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; + + from.Holding = null; + + if (!valid) + { + return false; + } + + var bounced = true; + + item.SetLastMoved(); + + if (to == null || !item.DropToMobile(from, to, loc)) + { + item.Bounce(from); + } + else + { + bounced = false; + } + + item.ClearBounce(); + + if (!bounced) + { + SendDropEffect(item); + } + + return !bounced; + } + + public virtual bool MutateSpeech(List hears, ref string text, ref object context) + { + if (Alive) + { + return false; + } + + using var sb = new ValueStringBuilder(stackalloc char[Math.Min(text.Length, 256)]); + for (var i = 0; i < text.Length; ++i) + { + sb.Append(text[i] != ' ' ? (GhostChars ?? DefaultGhostChars).RandomElement() : ' '); + } + + text = sb.ToString(); + context = m_GhostMutateContext; + return true; + } + + private void AutoManifest() + { + if (!Alive) + { + Warmode = false; + } + } + + public virtual void Manifest(TimeSpan delay) + { + Warmode = true; + _autoManifestTimerToken.Cancel(); + Timer.StartTimer(delay, AutoManifest, out _autoManifestTimerToken); + } + + public virtual bool CheckSpeechManifest() + { + if (Alive) + { + return false; + } + + var delay = AutoManifestTimeout; + + if (delay > TimeSpan.Zero && (!Warmode || _autoManifestTimerToken.Running)) + { + Manifest(delay); + return true; + } + + return false; + } + + public virtual bool CheckHearsMutatedSpeech(Mobile m, object context) => + context != m_GhostMutateContext || m.Alive && !m.CanHearGhosts; + + private void AddSpeechItemsFrom(List list, Container cont) + { + for (var i = 0; i < cont.Items.Count; ++i) + { + var item = cont.Items[i]; + + if (item.HandlesOnSpeech) + { + list.Add(item); + } + + if (item is Container container) + { + AddSpeechItemsFrom(list, container); + } + } + } + + public virtual void DoSpeech(string text, int[] keywords, MessageType type, int hue) + { + if (Deleted || CommandSystem.Handle(this, text, type)) + { + return; + } + + var range = 15; + + switch (type) + { + case MessageType.Regular: + SpeechHue = hue; + break; + case MessageType.Emote: + EmoteHue = hue; + break; + case MessageType.Whisper: + WhisperHue = hue; + range = 1; + break; + case MessageType.Yell: + YellHue = hue; + range = 18; + break; + case MessageType.System: + break; + case MessageType.Label: + break; + case MessageType.Focus: + break; + case MessageType.Spell: + break; + case MessageType.Guild: + break; + case MessageType.Alliance: + break; + case MessageType.Command: + break; + case MessageType.Encoded: + break; + default: + type = MessageType.Regular; + break; + } + + var regArgs = new SpeechEventArgs(this, text, type, hue, keywords); + + EventSink.InvokeSpeech(regArgs); + Region.OnSpeech(regArgs); + OnSaid(regArgs); + + if (regArgs.Blocked) + { + return; + } + + text = regArgs.Speech; + + if (string.IsNullOrEmpty(text)) + { + return; + } + + var hears = m_Hears; + var onSpeech = m_OnSpeech; + + if (m_Map != null) + { + var eable = m_Map.GetObjectsInRange(m_Location, range); + + foreach (var o in eable) + { + if (o is Mobile heard) { - var item = moveToPack[i]; - - if (RetainPackLocsOnDeath && item.Parent == pack) + if (!heard.CanSee(this) || !NoSpeechLOS && heard.Player && !heard.InLOS(this)) { continue; } - pack.DropItem(item); - } - } - - HairInfo hair = null; - if (m_Hair != null) - { - hair = new HairInfo(m_Hair.ItemID, m_Hair.Hue); - } - - FacialHairInfo facialhair = null; - if (m_FacialHair != null) - { - facialhair = new FacialHairInfo(m_FacialHair.ItemID, m_FacialHair.Hue); - } - - var c = CreateCorpseHandler?.Invoke(this, hair, facialhair, content, equip); - - if (m_Map != null) - { - var eable = m_Map.GetClientsInRange(m_Location); - var corpseSerial = c?.Serial ?? Serial.Zero; - - Span deathAnimation = stackalloc byte[OutgoingMobilePackets.DeathAnimationPacketLength].InitializePacket(); - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - - foreach (var state in eable) - { - if (state != m_NetState) + if (heard.m_NetState != null) { - OutgoingMobilePackets.CreateDeathAnimation(deathAnimation, Serial, corpseSerial); - state.Send(deathAnimation); - - if (!state.Mobile.CanSee(this)) - { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - state.Send(removeEntity); - } + hears.Add(heard); } - } - eable.Free(); - } - - Region.OnDeath(this); - OnDeath(c); - } - - /// - /// Overridable. Event invoked before the Mobile is killed. - /// - /// - /// - /// True to continue with death, false to override it. - public virtual bool OnBeforeDeath() => true; - - /// - /// Overridable. Event invoked after the Mobile is killed. Primarily, this method is responsible for - /// deleting an NPC or turning a PC into a ghost. - /// - /// - /// - public virtual void OnDeath(Container c) - { - var sound = GetDeathSound(); - - if (sound >= 0) - { - Effects.PlaySound(this, sound); - } - - if (!m_Player) - { - Delete(); - } - else - { - m_NetState.SendDeathStatus(true); - - Warmode = false; - - BodyMod = 0; - // Body = this.Female ? 0x193 : 0x192; - Body = Race.GhostBody(this); - - var deathShroud = new Item(0x204E) { Movable = false, Layer = Layer.OuterTorso }; - - AddItem(deathShroud); - - Items.Remove(deathShroud); - Items.Insert(0, deathShroud); - - Poison = null; - Combatant = null; - - Hits = 0; - Stam = 0; - Mana = 0; - - EventSink.InvokePlayerDeath(this); - - ProcessDeltaQueue(); - - m_NetState.SendDeathStatus(false); - - CheckStatTimers(); - } - } - - public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => true; - - public virtual void Use(Item item) - { - if (item?.Deleted != false || item.QuestItem || Deleted) - { - return; - } - - DisruptiveAction(); - - if (m_Spell?.OnCasterUsingObject(item) == false) - { - return; - } - - var root = item.RootParent; - var okay = false; - - if (!Utility.InUpdateRange(Location, item.GetWorldLocation())) - { - item.OnDoubleClickOutOfRange(this); - } - else if (!CanSee(item)) - { - item.OnDoubleClickCantSee(this); - } - else if (!item.IsAccessibleTo(this)) - { - var reg = Region.Find(item.GetWorldLocation(), item.Map); - - if (reg?.SendInaccessibleMessage(item, this) != true) - { - item.OnDoubleClickNotAccessible(this); - } - } - else if (!CheckAlive(false)) - { - item.OnDoubleClickDead(this); - } - else if (item.InSecureTrade) - { - item.OnDoubleClickSecureTrade(this); - } - else if (!AllowItemUse(item)) - { - } - else if (!item.CheckItemUse(this, item)) - { - } - else if (root is Mobile mobile && mobile.IsSnoop(this)) - { - item.OnSnoop(this); - } - else if (Region.OnDoubleClick(this, item)) - { - okay = true; - } - - if (okay) - { - // TODO: Is this correct? - if (!item.Deleted) - { - item.OnItemUsed(this, item); - } - - // TODO: Is this correct? - if (!item.Deleted) - { - item.OnDoubleClick(this); - } - } - } - - public virtual void Use(Mobile m) - { - if (m?.Deleted != false || Deleted) - { - return; - } - - DisruptiveAction(); - - if (m_Spell?.OnCasterUsingObject(m) == false) - { - return; - } - - if (!Utility.InUpdateRange(Location, m.Location)) - { - m.OnDoubleClickOutOfRange(this); - } - else if (!CanSee(m)) - { - m.OnDoubleClickCantSee(this); - } - else if (!CheckAlive(false)) - { - m.OnDoubleClickDead(this); - } - else if (Region.OnDoubleClick(this, m) && !m.Deleted) - { - m.OnDoubleClick(this); - } - } - - public virtual void Lift(Item item, int amount, out bool rejected, out LRReason reject) - { - rejected = true; - reject = LRReason.Inspecific; - - if (item == null) - { - return; - } - - var from = this; - var state = m_NetState; - - if (from.AccessLevel >= AccessLevel.GameMaster || Core.TickCount - from.NextActionTime >= 0) - { - if (from.CheckAlive()) - { - from.DisruptiveAction(); - - if (from.Holding != null) + if (heard.HandlesOnSpeech(this)) { - reject = LRReason.AreHolding; + onSpeech.Add(heard); } - else if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(item.GetWorldLocation(), 2)) + + for (var i = 0; i < heard.Items.Count; ++i) { - reject = LRReason.OutOfRange; - } - else if (!from.CanSee(item) || !from.InLOS(item)) - { - reject = LRReason.OutOfSight; - } - else if (!item.VerifyMove(from)) - { - reject = LRReason.CannotLift; - } - else if (!item.IsAccessibleTo(from)) - { - reject = LRReason.CannotLift; - } - else if (item.Nontransferable && amount != item.Amount) - { - if (item.QuestItem) - { - from.SendLocalizedMessage(1074868); // Stacks of quest items cannot be unstacked. - } + var item = heard.Items[i]; - reject = LRReason.CannotLift; - } - else if (!item.CheckLift(from, item, ref reject)) - { - } - else - { - var root = item.RootParent; - - if (root is Mobile mobile && !mobile.CheckNonlocalLift(from, item)) - { - reject = LRReason.TryToSteal; - } - else if (!from.OnDragLift(item) || !item.OnDragLift(from)) - { - reject = LRReason.Inspecific; - } - else if (!from.CheckAlive()) - { - reject = LRReason.Inspecific; - } - else - { - item.SetLastMoved(); - - if (item.Spawner != null) - { - item.Spawner.Remove(item); - item.Spawner = null; - } - - var oldAmount = item.Amount; - - if (oldAmount <= 0) - { - logger.Error( - "Item {Type} ({Serial}) has amount of {OldAmount}, but must be at least 1", - item.GetType(), - item.Serial, - oldAmount - ); - } - else - { - amount = Math.Clamp(amount, 1, oldAmount); - - if (amount < oldAmount) - { - LiftItemDupe(item, amount); - } - } - - var map = from.Map; - - if (DragEffects && map != null && root is null or Item) - { - var eable = map.GetClientsInRange(from.Location); - var rootItem = root as Item; - - Span buffer = stackalloc byte[OutgoingPlayerPackets.DragEffectPacketLength].InitializePacket(); - - foreach (var ns in eable) - { - if (ns.Mobile != from && ns.Mobile.CanSee(from) && ns.Mobile.InLOS(from) && - ns.Mobile.CanSee(root)) - { - OutgoingPlayerPackets.CreateDragEffect( - buffer, - rootItem?.Serial ?? Serial.Zero, - rootItem?.Location ?? item.Location, - from.Serial, - from.Location, - item.ItemID, - item.Hue, - amount - ); - - ns.Send(buffer); - } - } - - eable.Free(); - } - - var fixLoc = item.Location; - var fixMap = item.Map; - var shouldFix = item.Parent == null; - - item.RecordBounce(); - item.OnItemLifted(from, item); - item.Internalize(); - - from.Holding = item; - - from.SendSound(item.GetLiftSound(from)); - - from.NextActionTime = Core.TickCount + ActionDelay; - - if (fixMap != null && shouldFix) - { - fixMap.FixColumn(fixLoc.m_X, fixLoc.m_Y); - } - - reject = LRReason.Inspecific; - rejected = false; - } - } - } - else - { - reject = LRReason.Inspecific; - } - } - else - { - SendActionMessage(); - reject = LRReason.Inspecific; - } - - if (rejected && state != null) - { - state.SendLiftReject(reject); - - if (item.Deleted) - { - return; - } - - if (item.Parent is Item) - { - state.SendContainerContentUpdate(item); - } - else if (item.Parent is Mobile) - { - state.SendEquipUpdate(item); - } - else - { - item.SendInfoTo(state); - } - - if (item.Parent != null) - { - item.SendOPLPacketTo(state); - } - } - } - - public static T LiftItemDupe(T oldItem, int amount) where T : Item - { - T item; - try - { - item = oldItem.GetType().CreateInstance(); - } - catch (Exception e) - { - logger.Warning( - e, - "[{Serial} {Name}]: Item must have a zero parameter constructor to be separated from a stack.", - oldItem.Serial, - oldItem.GetType().Name - ); - - return null; - } - - var oldAmount = oldItem.Amount; - oldItem.Amount = amount; - - item.Visible = oldItem.Visible; - item.Movable = oldItem.Movable; - item.LootType = oldItem.LootType; - item.Direction = oldItem.Direction; - item.Hue = oldItem.Hue; - item.ItemID = oldItem.ItemID; - item.Location = oldItem.Location; - item.Layer = oldItem.Layer; - item.Name = oldItem.Name; - item.Weight = oldItem.Weight; - - item.Amount = oldAmount - amount; - item.Map = oldItem.Map; - - oldItem.OnAfterDuped(item); - - if (oldItem.Parent is Mobile parentMobile) - { - parentMobile.AddItem(item); - } - else if (oldItem.Parent is Item parentItem) - { - parentItem.AddItem(item); - } - - item.Delta(ItemDelta.Update); - - return item; - } - - public virtual void SendDropEffect(Item item) - { - if (!DragEffects || item.Deleted) - { - return; - } - - var map = m_Map; - var root = item.RootParent; - var rootItem = root as Item; - - if (map == null || root != null && rootItem == null) - { - return; - } - - var eable = map.GetClientsInRange(m_Location); - - Span buffer = stackalloc byte[OutgoingPlayerPackets.DragEffectPacketLength].InitializePacket(); - - foreach (var ns in eable) - { - if (ns.StygianAbyss || ns.Mobile == this || - !ns.Mobile.CanSee(this) || !ns.Mobile.InLOS(this) || !ns.Mobile.CanSee(root)) - { - continue; - } - - OutgoingPlayerPackets.CreateDragEffect( - buffer, - Serial, - Location, - rootItem?.Serial ?? Serial.Zero, - rootItem?.Location ?? item.Location, - item.ItemID, - item.Hue, - item.Amount - ); - - ns.Send(buffer); - } - - eable.Free(); - } - - public virtual bool Drop(Item to, Point3D loc) - { - var from = this; - var item = from.Holding; - - var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; - - from.Holding = null; - - if (!valid) - { - return false; - } - - var bounced = true; - - item.SetLastMoved(); - - if (to == null || !item.DropToItem(from, to, loc)) - { - item.Bounce(from); - } - else - { - bounced = false; - } - - item.ClearBounce(); - - if (!bounced) - { - SendDropEffect(item); - } - - return !bounced; - } - - public virtual bool Drop(Point3D loc) - { - var from = this; - var item = from.Holding; - - var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; - - from.Holding = null; - - if (!valid) - { - return false; - } - - var bounced = true; - - item.SetLastMoved(); - - if (!item.DropToWorld(from, loc)) - { - item.Bounce(from); - } - else - { - bounced = false; - } - - item.ClearBounce(); - - if (!bounced) - { - SendDropEffect(item); - } - - return !bounced; - } - - public virtual bool Drop(Mobile to, Point3D loc) - { - var from = this; - var item = from.Holding; - - var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; - - from.Holding = null; - - if (!valid) - { - return false; - } - - var bounced = true; - - item.SetLastMoved(); - - if (to == null || !item.DropToMobile(from, to, loc)) - { - item.Bounce(from); - } - else - { - bounced = false; - } - - item.ClearBounce(); - - if (!bounced) - { - SendDropEffect(item); - } - - return !bounced; - } - - public virtual bool MutateSpeech(List hears, ref string text, ref object context) - { - if (Alive) - { - return false; - } - - using var sb = new ValueStringBuilder(stackalloc char[Math.Min(text.Length, 256)]); - for (var i = 0; i < text.Length; ++i) - { - sb.Append(text[i] != ' ' ? (GhostChars ?? DefaultGhostChars).RandomElement() : ' '); - } - - text = sb.ToString(); - context = m_GhostMutateContext; - return true; - } - - private void AutoManifest() - { - if (!Alive) - { - Warmode = false; - } - } - - public virtual void Manifest(TimeSpan delay) - { - Warmode = true; - _autoManifestTimerToken.Cancel(); - Timer.StartTimer(delay, AutoManifest, out _autoManifestTimerToken); - } - - public virtual bool CheckSpeechManifest() - { - if (Alive) - { - return false; - } - - var delay = AutoManifestTimeout; - - if (delay > TimeSpan.Zero && (!Warmode || _autoManifestTimerToken.Running)) - { - Manifest(delay); - return true; - } - - return false; - } - - public virtual bool CheckHearsMutatedSpeech(Mobile m, object context) => - context != m_GhostMutateContext || m.Alive && !m.CanHearGhosts; - - private void AddSpeechItemsFrom(List list, Container cont) - { - for (var i = 0; i < cont.Items.Count; ++i) - { - var item = cont.Items[i]; - - if (item.HandlesOnSpeech) - { - list.Add(item); - } - - if (item is Container container) - { - AddSpeechItemsFrom(list, container); - } - } - } - - public virtual void DoSpeech(string text, int[] keywords, MessageType type, int hue) - { - if (Deleted || CommandSystem.Handle(this, text, type)) - { - return; - } - - var range = 15; - - switch (type) - { - case MessageType.Regular: - SpeechHue = hue; - break; - case MessageType.Emote: - EmoteHue = hue; - break; - case MessageType.Whisper: - WhisperHue = hue; - range = 1; - break; - case MessageType.Yell: - YellHue = hue; - range = 18; - break; - case MessageType.System: - break; - case MessageType.Label: - break; - case MessageType.Focus: - break; - case MessageType.Spell: - break; - case MessageType.Guild: - break; - case MessageType.Alliance: - break; - case MessageType.Command: - break; - case MessageType.Encoded: - break; - default: - type = MessageType.Regular; - break; - } - - var regArgs = new SpeechEventArgs(this, text, type, hue, keywords); - - EventSink.InvokeSpeech(regArgs); - Region.OnSpeech(regArgs); - OnSaid(regArgs); - - if (regArgs.Blocked) - { - return; - } - - text = regArgs.Speech; - - if (string.IsNullOrEmpty(text)) - { - return; - } - - var hears = m_Hears; - var onSpeech = m_OnSpeech; - - if (m_Map != null) - { - var eable = m_Map.GetObjectsInRange(m_Location, range); - - foreach (var o in eable) - { - if (o is Mobile heard) - { - if (!heard.CanSee(this) || !NoSpeechLOS && heard.Player && !heard.InLOS(this)) - { - continue; - } - - if (heard.m_NetState != null) - { - hears.Add(heard); - } - - if (heard.HandlesOnSpeech(this)) - { - onSpeech.Add(heard); - } - - for (var i = 0; i < heard.Items.Count; ++i) - { - var item = heard.Items[i]; - - if (item.HandlesOnSpeech) - { - onSpeech.Add(item); - } - - if (item is Container container) - { - AddSpeechItemsFrom(onSpeech, container); - } - } - } - else if (o is Item item) - { if (item.HandlesOnSpeech) { onSpeech.Add(item); @@ -5476,1053 +5463,1096 @@ namespace Server } } } - - eable.Free(); - - object mutateContext = null; - var mutatedText = text; - SpeechEventArgs mutatedArgs = null; - - if (MutateSpeech(hears, ref mutatedText, ref mutateContext)) + else if (o is Item item) { - mutatedArgs = new SpeechEventArgs(this, mutatedText, type, hue, Array.Empty()); - } - - CheckSpeechManifest(); - - ProcessDelta(); - - Span regBuffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].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) - { - var heard = hears[i]; - - if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) + if (item.HandlesOnSpeech) { - var length = OutgoingMessagePackets.CreateMessage( - regBuffer, Serial, Body, type, hue, 3, false, m_Language, Name, text - ); - - if (length != regBuffer.Length) - { - regBuffer = regBuffer[..length]; // Adjust to the actual size - } - - heard.OnSpeech(regArgs); - heard.NetState?.Send(regBuffer); + onSpeech.Add(item); } - else + + if (item is Container container) { - var length = OutgoingMessagePackets.CreateMessage( - mutBuffer, Serial, Body, type, hue, 3, false, m_Language, Name, mutatedText - ); - - if (length != mutBuffer.Length) - { - mutBuffer = mutBuffer[..length]; // Adjust to the actual size - } - - heard.OnSpeech(mutatedArgs); - heard.NetState?.Send(mutBuffer); + AddSpeechItemsFrom(onSpeech, container); } } - - if (onSpeech.Count > 1) - { - onSpeech.Sort(LocationComparer.GetInstance(this)); - } - - for (var i = 0; i < onSpeech.Count; ++i) - { - var obj = onSpeech[i]; - - if (obj is Mobile heard) - { - if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) - { - heard.OnSpeech(regArgs); - } - else - { - heard.OnSpeech(mutatedArgs); - } - } - else - { - ((Item)obj).OnSpeech(regArgs); - } - } - - if (m_Hears.Count > 0) - { - m_Hears.Clear(); - } - - if (m_OnSpeech.Count > 0) - { - m_OnSpeech.Clear(); - } - } - } - - public Mobile FindMostRecentDamager(bool allowSelf) => FindMostRecentDamageEntry(allowSelf)?.Damager; - - public DamageEntry FindMostRecentDamageEntry(bool allowSelf) - { - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - { - continue; - } - - var de = DamageEntries[i]; - - if (de.HasExpired) - { - DamageEntries.RemoveAt(i); - } - else if (allowSelf || de.Damager != this) - { - return de; - } - } - - return null; - } - - public Mobile FindLeastRecentDamager(bool allowSelf) => FindLeastRecentDamageEntry(allowSelf)?.Damager; - - public DamageEntry FindLeastRecentDamageEntry(bool allowSelf) - { - for (var i = 0; i < DamageEntries.Count; ++i) - { - if (i < 0) - { - continue; - } - - var de = DamageEntries[i]; - - if (de.HasExpired) - { - DamageEntries.RemoveAt(i); - --i; - } - else if (allowSelf || de.Damager != this) - { - return de; - } - } - - return null; - } - - public Mobile FindMostTotalDamager(bool allowSelf) => FindMostTotalDamageEntry(allowSelf)?.Damager; - - public DamageEntry FindMostTotalDamageEntry(bool allowSelf) - { - DamageEntry mostTotal = null; - - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - { - continue; - } - - var de = DamageEntries[i]; - - if (de.HasExpired) - { - DamageEntries.RemoveAt(i); - } - else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven > mostTotal.DamageGiven)) - { - mostTotal = de; - } - } - - return mostTotal; - } - - public Mobile FindLeastTotalDamager(bool allowSelf) => FindLeastTotalDamageEntry(allowSelf)?.Damager; - - public DamageEntry FindLeastTotalDamageEntry(bool allowSelf) - { - DamageEntry mostTotal = null; - - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - { - continue; - } - - var de = DamageEntries[i]; - - if (de.HasExpired) - { - DamageEntries.RemoveAt(i); - } - else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven < mostTotal.DamageGiven)) - { - mostTotal = de; - } - } - - return mostTotal; - } - - public DamageEntry FindDamageEntryFor(Mobile m) - { - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - { - continue; - } - - var de = DamageEntries[i]; - - if (de.HasExpired) - { - DamageEntries.RemoveAt(i); - } - else if (de.Damager == m) - { - return de; - } - } - - return null; - } - - public virtual Mobile GetDamageMaster(Mobile damagee) => null; - - public virtual DamageEntry RegisterDamage(int amount, Mobile from) - { - var de = FindDamageEntryFor(from) ?? new DamageEntry(from); - - de.DamageGiven += amount; - de.LastDamage = Core.Now; - - DamageEntries.Remove(de); - DamageEntries.Add(de); - - var master = from.GetDamageMaster(this); - - if (master != null) - { - var list = de.Responsible; - - if (list == null) - { - de.Responsible = list = new List(); - } - - DamageEntry resp = null; - foreach (var check in list) - { - if (check.Damager == master) - { - resp = check; - break; - } - } - - if (resp == null) - { - list.Add(resp = new DamageEntry(master)); - } - - resp.DamageGiven += amount; - resp.LastDamage = Core.Now; - } - - return de; - } - - /// - /// Overridable. Virtual event invoked when the Mobile is damaged. It is called before - /// hit points are lowered or the Mobile is killed. - /// - /// - /// - /// - public virtual void OnDamage(int amount, Mobile from, bool willKill) - { - } - - public virtual bool CanBeDamaged() => !m_Blessed; - - public virtual void Damage(int amount, Mobile from = null, bool informMount = true) - { - if (amount <= 0) - { - return; - } - - if (!CanBeDamaged() || Deleted) - { - return; - } - - if (!Region.OnDamage(this, ref amount)) - { - return; - } - - var oldHits = Hits; - var newHits = oldHits - amount; - - m_Spell?.OnCasterHurt(); - - // if (m_Spell != null && m_Spell.State == SpellState.Casting) - // m_Spell.Disturb( DisturbType.Hurt, false, true ); - - if (from != null) - { - RegisterDamage(amount, from); - } - - DisruptiveAction(); - - Paralyzed = false; - - switch (VisibleDamageType) - { - case VisibleDamageType.Related: - { - SendVisibleDamageRelated(from, amount); - break; - } - case VisibleDamageType.Everyone: - { - SendVisibleDamageEveryone(amount); - break; - } - case VisibleDamageType.Selective: - { - SendVisibleDamageSelective(from, amount); - break; - } - } - - OnDamage(amount, from, newHits < 0); - - if (informMount) - { - Mount?.OnRiderDamaged(amount, from, newHits < 0); - } - - if (newHits < 0) - { - LastKiller = from; - - Hits = 0; - - if (oldHits >= 0) - { - Kill(); - } - } - else - { - Hits = newHits; - } - } - - public void SendVisibleDamageRelated(Mobile from, int amount) - { - var ourState = m_NetState ?? GetDamageMaster(from)?.m_NetState; - var theirState = from?.m_NetState ?? from?.GetDamageMaster(this)?.m_NetState; - - if (amount > 0) - { - ourState.SendDamage(Serial, amount); - - if (theirState != null && theirState != ourState) - { - theirState.SendDamage(Serial, amount); - } - } - } - - public void SendVisibleDamageEveryone(int amount) - { - if (amount < 0) - { - return; - } - - var map = m_Map; - - if (map == null) - { - return; - } - - var eable = map.GetClientsInRange(m_Location); - - foreach (var ns in eable) - { - if (ns.Mobile.CanSee(this)) - { - ns.SendDamage(Serial, amount); - } } eable.Free(); - } - public void SendVisibleDamageSelective(Mobile from, int amount) - { - var ourState = m_NetState; - var theirState = from?.m_NetState; + object mutateContext = null; + var mutatedText = text; + SpeechEventArgs mutatedArgs = null; - var damager = from; - var damaged = this; - - if (ourState == null) + if (MutateSpeech(hears, ref mutatedText, ref mutateContext)) { - var master = GetDamageMaster(from); + mutatedArgs = new SpeechEventArgs(this, mutatedText, type, hue, Array.Empty()); + } - if (master != null) + CheckSpeechManifest(); + + ProcessDelta(); + + Span regBuffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].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) + { + var heard = hears[i]; + + if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) { - damaged = master; - ourState = master.m_NetState; + var length = OutgoingMessagePackets.CreateMessage( + regBuffer, Serial, Body, type, hue, 3, false, m_Language, Name, text + ); + + if (length != regBuffer.Length) + { + regBuffer = regBuffer[..length]; // Adjust to the actual size + } + + heard.OnSpeech(regArgs); + heard.NetState?.Send(regBuffer); + } + else + { + var length = OutgoingMessagePackets.CreateMessage( + mutBuffer, Serial, Body, type, hue, 3, false, m_Language, Name, mutatedText + ); + + if (length != mutBuffer.Length) + { + mutBuffer = mutBuffer[..length]; // Adjust to the actual size + } + + heard.OnSpeech(mutatedArgs); + heard.NetState?.Send(mutBuffer); } } - if (!damaged.ShowVisibleDamage) + if (onSpeech.Count > 1) { - return; + onSpeech.Sort(LocationComparer.GetInstance(this)); } - if (theirState == null && from != null) + for (var i = 0; i < onSpeech.Count; ++i) { - var master = from.GetDamageMaster(this); + var obj = onSpeech[i]; - if (master != null) + if (obj is Mobile heard) { - damager = master; - theirState = master.m_NetState; + if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) + { + heard.OnSpeech(regArgs); + } + else + { + heard.OnSpeech(mutatedArgs); + } + } + else + { + ((Item)obj).OnSpeech(regArgs); } } - if (amount > 0) + if (m_Hears.Count > 0) { - if (damaged.CanSeeVisibleDamage) - { - ourState.SendDamage(Serial, amount); - } + m_Hears.Clear(); + } - if (theirState != null && theirState != ourState && damager.CanSeeVisibleDamage) + if (m_OnSpeech.Count > 0) + { + m_OnSpeech.Clear(); + } + } + } + + public Mobile FindMostRecentDamager(bool allowSelf) => FindMostRecentDamageEntry(allowSelf)?.Damager; + + public DamageEntry FindMostRecentDamageEntry(bool allowSelf) + { + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + { + continue; + } + + var de = DamageEntries[i]; + + if (de.HasExpired) + { + DamageEntries.RemoveAt(i); + } + else if (allowSelf || de.Damager != this) + { + return de; + } + } + + return null; + } + + public Mobile FindLeastRecentDamager(bool allowSelf) => FindLeastRecentDamageEntry(allowSelf)?.Damager; + + public DamageEntry FindLeastRecentDamageEntry(bool allowSelf) + { + for (var i = 0; i < DamageEntries.Count; ++i) + { + if (i < 0) + { + continue; + } + + var de = DamageEntries[i]; + + if (de.HasExpired) + { + DamageEntries.RemoveAt(i); + --i; + } + else if (allowSelf || de.Damager != this) + { + return de; + } + } + + return null; + } + + public Mobile FindMostTotalDamager(bool allowSelf) => FindMostTotalDamageEntry(allowSelf)?.Damager; + + public DamageEntry FindMostTotalDamageEntry(bool allowSelf) + { + DamageEntry mostTotal = null; + + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + { + continue; + } + + var de = DamageEntries[i]; + + if (de.HasExpired) + { + DamageEntries.RemoveAt(i); + } + else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven > mostTotal.DamageGiven)) + { + mostTotal = de; + } + } + + return mostTotal; + } + + public Mobile FindLeastTotalDamager(bool allowSelf) => FindLeastTotalDamageEntry(allowSelf)?.Damager; + + public DamageEntry FindLeastTotalDamageEntry(bool allowSelf) + { + DamageEntry mostTotal = null; + + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + { + continue; + } + + var de = DamageEntries[i]; + + if (de.HasExpired) + { + DamageEntries.RemoveAt(i); + } + else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven < mostTotal.DamageGiven)) + { + mostTotal = de; + } + } + + return mostTotal; + } + + public DamageEntry FindDamageEntryFor(Mobile m) + { + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + { + continue; + } + + var de = DamageEntries[i]; + + if (de.HasExpired) + { + DamageEntries.RemoveAt(i); + } + else if (de.Damager == m) + { + return de; + } + } + + return null; + } + + public virtual Mobile GetDamageMaster(Mobile damagee) => null; + + public virtual DamageEntry RegisterDamage(int amount, Mobile from) + { + var de = FindDamageEntryFor(from) ?? new DamageEntry(from); + + de.DamageGiven += amount; + de.LastDamage = Core.Now; + + DamageEntries.Remove(de); + DamageEntries.Add(de); + + var master = from.GetDamageMaster(this); + + if (master != null) + { + var list = de.Responsible; + + if (list == null) + { + de.Responsible = list = new List(); + } + + DamageEntry resp = null; + foreach (var check in list) + { + if (check.Damager == master) { - theirState.SendDamage(Serial, amount); + resp = check; + break; } } + + if (resp == null) + { + list.Add(resp = new DamageEntry(master)); + } + + resp.DamageGiven += amount; + resp.LastDamage = Core.Now; } - public void Heal(int amount) => Heal(amount, this); + return de; + } - public void Heal(int amount, Mobile from, bool message = true) + /// + /// Overridable. Virtual event invoked when the Mobile is damaged. It is called before + /// hit points are lowered or the Mobile is killed. + /// + /// + /// + /// + public virtual void OnDamage(int amount, Mobile from, bool willKill) + { + } + + public virtual bool CanBeDamaged() => !m_Blessed; + + public virtual void Damage(int amount, Mobile from = null, bool informMount = true) + { + if (amount <= 0) { - if (!Alive || IsDeadBondedPet) + return; + } + + if (!CanBeDamaged() || Deleted) + { + return; + } + + if (!Region.OnDamage(this, ref amount)) + { + return; + } + + var oldHits = Hits; + var newHits = oldHits - amount; + + m_Spell?.OnCasterHurt(); + + // if (m_Spell != null && m_Spell.State == SpellState.Casting) + // m_Spell.Disturb( DisturbType.Hurt, false, true ); + + if (from != null) + { + RegisterDamage(amount, from); + } + + DisruptiveAction(); + + Paralyzed = false; + + switch (VisibleDamageType) + { + case VisibleDamageType.Related: + { + SendVisibleDamageRelated(from, amount); + break; + } + case VisibleDamageType.Everyone: + { + SendVisibleDamageEveryone(amount); + break; + } + case VisibleDamageType.Selective: + { + SendVisibleDamageSelective(from, amount); + break; + } + } + + OnDamage(amount, from, newHits < 0); + + if (informMount) + { + Mount?.OnRiderDamaged(amount, from, newHits < 0); + } + + if (newHits < 0) + { + LastKiller = from; + + Hits = 0; + + if (oldHits >= 0) { - return; + Kill(); } + } + else + { + Hits = newHits; + } + } - if (!Region.OnHeal(this, ref amount)) + public void SendVisibleDamageRelated(Mobile from, int amount) + { + var ourState = m_NetState ?? GetDamageMaster(from)?.m_NetState; + var theirState = from?.m_NetState ?? from?.GetDamageMaster(this)?.m_NetState; + + if (amount > 0) + { + ourState.SendDamage(Serial, amount); + + if (theirState != null && theirState != ourState) { - return; + theirState.SendDamage(Serial, amount); } + } + } - OnHeal(ref amount, from); + public void SendVisibleDamageEveryone(int amount) + { + if (amount < 0) + { + return; + } - if (Hits + amount > HitsMax) + var map = m_Map; + + if (map == null) + { + return; + } + + var eable = map.GetClientsInRange(m_Location); + + foreach (var ns in eable) + { + if (ns.Mobile.CanSee(this)) { - amount = HitsMax - Hits; - } - - Hits += amount; - - if (message && amount > 0) - { - m_NetState.SendMessageLocalizedAffix( - Serial.MinusOne, - -1, - MessageType.Label, - 0x3B2, - 3, - 1008158, - "", - AffixType.Append | AffixType.System, - amount.ToString() - ); + ns.SendDamage(Serial, amount); } } - public virtual void OnHeal(ref int amount, Mobile from) - { - } + eable.Free(); + } - public virtual void BeforeSerialize() - { - } + public void SendVisibleDamageSelective(Mobile from, int amount) + { + var ourState = m_NetState; + var theirState = from?.m_NetState; - public virtual void Deserialize(IGenericReader reader) - { - var version = reader.ReadInt(); + var damager = from; + var damaged = this; - switch (version) + if (ourState == null) + { + var master = GetDamageMaster(from); + + if (master != null) { - case 33: - { - // Removed created - goto case 32; - } - case 32: - { - // Removed StuckMenu - goto case 31; - } - case 31: - { - LastStrGain = reader.ReadDeltaTime(); - LastIntGain = reader.ReadDeltaTime(); - LastDexGain = reader.ReadDeltaTime(); + damaged = master; + ourState = master.m_NetState; + } + } - goto case 30; - } - case 30: - { - var hairflag = reader.ReadByte(); + if (!damaged.ShowVisibleDamage) + { + return; + } - if ((hairflag & 0x01) != 0) + if (theirState == null && from != null) + { + var master = from.GetDamageMaster(this); + + if (master != null) + { + damager = master; + theirState = master.m_NetState; + } + } + + if (amount > 0) + { + if (damaged.CanSeeVisibleDamage) + { + ourState.SendDamage(Serial, amount); + } + + if (theirState != null && theirState != ourState && damager.CanSeeVisibleDamage) + { + theirState.SendDamage(Serial, amount); + } + } + } + + public void Heal(int amount) => Heal(amount, this); + + public void Heal(int amount, Mobile from, bool message = true) + { + if (!Alive || IsDeadBondedPet) + { + return; + } + + if (!Region.OnHeal(this, ref amount)) + { + return; + } + + OnHeal(ref amount, from); + + if (Hits + amount > HitsMax) + { + amount = HitsMax - Hits; + } + + Hits += amount; + + if (message && amount > 0) + { + m_NetState.SendMessageLocalizedAffix( + Serial.MinusOne, + -1, + MessageType.Label, + 0x3B2, + 3, + 1008158, + "", + AffixType.Append | AffixType.System, + amount.ToString() + ); + } + } + + public virtual void OnHeal(ref int amount, Mobile from) + { + } + + public virtual void BeforeSerialize() + { + } + + public virtual void Deserialize(IGenericReader reader) + { + var version = reader.ReadInt(); + + switch (version) + { + case 33: + { + // Removed created + goto case 32; + } + case 32: + { + // Removed StuckMenu + goto case 31; + } + case 31: + { + LastStrGain = reader.ReadDeltaTime(); + LastIntGain = reader.ReadDeltaTime(); + LastDexGain = reader.ReadDeltaTime(); + + goto case 30; + } + case 30: + { + var hairflag = reader.ReadByte(); + + if ((hairflag & 0x01) != 0) + { + m_Hair = new HairInfo(reader); + } + + if ((hairflag & 0x02) != 0) + { + m_FacialHair = new FacialHairInfo(reader); + } + + goto case 29; + } + case 29: + { + m_Race = reader.ReadRace(); + goto case 28; + } + case 28: + { + if (version <= 30) + { + LastStatGain = reader.ReadDeltaTime(); + } + + goto case 27; + } + case 27: + { + m_TithingPoints = reader.ReadInt(); + + goto case 26; + } + case 26: + case 25: + case 24: + { + Corpse = reader.ReadEntity(); + + goto case 23; + } + case 23: + { + if (version < 33) + { + Created = reader.ReadDateTime(); + } + + goto case 22; + } + case 22: // Just removed followers + case 21: + { + Stabled = reader.ReadEntityList(); + + goto case 20; + } + case 20: + { + CantWalk = reader.ReadBool(); + + goto case 19; + } + case 19: // Just removed variables + case 18: + { + Virtues = new VirtueInfo(reader); + + goto case 17; + } + case 17: + { + Thirst = reader.ReadInt(); + BAC = reader.ReadInt(); + + goto case 16; + } + case 16: + { + m_ShortTermMurders = reader.ReadInt(); + + if (version <= 24) + { + reader.ReadDateTime(); + reader.ReadDateTime(); + } + + goto case 15; + } + case 15: + { + if (version < 22) + { + reader.ReadInt(); // followers + } + + m_FollowersMax = reader.ReadInt(); + + goto case 14; + } + case 14: + { + MagicDamageAbsorb = reader.ReadInt(); + + goto case 13; + } + case 13: + { + GuildFealty = reader.ReadEntity(); + + goto case 12; + } + case 12: + { + m_Guild = reader.ReadEntity(); + + goto case 11; + } + case 11: + { + m_DisplayGuildTitle = reader.ReadBool(); + + goto case 10; + } + case 10: + { + CanSwim = reader.ReadBool(); + + goto case 9; + } + case 9: + { + Squelched = reader.ReadBool(); + + goto case 8; + } + case 8: + { + m_Holding = reader.ReadEntity(); + + goto case 7; + } + case 7: + { + m_VirtualArmor = reader.ReadInt(); + + goto case 6; + } + case 6: + { + BaseSoundID = reader.ReadInt(); + + goto case 5; + } + case 5: + { + DisarmReady = reader.ReadBool(); + StunReady = reader.ReadBool(); + + goto case 4; + } + case 4: + { + if (version <= 25) + { + reader.ReadPoison(); + } + + goto case 3; + } + case 3: + { + m_StatCap = reader.ReadInt(); + + goto case 2; + } + case 2: + { + NameHue = reader.ReadInt(); + + goto case 1; + } + case 1: + { + m_Hunger = reader.ReadInt(); + + goto case 0; + } + case 0: + { + if (version < 21) + { + Stabled = new List(); + } + + if (version < 18) + { + Virtues = new VirtueInfo(); + } + + if (version < 11) + { + m_DisplayGuildTitle = true; + } + + if (version < 3) + { + m_StatCap = 225; + } + + if (version < 15) + { + m_Followers = 0; + m_FollowersMax = 5; + } + + m_Location = reader.ReadPoint3D(); + m_Body = new Body(reader.ReadInt()); + m_Name = reader.ReadString(); + m_GuildTitle = reader.ReadString(); + m_Criminal = reader.ReadBool(); + m_Kills = reader.ReadInt(); + SpeechHue = reader.ReadInt(); + EmoteHue = reader.ReadInt(); + WhisperHue = reader.ReadInt(); + YellHue = reader.ReadInt(); + m_Language = reader.ReadString(); + m_Female = reader.ReadBool(); + m_Warmode = reader.ReadBool(); + m_Hidden = reader.ReadBool(); + m_Direction = (Direction)reader.ReadByte(); + m_Hue = reader.ReadInt(); + m_Str = reader.ReadInt(); + m_Dex = reader.ReadInt(); + m_Int = reader.ReadInt(); + m_Hits = reader.ReadInt(); + m_Stam = reader.ReadInt(); + m_Mana = reader.ReadInt(); + m_Map = reader.ReadMap(); + m_Blessed = reader.ReadBool(); + m_Fame = reader.ReadInt(); + m_Karma = reader.ReadInt(); + m_AccessLevel = (AccessLevel)reader.ReadByte(); + + Skills = new Skills(this, reader); + + Items = reader.ReadEntityList(); + + m_Player = reader.ReadBool(); + m_Title = reader.ReadString(); + Profile = reader.ReadString(); + ProfileLocked = reader.ReadBool(); + + if (version <= 18) + { + reader.ReadInt(); + reader.ReadInt(); + reader.ReadInt(); + } + + AutoPageNotify = reader.ReadBool(); + + LogoutLocation = reader.ReadPoint3D(); + LogoutMap = reader.ReadMap(); + + m_StrLock = (StatLockType)reader.ReadByte(); + m_DexLock = (StatLockType)reader.ReadByte(); + m_IntLock = (StatLockType)reader.ReadByte(); + + StatMods = new HashSet(); + SkillMods = new HashSet(); + + if (version < 32) + { + if (reader.ReadBool()) { - m_Hair = new HairInfo(reader); - } - - if ((hairflag & 0x02) != 0) - { - m_FacialHair = new FacialHairInfo(reader); - } - - goto case 29; - } - case 29: - { - m_Race = reader.ReadRace(); - goto case 28; - } - case 28: - { - if (version <= 30) - { - LastStatGain = reader.ReadDeltaTime(); - } - - goto case 27; - } - case 27: - { - m_TithingPoints = reader.ReadInt(); - - goto case 26; - } - case 26: - case 25: - case 24: - { - Corpse = reader.ReadEntity(); - - goto case 23; - } - case 23: - { - if (version < 33) - { - Created = reader.ReadDateTime(); - } - - goto case 22; - } - case 22: // Just removed followers - case 21: - { - Stabled = reader.ReadEntityList(); - - goto case 20; - } - case 20: - { - CantWalk = reader.ReadBool(); - - goto case 19; - } - case 19: // Just removed variables - case 18: - { - Virtues = new VirtueInfo(reader); - - goto case 17; - } - case 17: - { - Thirst = reader.ReadInt(); - BAC = reader.ReadInt(); - - goto case 16; - } - case 16: - { - m_ShortTermMurders = reader.ReadInt(); - - if (version <= 24) - { - reader.ReadDateTime(); - reader.ReadDateTime(); - } - - goto case 15; - } - case 15: - { - if (version < 22) - { - reader.ReadInt(); // followers - } - - m_FollowersMax = reader.ReadInt(); - - goto case 14; - } - case 14: - { - MagicDamageAbsorb = reader.ReadInt(); - - goto case 13; - } - case 13: - { - GuildFealty = reader.ReadEntity(); - - goto case 12; - } - case 12: - { - m_Guild = reader.ReadEntity(); - - goto case 11; - } - case 11: - { - m_DisplayGuildTitle = reader.ReadBool(); - - goto case 10; - } - case 10: - { - CanSwim = reader.ReadBool(); - - goto case 9; - } - case 9: - { - Squelched = reader.ReadBool(); - - goto case 8; - } - case 8: - { - m_Holding = reader.ReadEntity(); - - goto case 7; - } - case 7: - { - m_VirtualArmor = reader.ReadInt(); - - goto case 6; - } - case 6: - { - BaseSoundID = reader.ReadInt(); - - goto case 5; - } - case 5: - { - DisarmReady = reader.ReadBool(); - StunReady = reader.ReadBool(); - - goto case 4; - } - case 4: - { - if (version <= 25) - { - reader.ReadPoison(); - } - - goto case 3; - } - case 3: - { - m_StatCap = reader.ReadInt(); - - goto case 2; - } - case 2: - { - NameHue = reader.ReadInt(); - - goto case 1; - } - case 1: - { - m_Hunger = reader.ReadInt(); - - goto case 0; - } - case 0: - { - if (version < 21) - { - Stabled = new List(); - } - - if (version < 18) - { - Virtues = new VirtueInfo(); - } - - if (version < 11) - { - m_DisplayGuildTitle = true; - } - - if (version < 3) - { - m_StatCap = 225; - } - - if (version < 15) - { - m_Followers = 0; - m_FollowersMax = 5; - } - - m_Location = reader.ReadPoint3D(); - m_Body = new Body(reader.ReadInt()); - m_Name = reader.ReadString(); - m_GuildTitle = reader.ReadString(); - m_Criminal = reader.ReadBool(); - m_Kills = reader.ReadInt(); - SpeechHue = reader.ReadInt(); - EmoteHue = reader.ReadInt(); - WhisperHue = reader.ReadInt(); - YellHue = reader.ReadInt(); - m_Language = reader.ReadString(); - m_Female = reader.ReadBool(); - m_Warmode = reader.ReadBool(); - m_Hidden = reader.ReadBool(); - m_Direction = (Direction)reader.ReadByte(); - m_Hue = reader.ReadInt(); - m_Str = reader.ReadInt(); - m_Dex = reader.ReadInt(); - m_Int = reader.ReadInt(); - m_Hits = reader.ReadInt(); - m_Stam = reader.ReadInt(); - m_Mana = reader.ReadInt(); - m_Map = reader.ReadMap(); - m_Blessed = reader.ReadBool(); - m_Fame = reader.ReadInt(); - m_Karma = reader.ReadInt(); - m_AccessLevel = (AccessLevel)reader.ReadByte(); - - Skills = new Skills(this, reader); - - Items = reader.ReadEntityList(); - - m_Player = reader.ReadBool(); - m_Title = reader.ReadString(); - Profile = reader.ReadString(); - ProfileLocked = reader.ReadBool(); - - if (version <= 18) - { - reader.ReadInt(); - reader.ReadInt(); - reader.ReadInt(); - } - - AutoPageNotify = reader.ReadBool(); - - LogoutLocation = reader.ReadPoint3D(); - LogoutMap = reader.ReadMap(); - - m_StrLock = (StatLockType)reader.ReadByte(); - m_DexLock = (StatLockType)reader.ReadByte(); - m_IntLock = (StatLockType)reader.ReadByte(); - - StatMods = new HashSet(); - SkillMods = new HashSet(); - - if (version < 32) - { - if (reader.ReadBool()) + var count = reader.ReadInt(); + for (var i = 0; i < count; ++i) { - var count = reader.ReadInt(); - for (var i = 0; i < count; ++i) - { - reader.ReadDateTime(); - } + reader.ReadDateTime(); } } - - if (m_Player && m_Map != Map.Internal) - { - LogoutLocation = m_Location; - LogoutMap = m_Map; - - m_Map = Map.Internal; - } - - m_Map?.OnEnter(this); - - if (m_Criminal) - { - Timer.StartTimer(ExpireCriminalDelay, ExpireCriminal, out _expireCriminalTimerToken); - } - - if (ShouldCheckStatTimers) - { - CheckStatTimers(); - } - - UpdateRegion(); - - UpdateResistances(); - - break; } - } - if (!m_Player) - { - Utility.Intern(ref m_Name); - } + if (m_Player && m_Map != Map.Internal) + { + LogoutLocation = m_Location; + LogoutMap = m_Map; - Utility.Intern(ref m_Title); - Utility.Intern(ref m_Language); + m_Map = Map.Internal; + } + + m_Map?.OnEnter(this); + + if (m_Criminal) + { + Timer.StartTimer(ExpireCriminalDelay, ExpireCriminal, out _expireCriminalTimerToken); + } + + if (ShouldCheckStatTimers) + { + CheckStatTimers(); + } + + UpdateRegion(); + + UpdateResistances(); + + break; + } } - public void ConvertHair() + if (!m_Player) { - Item hair; - - if ((hair = FindItemOnLayer(Layer.Hair)) != null) - { - HairItemID = hair.ItemID; - HairHue = hair.Hue; - hair.Delete(); - } - - if ((hair = FindItemOnLayer(Layer.FacialHair)) != null) - { - FacialHairItemID = hair.ItemID; - FacialHairHue = hair.Hue; - hair.Delete(); - } + Utility.Intern(ref m_Name); } - public virtual void CheckStatTimers() - { - if (Deleted) - { - return; - } + Utility.Intern(ref m_Title); + Utility.Intern(ref m_Language); + } - if (Hits < HitsMax) + public void ConvertHair() + { + Item hair; + + if ((hair = FindItemOnLayer(Layer.Hair)) != null) + { + HairItemID = hair.ItemID; + HairHue = hair.Hue; + hair.Delete(); + } + + if ((hair = FindItemOnLayer(Layer.FacialHair)) != null) + { + FacialHairItemID = hair.ItemID; + FacialHairHue = hair.Hue; + hair.Delete(); + } + } + + public virtual void CheckStatTimers() + { + if (Deleted) + { + return; + } + + if (Hits < HitsMax) + { + if (CanRegenHits) { - if (CanRegenHits) - { - m_HitsTimer ??= new HitsTimer(this); - m_HitsTimer.Start(); - } - else - { - m_HitsTimer?.Stop(); - } + m_HitsTimer ??= new HitsTimer(this); + m_HitsTimer.Start(); } else { - Hits = HitsMax; + m_HitsTimer?.Stop(); } + } + else + { + Hits = HitsMax; + } - if (Stam < StamMax) + if (Stam < StamMax) + { + if (CanRegenStam) { - if (CanRegenStam) - { - m_StamTimer ??= new StamTimer(this); - m_StamTimer.Start(); - } - else - { - m_StamTimer?.Stop(); - } + m_StamTimer ??= new StamTimer(this); + m_StamTimer.Start(); } else { - Stam = StamMax; + m_StamTimer?.Stop(); } + } + else + { + Stam = StamMax; + } - if (Mana < ManaMax) + if (Mana < ManaMax) + { + if (CanRegenMana) { - if (CanRegenMana) - { - m_ManaTimer ??= new ManaTimer(this); - m_ManaTimer.Start(); - } - else - { - m_ManaTimer?.Stop(); - } + m_ManaTimer ??= new ManaTimer(this); + m_ManaTimer.Start(); } else { - Mana = ManaMax; + m_ManaTimer?.Stop(); } } - - public static string GetAccessLevelName(AccessLevel level) => m_AccessLevelNames[(int)level]; - - public virtual bool CanPaperdollBeOpenedBy(Mobile from) => Body.IsHuman || Body.IsGhost || IsBodyMod; - - public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) + else { + Mana = ManaMax; + } + } + + public static string GetAccessLevelName(AccessLevel level) => m_AccessLevelNames[(int)level]; + + public virtual bool CanPaperdollBeOpenedBy(Mobile from) => Body.IsHuman || Body.IsGhost || IsBodyMod; + + public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) + { + } + + public virtual void GetContextMenuEntries(Mobile from, List list) + { + if (Deleted) + { + return; } - public virtual void GetContextMenuEntries(Mobile from, List list) + if (CanPaperdollBeOpenedBy(from)) { - if (Deleted) - { - return; - } - - if (CanPaperdollBeOpenedBy(from)) - { - list.Add(new PaperdollEntry(this)); - } - - if (from == this && Backpack != null && CanSee(Backpack) && CheckAlive(false)) - { - list.Add(new OpenBackpackEntry(this)); - } + list.Add(new PaperdollEntry(this)); } - public void Internalize() + if (from == this && Backpack != null && CanSee(Backpack) && CheckAlive(false)) { - Map = Map.Internal; + list.Add(new OpenBackpackEntry(this)); + } + } + + public void Internalize() + { + Map = Map.Internal; + } + + /// + /// Overridable. Virtual event invoked when is added from the Mobile, + /// such + /// as when it is equipped. + /// + /// + /// + public virtual void OnItemAdded(Item item) + { + } + + /// + /// Overridable. Virtual event invoked when is removed from the + /// Mobile. + /// + /// + /// + public virtual void OnItemRemoved(Item item) + { + } + + /// + /// Overridable. Virtual event invoked when is becomes a child of the Mobile; it's worn or + /// contained + /// at some level of the Mobile's backpack or bank box + /// + /// + /// + public virtual void OnSubItemAdded(Item item) + { + } + + /// + /// Overridable. Virtual event invoked when is removed from the Mobile, its + /// backpack, or its bank box. + /// + /// + /// + public virtual void OnSubItemRemoved(Item item) + { + } + + public virtual void OnItemBounceCleared(Item item) + { + } + + public virtual void OnSubItemBounceCleared(Item item) + { + } + + public void AddItem(Item item) + { + if (item?.Deleted != false) + { + return; } - /// - /// Overridable. Virtual event invoked when is added from the Mobile, - /// such - /// as when it is equipped. - /// - /// - /// - public virtual void OnItemAdded(Item item) + if (item.Parent == this) { + return; } - /// - /// Overridable. Virtual event invoked when is removed from the - /// Mobile. - /// - /// - /// - public virtual void OnItemRemoved(Item item) + if (item.Parent is Mobile parentMobile) { + parentMobile.RemoveItem(item); + } + else if (item.Parent is Item parentItem) + { + parentItem.RemoveItem(item); + } + else + { + item.SendRemovePacket(); } - /// - /// Overridable. Virtual event invoked when is becomes a child of the Mobile; it's worn or - /// contained - /// at some level of the Mobile's backpack or bank box - /// - /// - /// - public virtual void OnSubItemAdded(Item item) + item.Parent = this; + item.Map = m_Map; + + Items.Add(item); + + if (!item.IsVirtualItem) { + UpdateTotal(item, TotalType.Gold, item.TotalGold); + UpdateTotal(item, TotalType.Items, item.TotalItems + 1); + UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); } - /// - /// Overridable. Virtual event invoked when is removed from the Mobile, its - /// backpack, or its bank box. - /// - /// - /// - public virtual void OnSubItemRemoved(Item item) + item.Delta(ItemDelta.Update); + + item.OnAdded(this); + OnItemAdded(item); + + if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || + item.PoisonResistance != 0 || item.EnergyResistance != 0) { + UpdateResistances(); + } + } + + public void RemoveItem(Item item) + { + if (item == null || Items == null) + { + return; } - public virtual void OnItemBounceCleared(Item item) + if (Items.Remove(item)) { - } - - public virtual void OnSubItemBounceCleared(Item item) - { - } - - public void AddItem(Item item) - { - if (item?.Deleted != false) - { - return; - } - - if (item.Parent == this) - { - return; - } - - if (item.Parent is Mobile parentMobile) - { - parentMobile.RemoveItem(item); - } - else if (item.Parent is Item parentItem) - { - parentItem.RemoveItem(item); - } - else - { - item.SendRemovePacket(); - } - - item.Parent = this; - item.Map = m_Map; - - Items.Add(item); + item.SendRemovePacket(); if (!item.IsVirtualItem) { - UpdateTotal(item, TotalType.Gold, item.TotalGold); - UpdateTotal(item, TotalType.Items, item.TotalItems + 1); - UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); + UpdateTotal(item, TotalType.Gold, -item.TotalGold); + UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); + UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); } - item.Delta(ItemDelta.Update); + item.Parent = null; - item.OnAdded(this); - OnItemAdded(item); + item.OnRemoved(this); + OnItemRemoved(item); if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || item.PoisonResistance != 0 || item.EnergyResistance != 0) @@ -6530,747 +6560,666 @@ namespace Server UpdateResistances(); } } + } - public void RemoveItem(Item item) + public virtual void Animate(int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay) + { + var map = m_Map; + + if (map == null) { - if (item == null || Items == null) - { - return; - } - - if (Items.Remove(item)) - { - item.SendRemovePacket(); - - if (!item.IsVirtualItem) - { - UpdateTotal(item, TotalType.Gold, -item.TotalGold); - UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); - UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); - } - - item.Parent = null; - - item.OnRemoved(this); - OnItemRemoved(item); - - if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || - item.PoisonResistance != 0 || item.EnergyResistance != 0) - { - UpdateResistances(); - } - } + return; } - public virtual void Animate(int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay) - { - var map = m_Map; + ProcessDelta(); - if (map == null) + var eable = map.GetClientsInRange(m_Location); + + Span buffer = stackalloc byte[OutgoingMobilePackets.MobileAnimationPacketLength].InitializePacket(); + + foreach (var state in eable) + { + if (!state.Mobile.CanSee(this)) { - return; + continue; } - ProcessDelta(); + state.Mobile.ProcessDelta(); - var eable = map.GetClientsInRange(m_Location); - - Span buffer = stackalloc byte[OutgoingMobilePackets.MobileAnimationPacketLength].InitializePacket(); - - foreach (var state in eable) + if (Body.IsGargoyle) { - if (!state.Mobile.CanSee(this)) + frameCount = 10; + + if (Flying) { - continue; - } - - state.Mobile.ProcessDelta(); - - if (Body.IsGargoyle) - { - frameCount = 10; - - if (Flying) + action = action switch { - action = action switch - { - >= 9 and <= 11 => 71, - >= 12 and <= 14 => 72, - 20 => 77, - 31 => 71, - 34 => 78, - >= 200 and <= 259 => 75, - >= 260 and <= 270 => 75, - _ => action - }; - } - else - { - action = action switch - { - >= 200 and <= 259 => 17, - >= 260 and <= 270 => 16, - _ => action - }; - } - } - - OutgoingMobilePackets.CreateMobileAnimation( - buffer, - Serial, - action, - frameCount, - repeatCount, - forward, - repeat, - delay - ); - - state.Send(buffer); - } - - eable.Free(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SendSound(int soundID) - { - m_NetState.SendSoundEffect(soundID, this); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SendSound(int soundID, IPoint3D p) - { - m_NetState.SendSoundEffect(soundID, p); - } - - /** - * Plays a sound to netstates that can see this mobile - */ - public void PlaySound(int soundID) - { - if (soundID == -1 || m_Map == null) - { - return; - } - - Span buffer = stackalloc byte[OutgoingEffectPackets.SoundPacketLength].InitializePacket(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - { - if (state.Mobile.CanSee(this)) - { - OutgoingEffectPackets.CreateSoundEffect(buffer, soundID, this); - state.Send(buffer); - } - } - - eable.Free(); - } - - public virtual void SendOPLPacketTo(NetState ns) - { - if (ObjectPropertyList.Enabled) - { - ns.SendOPLInfo(this); - } - } - - public virtual void OnAccessLevelChanged(AccessLevel oldLevel) - { - } - - public virtual void OnFameChange(int oldValue) - { - } - - public virtual void OnKarmaChange(int oldValue) - { - } - - // Mobile did something which should unhide him - public virtual void RevealingAction() - { - if (m_Hidden && m_AccessLevel == AccessLevel.Player) - { - Hidden = false; - } - - DisruptiveAction(); // Anything that unhides you will also distrupt meditation - } - - public void SendRemovePacket(bool everyone = true) - { - if (m_Map == null) - { - return; - } - - var eable = m_Map.GetClientsInRange(m_Location); - - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - - foreach (var state in eable) - { - if (state != m_NetState && (everyone || !state.Mobile.CanSee(this))) - { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - state.Send(removeEntity); - } - } - - eable.Free(); - } - - public void ClearScreen() - { - if (m_Map == null || m_NetState == null) - { - return; - } - - var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); - - foreach (var o in eable) - { - if (o is Mobile m) - { - if (m != this && Utility.InUpdateRange(m_Location, m.m_Location)) - { - m_NetState.SendRemoveEntity(m.Serial); - } - } - else if (o is Item item) - { - if (InRange(item.Location, item.GetUpdateRange(this))) - { - m_NetState.SendRemoveEntity(item.Serial); - } - } - } - - eable.Free(); - } - - /// - /// Overridable. Event invoked before the Mobile says something. - /// - /// - public virtual void OnSaid(SpeechEventArgs e) - { - if (Squelched) - { - if (Core.ML) - { - SendLocalizedMessage(500168); // You can not say anything, you have been muted. + >= 9 and <= 11 => 71, + >= 12 and <= 14 => 72, + 20 => 77, + 31 => 71, + 34 => 78, + >= 200 and <= 259 => 75, + >= 260 and <= 270 => 75, + _ => action + }; } else { - SendMessage("You can not say anything, you have been squelched."); // Cliloc ITSELF changed during ML. - } - - e.Blocked = true; - } - - if (!e.Blocked) - { - RevealingAction(); - } - } - - public virtual bool HandlesOnSpeech(Mobile from) => false; - - /// - /// Overridable. Virtual event invoked when the Mobile hears speech. This event will only be invoked if - /// returns true. - /// - /// - public virtual void OnSpeech(SpeechEventArgs e) - { - } - - public void SendEverything() - { - var ns = m_NetState; - - if (m_Map == null || ns == null) - { - return; - } - - var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); - - foreach (var o in eable) - { - if (o is Item item) - { - if (CanSee(item) && InRange(item.Location, item.GetUpdateRange(this))) + action = action switch { - item.SendInfoTo(ns); - } + >= 200 and <= 259 => 17, + >= 260 and <= 270 => 16, + _ => action + }; } - else if (o is Mobile m) + } + + OutgoingMobilePackets.CreateMobileAnimation( + buffer, + Serial, + action, + frameCount, + repeatCount, + forward, + repeat, + delay + ); + + state.Send(buffer); + } + + eable.Free(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SendSound(int soundID) + { + m_NetState.SendSoundEffect(soundID, this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SendSound(int soundID, IPoint3D p) + { + m_NetState.SendSoundEffect(soundID, p); + } + + /** + * Plays a sound to netstates that can see this mobile + */ + public void PlaySound(int soundID) + { + if (soundID == -1 || m_Map == null) + { + return; + } + + Span buffer = stackalloc byte[OutgoingEffectPackets.SoundPacketLength].InitializePacket(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + { + if (state.Mobile.CanSee(this)) + { + OutgoingEffectPackets.CreateSoundEffect(buffer, soundID, this); + state.Send(buffer); + } + } + + eable.Free(); + } + + public virtual void SendOPLPacketTo(NetState ns) + { + if (ObjectPropertyList.Enabled) + { + ns.SendOPLInfo(this); + } + } + + public virtual void OnAccessLevelChanged(AccessLevel oldLevel) + { + } + + public virtual void OnFameChange(int oldValue) + { + } + + public virtual void OnKarmaChange(int oldValue) + { + } + + // Mobile did something which should unhide him + public virtual void RevealingAction() + { + if (m_Hidden && m_AccessLevel == AccessLevel.Player) + { + Hidden = false; + } + + DisruptiveAction(); // Anything that unhides you will also distrupt meditation + } + + public void SendRemovePacket(bool everyone = true) + { + if (m_Map == null) + { + return; + } + + var eable = m_Map.GetClientsInRange(m_Location); + + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); + + foreach (var state in eable) + { + if (state != m_NetState && (everyone || !state.Mobile.CanSee(this))) + { + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + state.Send(removeEntity); + } + } + + eable.Free(); + } + + public void ClearScreen() + { + if (m_Map == null || m_NetState == null) + { + return; + } + + var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); + + foreach (var o in eable) + { + if (o is Mobile m) + { + if (m != this && Utility.InUpdateRange(m_Location, m.m_Location)) { - if (CanSee(m) && Utility.InUpdateRange(m_Location, m.m_Location)) - { - ns.SendMobileIncoming(this, m); - - if (ns.StygianAbyss) - { - ns.SendMobileHealthbar(m, Healthbar.Poison); - ns.SendMobileHealthbar(m, Healthbar.Yellow); - } - - if (m.IsDeadBondedPet) - { - ns.SendBondedStatus(m.Serial, true); - } - - m.SendOPLPacketTo(ns); - } + m_NetState.SendRemoveEntity(m.Serial); } } - - eable.Free(); - } - - public void UpdateRegion() - { - if (Deleted) + else if (o is Item item) { - return; - } - - var newRegion = Region.Find(m_Location, m_Map); - - if (newRegion != m_Region) - { - Region.OnRegionChange(this, m_Region, newRegion); - - m_Region = newRegion; - OnRegionChange(m_Region, newRegion); - } - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - protected virtual void OnMapChange(Map oldMap) - { - } - - public void SetDirection(Direction dir) - { - m_Direction = dir; - } - - public virtual int GetSeason() => m_Map?.Season ?? 1; - - public virtual int GetPacketFlags(bool stygianAbyss) - { - var flags = 0x0; - - if (m_Paralyzed || m_Frozen) - { - flags |= 0x01; - } - - if (m_Female) - { - flags |= 0x02; - } - - if (stygianAbyss) - { - if (m_Flying) + if (InRange(item.Location, item.GetUpdateRange(this))) { - flags |= 0x04; + m_NetState.SendRemoveEntity(item.Serial); } } + } + + eable.Free(); + } + + /// + /// Overridable. Event invoked before the Mobile says something. + /// + /// + public virtual void OnSaid(SpeechEventArgs e) + { + if (Squelched) + { + if (Core.ML) + { + SendLocalizedMessage(500168); // You can not say anything, you have been muted. + } else { - if (m_Poison != null) + SendMessage("You can not say anything, you have been squelched."); // Cliloc ITSELF changed during ML. + } + + e.Blocked = true; + } + + if (!e.Blocked) + { + RevealingAction(); + } + } + + public virtual bool HandlesOnSpeech(Mobile from) => false; + + /// + /// Overridable. Virtual event invoked when the Mobile hears speech. This event will only be invoked if + /// returns true. + /// + /// + public virtual void OnSpeech(SpeechEventArgs e) + { + } + + public void SendEverything() + { + var ns = m_NetState; + + if (m_Map == null || ns == null) + { + return; + } + + var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); + + foreach (var o in eable) + { + if (o is Item item) + { + if (CanSee(item) && InRange(item.Location, item.GetUpdateRange(this))) { - flags |= 0x04; + item.SendInfoTo(ns); } } - - if (m_Blessed || m_YellowHealthbar) + else if (o is Mobile m) { - flags |= 0x08; - } - - if (m_Warmode) - { - flags |= 0x40; - } - - if (m_Hidden) - { - flags |= 0x80; - } - - return flags; - } - - public virtual void OnGenderChanged(bool oldFemale) - { - } - - public virtual void ToggleFlying() - { - } - - /// - /// Overridable. Virtual event invoked after the Warmode property has changed. - /// - public virtual void OnWarmodeChanged() - { - } - - public virtual void OnHiddenChanged() - { - AllowedStealthSteps = 0; - - if (m_Map == null) - { - return; - } - - var eable = m_Map.GetClientsInRange(m_Location); - - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - - foreach (var state in eable) - { - var m = state.Mobile; - if (!m.CanSee(this)) + if (CanSee(m) && Utility.InUpdateRange(m_Location, m.m_Location)) { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - state.Send(removeEntity); - } - else - { - state.SendMobileIncoming(m, this); + ns.SendMobileIncoming(this, m); - if (IsDeadBondedPet) + if (ns.StygianAbyss) { - state.SendBondedStatus(Serial, true); + ns.SendMobileHealthbar(m, Healthbar.Poison); + ns.SendMobileHealthbar(m, Healthbar.Yellow); } - SendOPLPacketTo(state); - } - } - - eable.Free(); - } - - public virtual void OnConnected() - { - } - - public virtual void OnBeforeDisconnected() - { - } - - public virtual void OnDisconnected() - { - } - - public virtual void OnNetStateChanged() - { - } - - public virtual bool CanSee(object o) - { - return o switch - { - Item item => CanSee(item), - Mobile mobile => CanSee(mobile), - _ => true - }; - } - - public virtual bool CanSee(Item item) - { - if (m_Map == Map.Internal) - { - return false; - } - - if (item.Map == Map.Internal) - { - return false; - } - - if (item.Parent != null) - { - if (item.Parent is Item parent) - { - if (!(CanSee(parent) && parent.IsChildVisibleTo(this, item))) + if (m.IsDeadBondedPet) { - return false; + ns.SendBondedStatus(m.Serial, true); } + + m.SendOPLPacketTo(ns); } - else if (item.Parent is Mobile mobile) + } + } + + eable.Free(); + } + + public void UpdateRegion() + { + if (Deleted) + { + return; + } + + var newRegion = Region.Find(m_Location, m_Map); + + if (newRegion != m_Region) + { + Region.OnRegionChange(this, m_Region, newRegion); + + m_Region = newRegion; + OnRegionChange(m_Region, newRegion); + } + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + protected virtual void OnMapChange(Map oldMap) + { + } + + public void SetDirection(Direction dir) + { + m_Direction = dir; + } + + public virtual int GetSeason() => m_Map?.Season ?? 1; + + public virtual int GetPacketFlags(bool stygianAbyss) + { + var flags = 0x0; + + if (m_Paralyzed || m_Frozen) + { + flags |= 0x01; + } + + if (m_Female) + { + flags |= 0x02; + } + + if (stygianAbyss) + { + if (m_Flying) + { + flags |= 0x04; + } + } + else + { + if (m_Poison != null) + { + flags |= 0x04; + } + } + + if (m_Blessed || m_YellowHealthbar) + { + flags |= 0x08; + } + + if (m_Warmode) + { + flags |= 0x40; + } + + if (m_Hidden) + { + flags |= 0x80; + } + + return flags; + } + + public virtual void OnGenderChanged(bool oldFemale) + { + } + + public virtual void ToggleFlying() + { + } + + /// + /// Overridable. Virtual event invoked after the Warmode property has changed. + /// + public virtual void OnWarmodeChanged() + { + } + + public virtual void OnHiddenChanged() + { + AllowedStealthSteps = 0; + + if (m_Map == null) + { + return; + } + + var eable = m_Map.GetClientsInRange(m_Location); + + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); + + foreach (var state in eable) + { + var m = state.Mobile; + if (!m.CanSee(this)) + { + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + state.Send(removeEntity); + } + else + { + state.SendMobileIncoming(m, this); + + if (IsDeadBondedPet) { - if (!CanSee(mobile)) - { - return false; - } + state.SendBondedStatus(Serial, true); } + + SendOPLPacketTo(state); } + } - if (item is BankBox box && m_AccessLevel <= AccessLevel.Counselor && (box.Owner != this || !box.Opened)) + eable.Free(); + } + + public virtual void OnConnected() + { + } + + public virtual void OnBeforeDisconnected() + { + } + + public virtual void OnDisconnected() + { + } + + public virtual void OnNetStateChanged() + { + } + + public virtual bool CanSee(object o) + { + return o switch + { + Item item => CanSee(item), + Mobile mobile => CanSee(mobile), + _ => true + }; + } + + public virtual bool CanSee(Item item) + { + if (m_Map == Map.Internal) + { + return false; + } + + if (item.Map == Map.Internal) + { + return false; + } + + if (item.Parent != null) + { + if (item.Parent is Item parent) { - return false; - } - - if (item is SecureTradeContainer container) - { - var trade = container.Trade; - - if (trade != null && trade.From.Mobile != this && trade.To.Mobile != this) + if (!(CanSee(parent) && parent.IsChildVisibleTo(this, item))) + { + return false; + } + } + else if (item.Parent is Mobile mobile) + { + if (!CanSee(mobile)) { return false; } } - - return !item.Deleted && item.Map == m_Map && (item.Visible || item.CanSeeStaffOnly(this)); } - public virtual bool CanSee(Mobile m) + if (item is BankBox box && m_AccessLevel <= AccessLevel.Counselor && (box.Owner != this || !box.Opened)) { - if (Deleted || m.Deleted || m_Map == Map.Internal || m.m_Map == Map.Internal) + return false; + } + + if (item is SecureTradeContainer container) + { + var trade = container.Trade; + + if (trade != null && trade.From.Mobile != this && trade.To.Mobile != this) { return false; } - - return this == m || m.m_Map == m_Map && - (!m.Hidden || m_AccessLevel != AccessLevel.Player && - (m_AccessLevel >= m.AccessLevel || m_AccessLevel >= AccessLevel.Administrator)) && - (m.Alive || Core.SE && Skills.SpiritSpeak.Value >= 100.0 || !Alive || - m_AccessLevel > AccessLevel.Player || m.Warmode); } - public virtual bool CanBeRenamedBy(Mobile from) => - from.AccessLevel >= AccessLevel.GameMaster && from.m_AccessLevel > m_AccessLevel; + return !item.Deleted && item.Map == m_Map && (item.Visible || item.CanSeeStaffOnly(this)); + } - public virtual void OnGuildTitleChange(string oldTitle) + public virtual bool CanSee(Mobile m) + { + if (Deleted || m.Deleted || m_Map == Map.Internal || m.m_Map == Map.Internal) { + return false; } - public virtual void OnAfterNameChange(string oldName, string newName) + return this == m || m.m_Map == m_Map && + (!m.Hidden || m_AccessLevel != AccessLevel.Player && + (m_AccessLevel >= m.AccessLevel || m_AccessLevel >= AccessLevel.Administrator)) && + (m.Alive || Core.SE && Skills.SpiritSpeak.Value >= 100.0 || !Alive || + m_AccessLevel > AccessLevel.Player || m.Warmode); + } + + public virtual bool CanBeRenamedBy(Mobile from) => + from.AccessLevel >= AccessLevel.GameMaster && from.m_AccessLevel > m_AccessLevel; + + public virtual void OnGuildTitleChange(string oldTitle) + { + } + + public virtual void OnAfterNameChange(string oldName, string newName) + { + } + + public virtual void OnGuildChange(BaseGuild oldGuild) + { + } + + public virtual int SafeBody(int body) + { + var delta = -1; + + for (var i = 0; delta < 0 && i < m_InvalidBodies.Length; ++i) { + delta = m_InvalidBodies[i] - body; } - public virtual void OnGuildChange(BaseGuild oldGuild) - { - } + return delta != 0 ? body : 0; + } - public virtual int SafeBody(int body) - { - var delta = -1; + private ObjectPropertyList InitializePropertyList(ObjectPropertyList list) + { + GetProperties(list); + list.Terminate(); + return list; + } - for (var i = 0; delta < 0 && i < m_InvalidBodies.Length; ++i) - { - delta = m_InvalidBodies[i] - body; - } - - return delta != 0 ? body : 0; - } - - private ObjectPropertyList InitializePropertyList(ObjectPropertyList list) - { - GetProperties(list); - list.Terminate(); - return list; - } - - public void ClearProperties() - { - m_PropertyList = null; - } + public void ClearProperties() + { + m_PropertyList = null; + } #nullable enable - public void InvalidateProperties() + public void InvalidateProperties() + { + if (!ObjectPropertyList.Enabled) { - if (!ObjectPropertyList.Enabled) - { - return; - } + return; + } - if (m_Map != null && m_Map != Map.Internal && !World.Loading) + if (m_Map != null && m_Map != Map.Internal && !World.Loading) + { + int? oldHash; + int newHash; + if (m_PropertyList != null) { - int? oldHash; - int newHash; - if (m_PropertyList != null) - { - oldHash = m_PropertyList.Hash; - m_PropertyList.Reset(); - InitializePropertyList(m_PropertyList); - newHash = m_PropertyList.Hash; - } - else - { - oldHash = null; - newHash = PropertyList.Hash; - } - - if (oldHash != newHash) - { - Delta(MobileDelta.Properties); - } + oldHash = m_PropertyList.Hash; + m_PropertyList.Reset(); + InitializePropertyList(m_PropertyList); + newHash = m_PropertyList.Hash; } else { - ClearProperties(); + oldHash = null; + newHash = PropertyList.Hash; + } + + if (oldHash != newHash) + { + Delta(MobileDelta.Properties); } } + else + { + ClearProperties(); + } + } #nullable restore - public virtual void SetLocation(Point3D newLocation, bool isTeleport) + public virtual void SetLocation(Point3D newLocation, bool isTeleport) + { + if (Deleted) { - if (Deleted) + return; + } + + var oldLocation = m_Location; + + if (oldLocation == newLocation) + { + return; + } + + m_Location = newLocation; + UpdateRegion(); + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + { + box.Close(); + } + + m_NetState?.ValidateAllTrades(); + + m_Map?.OnMove(oldLocation, this); + + if (isTeleport && m_NetState != null && (!m_NetState.HighSeas || !NoMoveHS)) + { + m_NetState.Sequence = 0; + m_NetState.SendMobileUpdate(this); + } + + var map = m_Map; + + if (map != null) + { + // First, send a remove message to everyone who can no longer see us. (inOldRange && !inNewRange) + + var eable = map.GetClientsInRange(oldLocation); + + Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); + + foreach (var ns in eable) { - return; - } - - var oldLocation = m_Location; - - if (oldLocation == newLocation) - { - return; - } - - m_Location = newLocation; - UpdateRegion(); - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - { - box.Close(); - } - - m_NetState?.ValidateAllTrades(); - - m_Map?.OnMove(oldLocation, this); - - if (isTeleport && m_NetState != null && (!m_NetState.HighSeas || !NoMoveHS)) - { - m_NetState.Sequence = 0; - m_NetState.SendMobileUpdate(this); - } - - var map = m_Map; - - if (map != null) - { - // First, send a remove message to everyone who can no longer see us. (inOldRange && !inNewRange) - - var eable = map.GetClientsInRange(oldLocation); - - Span removeEntity = stackalloc byte[OutgoingEntityPackets.RemoveEntityLength].InitializePacket(); - - foreach (var ns in eable) + if (ns != m_NetState && !Utility.InUpdateRange(newLocation, ns.Mobile.Location)) { - if (ns != m_NetState && !Utility.InUpdateRange(newLocation, ns.Mobile.Location)) - { - OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); - ns.Send(removeEntity); - } + OutgoingEntityPackets.CreateRemoveEntity(removeEntity, Serial); + ns.Send(removeEntity); } + } - eable.Free(); + eable.Free(); - var ourState = m_NetState; + var ourState = m_NetState; - // Check to see if we are attached to a client - if (ourState != null) + // Check to see if we are attached to a client + if (ourState != null) + { + var eeable = map.GetObjectsInRange(newLocation, Core.GlobalMaxUpdateRange); + + // We are attached to a client, so it's a bit more complex. We need to send new items and people to ourself, and ourself to other clients + + foreach (var o in eeable) { - var eeable = map.GetObjectsInRange(newLocation, Core.GlobalMaxUpdateRange); - - // We are attached to a client, so it's a bit more complex. We need to send new items and people to ourself, and ourself to other clients - - foreach (var o in eeable) + if (o is Item item) { - if (o is Item item) + var range = item.GetUpdateRange(this); + var loc = item.Location; + + if (!Utility.InRange(oldLocation, loc, range) && Utility.InRange(newLocation, loc, range) && + CanSee(item)) { - var range = item.GetUpdateRange(this); - var loc = item.Location; - - if (!Utility.InRange(oldLocation, loc, range) && Utility.InRange(newLocation, loc, range) && - CanSee(item)) - { - item.SendInfoTo(ourState); - } - } - else if (o != this && o is Mobile m) - { - if (!Utility.InUpdateRange(newLocation, m.m_Location)) - { - continue; - } - - var inOldRange = Utility.InUpdateRange(oldLocation, m.m_Location); - var ns = m.m_NetState; - - if (ns != null && - (isTeleport && (!ns.HighSeas || !NoMoveHS) || !inOldRange) && m.CanSee(this)) - { - ns.SendMobileIncoming(m, this); - - if (ns.StygianAbyss) - { - ns.SendMobileHealthbar(this, Healthbar.Poison); - ns.SendMobileHealthbar(this, Healthbar.Yellow); - } - - if (IsDeadBondedPet) - { - ns.SendBondedStatus(Serial, true); - } - - SendOPLPacketTo(ns); - } - - if (inOldRange || !CanSee(m)) - { - continue; - } - - ourState.SendMobileIncoming(this, m); - - if (ourState.StygianAbyss) - { - ourState.SendMobileHealthbar(m, Healthbar.Poison); - ourState.SendMobileHealthbar(m, Healthbar.Yellow); - } - - if (m.IsDeadBondedPet) - { - ourState.SendBondedStatus(m.Serial, true); - } - - m.SendOPLPacketTo(ourState); + item.SendInfoTo(ourState); } } - - eeable.Free(); - } - else - { - eable = map.GetClientsInRange(newLocation); - - // We're not attached to a client, so simply send an Incoming - foreach (var ns in eable) + else if (o != this && o is Mobile m) { - var m = ns.Mobile; - if ((isTeleport && (!ns.HighSeas || !NoMoveHS) || !Utility.InUpdateRange(oldLocation, m.Location)) && - m.CanSee(this)) + if (!Utility.InUpdateRange(newLocation, m.m_Location)) + { + continue; + } + + var inOldRange = Utility.InUpdateRange(oldLocation, m.m_Location); + var ns = m.m_NetState; + + if (ns != null && + (isTeleport && (!ns.HighSeas || !NoMoveHS) || !inOldRange) && m.CanSee(this)) { ns.SendMobileIncoming(m, this); @@ -7287,354 +7236,405 @@ namespace Server SendOPLPacketTo(ns); } + + if (inOldRange || !CanSee(m)) + { + continue; + } + + ourState.SendMobileIncoming(this, m); + + if (ourState.StygianAbyss) + { + ourState.SendMobileHealthbar(m, Healthbar.Poison); + ourState.SendMobileHealthbar(m, Healthbar.Yellow); + } + + if (m.IsDeadBondedPet) + { + ourState.SendBondedStatus(m.Serial, true); + } + + m.SendOPLPacketTo(ourState); } - - eable.Free(); } + + eeable.Free(); } - - OnLocationChange(oldLocation); - - Region.OnLocationChanged(this, oldLocation); - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - protected virtual void OnLocationChange(Point3D oldLocation) - { - } - - public bool HasFreeHand() => FindItemOnLayer(Layer.TwoHanded) == null; - - public virtual IWeapon GetDefaultWeapon() => DefaultWeapon; - - public BankBox FindBankNoCreate() - { - if (m_BankBox?.Deleted != false || m_BankBox.Parent != this) + else { - m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; - } + eable = map.GetClientsInRange(newLocation); - return m_BankBox; - } - - public Item FindItemOnLayer(Layer layer) - { - var eq = Items; - var count = eq.Count; - - for (var i = 0; i < count; ++i) - { - var item = eq[i]; - - if (!item.Deleted && item.Layer == layer) + // We're not attached to a client, so simply send an Incoming + foreach (var ns in eable) { - return item; - } - } - - return null; - } - - public void SendIncomingPacket() - { - if (m_Map == null) - { - return; - } - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - { - var m = state.Mobile; - if (m.CanSee(this)) - { - state.SendMobileIncoming(m, this); - - if (state.StygianAbyss) + var m = ns.Mobile; + if ((isTeleport && (!ns.HighSeas || !NoMoveHS) || !Utility.InUpdateRange(oldLocation, m.Location)) && + m.CanSee(this)) { - state.SendMobileHealthbar(this, Healthbar.Poison); - state.SendMobileHealthbar(this, Healthbar.Yellow); + ns.SendMobileIncoming(m, this); + + if (ns.StygianAbyss) + { + ns.SendMobileHealthbar(this, Healthbar.Poison); + ns.SendMobileHealthbar(this, Healthbar.Yellow); + } + + if (IsDeadBondedPet) + { + ns.SendBondedStatus(Serial, true); + } + + SendOPLPacketTo(ns); } - - if (IsDeadBondedPet) - { - state.SendBondedStatus(Serial, true); - } - - SendOPLPacketTo(state); } - } - eable.Free(); + eable.Free(); + } } - public bool PlaceInBackpack(Item item) - { - if (item.Deleted) - { - return false; - } + OnLocationChange(oldLocation); - return Backpack?.TryDropItem(this, item, false) == true; + Region.OnLocationChanged(this, oldLocation); + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + protected virtual void OnLocationChange(Point3D oldLocation) + { + } + + public bool HasFreeHand() => FindItemOnLayer(Layer.TwoHanded) == null; + + public virtual IWeapon GetDefaultWeapon() => DefaultWeapon; + + public BankBox FindBankNoCreate() + { + if (m_BankBox?.Deleted != false || m_BankBox.Parent != this) + { + m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; } - public bool AddToBackpack(Item item) + return m_BankBox; + } + + public Item FindItemOnLayer(Layer layer) + { + var eq = Items; + var count = eq.Count; + + for (var i = 0; i < count; ++i) { - if (item.Deleted) + var item = eq[i]; + + if (!item.Deleted && item.Layer == layer) { - return false; + return item; } + } - if (!PlaceInBackpack(item)) + return null; + } + + public void SendIncomingPacket() + { + if (m_Map == null) + { + return; + } + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + { + var m = state.Mobile; + if (m.CanSee(this)) { - var loc = m_Location; - var map = m_Map; + state.SendMobileIncoming(m, this); - if ((map == null || map == Map.Internal) && LogoutMap != null) + if (state.StygianAbyss) { - loc = LogoutLocation; - map = LogoutMap; + state.SendMobileHealthbar(this, Healthbar.Poison); + state.SendMobileHealthbar(this, Healthbar.Yellow); } - item.MoveToWorld(loc, map); - return false; - } - - return true; - } - - public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => true; - - public virtual bool CheckNonlocalLift(Mobile from, Item item) => - from == this || from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster; - - public virtual bool CheckTrade( - Mobile to, Item item, SecureTradeContainer cont, bool message, bool checkItems, - int plusItems, int plusWeight - ) => - true; - - public virtual bool OpenTrade(Mobile from, Item offer = null) - { - if (!from.Player || !Player || !from.Alive || !Alive) - { - return false; - } - - var ourState = m_NetState; - var theirState = from.m_NetState; - - if (ourState == null || theirState == null) - { - return false; - } - - var cont = theirState.FindTradeContainer(this); - - if (!from.CheckTrade(this, offer, cont, true, true, 0, 0)) - { - return false; - } - - cont ??= theirState.AddTrade(ourState); - - if (offer != null) - { - cont.DropItem(offer); - } - - return true; - } - - /// - /// Overridable. Event invoked when a Mobile () drops an - /// - /// - /// - /// onto the Mobile. - /// - public virtual bool OnDragDrop(Mobile from, Item dropped) - { - if (from == this) - { - var pack = Backpack; - return pack != null && dropped.DropToItem(from, pack, new Point3D(-1, -1, 0)); - } - - return from.InRange(Location, 2) && OpenTrade(from, dropped); - } - - public virtual bool CheckEquip(Item item) - { - for (var i = 0; i < Items.Count; ++i) - { - if (Items[i].CheckConflictingLayer(this, item, item.Layer) || - item.CheckConflictingLayer(this, Items[i], Items[i].Layer)) + if (IsDeadBondedPet) { - return false; - } - } - - return true; - } - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to wear . - /// - /// True if the request is accepted, false if otherwise. - public virtual bool OnEquip(Item item) - { - // For some reason OSI allows equipping quest items, but they are unmarked in the process - if (item.QuestItem) - { - item.QuestItem = false; - - // An item must be in your backpack (and not in a container within) to be toggled as a quest item. - SendLocalizedMessage(1074769); - } - - return true; - } - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to lift . - /// - /// True if the lift is allowed, false if otherwise. - /// - /// The following example demonstrates usage. It will disallow any attempts to pick up a pick axe if the Mobile does not - /// have - /// enough strength. - /// - /// public override bool OnDragLift( Item item ) - /// { - /// if (item is Pickaxe && this.Str < 60) - /// { - /// SendMessage( "That is too heavy for you to lift." ); - /// return false; - /// } - /// - /// return base.OnDragLift( item ); - /// } - /// - public virtual bool OnDragLift(Item item) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop into a - /// - /// - /// - /// . - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemInto(Item item, Container container, Point3D loc) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop directly onto another - /// , . This is the case of stacking items. - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemOnto(Item item, Item target) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop into another - /// , . The target item is most likely a . - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemToItem(Item item, Item target, Point3D loc) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to give to a Mobile ( - /// ). - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemToMobile(Item item, Mobile target) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop to the world at a - /// - /// - /// - /// . - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemToWorld(Item item, Point3D location) => true; - - /// - /// Overridable. Virtual event when successfully uses while it's on this - /// Mobile. - /// - /// - public virtual void OnItemUsed(Mobile from, Item item) - { - } - - public virtual bool CheckNonlocalDrop(Mobile from, Item item, Item target) => - from == this || from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster; - - public virtual bool CheckItemUse(Mobile from, Item item) => true; - - /// - /// Overridable. Virtual event invoked when successfully lifts from this - /// Mobile. - /// - /// - public virtual void OnItemLifted(Mobile from, Item item) - { - } - - public virtual bool AllowItemUse(Item item) => true; - - public virtual bool AllowEquipFrom(Mobile mob) => - mob == this || mob.AccessLevel >= AccessLevel.GameMaster && mob.AccessLevel > AccessLevel; - - public virtual bool EquipItem(Item item) - { - if (item?.Deleted != false || !item.CanEquip(this)) - { - return false; - } - - if (CheckEquip(item) && OnEquip(item) && item.OnEquip(this)) - { - if (m_Spell?.OnCasterEquipping(item) == false) - { - return false; + state.SendBondedStatus(Serial, true); } - // if (m_Spell != null && m_Spell.State == SpellState.Casting) - // m_Spell.Disturb( DisturbType.EquipRequest ); - - AddItem(item); - return true; + SendOPLPacketTo(state); } + } + eable.Free(); + } + + public bool PlaceInBackpack(Item item) + { + if (item.Deleted) + { return false; } - public void DefaultMobileInit() - { - m_StatCap = 225; - m_FollowersMax = 5; - Skills = new Skills(this); - Items = new List(); - StatMods = new HashSet(); - SkillMods = new HashSet(); - Map = Map.Internal; - AutoPageNotify = true; - Aggressors = new List(); - Aggressed = new List(); - Virtues = new VirtueInfo(); - Stabled = new List(); - DamageEntries = new List(); + return Backpack?.TryDropItem(this, item, false) == true; + } - NextSkillTime = Core.TickCount; + public bool AddToBackpack(Item item) + { + if (item.Deleted) + { + return false; } - public virtual void Delta(MobileDelta flag) + if (!PlaceInBackpack(item)) { + var loc = m_Location; + var map = m_Map; + + if ((map == null || map == Map.Internal) && LogoutMap != null) + { + loc = LogoutLocation; + map = LogoutMap; + } + + item.MoveToWorld(loc, map); + return false; + } + + return true; + } + + public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => true; + + public virtual bool CheckNonlocalLift(Mobile from, Item item) => + from == this || from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster; + + public virtual bool CheckTrade( + Mobile to, Item item, SecureTradeContainer cont, bool message, bool checkItems, + int plusItems, int plusWeight + ) => + true; + + public virtual bool OpenTrade(Mobile from, Item offer = null) + { + if (!from.Player || !Player || !from.Alive || !Alive) + { + return false; + } + + var ourState = m_NetState; + var theirState = from.m_NetState; + + if (ourState == null || theirState == null) + { + return false; + } + + var cont = theirState.FindTradeContainer(this); + + if (!from.CheckTrade(this, offer, cont, true, true, 0, 0)) + { + return false; + } + + cont ??= theirState.AddTrade(ourState); + + if (offer != null) + { + cont.DropItem(offer); + } + + return true; + } + + /// + /// Overridable. Event invoked when a Mobile () drops an + /// + /// + /// + /// onto the Mobile. + /// + public virtual bool OnDragDrop(Mobile from, Item dropped) + { + if (from == this) + { + var pack = Backpack; + return pack != null && dropped.DropToItem(from, pack, new Point3D(-1, -1, 0)); + } + + return from.InRange(Location, 2) && OpenTrade(from, dropped); + } + + public virtual bool CheckEquip(Item item) + { + for (var i = 0; i < Items.Count; ++i) + { + if (Items[i].CheckConflictingLayer(this, item, item.Layer) || + item.CheckConflictingLayer(this, Items[i], Items[i].Layer)) + { + return false; + } + } + + return true; + } + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to wear . + /// + /// True if the request is accepted, false if otherwise. + public virtual bool OnEquip(Item item) + { + // For some reason OSI allows equipping quest items, but they are unmarked in the process + if (item.QuestItem) + { + item.QuestItem = false; + + // An item must be in your backpack (and not in a container within) to be toggled as a quest item. + SendLocalizedMessage(1074769); + } + + return true; + } + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to lift . + /// + /// True if the lift is allowed, false if otherwise. + /// + /// The following example demonstrates usage. It will disallow any attempts to pick up a pick axe if the Mobile does not + /// have + /// enough strength. + /// + /// public override bool OnDragLift( Item item ) + /// { + /// if (item is Pickaxe && this.Str < 60) + /// { + /// SendMessage( "That is too heavy for you to lift." ); + /// return false; + /// } + /// + /// return base.OnDragLift( item ); + /// } + /// + public virtual bool OnDragLift(Item item) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop into a + /// + /// + /// + /// . + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemInto(Item item, Container container, Point3D loc) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop directly onto another + /// , . This is the case of stacking items. + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemOnto(Item item, Item target) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop into another + /// , . The target item is most likely a . + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemToItem(Item item, Item target, Point3D loc) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to give to a Mobile ( + /// ). + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemToMobile(Item item, Mobile target) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop to the world at a + /// + /// + /// + /// . + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemToWorld(Item item, Point3D location) => true; + + /// + /// Overridable. Virtual event when successfully uses while it's on this + /// Mobile. + /// + /// + public virtual void OnItemUsed(Mobile from, Item item) + { + } + + public virtual bool CheckNonlocalDrop(Mobile from, Item item, Item target) => + from == this || from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster; + + public virtual bool CheckItemUse(Mobile from, Item item) => true; + + /// + /// Overridable. Virtual event invoked when successfully lifts from this + /// Mobile. + /// + /// + public virtual void OnItemLifted(Mobile from, Item item) + { + } + + public virtual bool AllowItemUse(Item item) => true; + + public virtual bool AllowEquipFrom(Mobile mob) => + mob == this || mob.AccessLevel >= AccessLevel.GameMaster && mob.AccessLevel > AccessLevel; + + public virtual bool EquipItem(Item item) + { + if (item?.Deleted != false || !item.CanEquip(this)) + { + return false; + } + + if (CheckEquip(item) && OnEquip(item) && item.OnEquip(this)) + { + if (m_Spell?.OnCasterEquipping(item) == false) + { + return false; + } + + // if (m_Spell != null && m_Spell.State == SpellState.Casting) + // m_Spell.Disturb( DisturbType.EquipRequest ); + + AddItem(item); + return true; + } + + return false; + } + + public void DefaultMobileInit() + { + m_StatCap = 225; + m_FollowersMax = 5; + Skills = new Skills(this); + Items = new List(); + StatMods = new HashSet(); + SkillMods = new HashSet(); + Map = Map.Internal; + AutoPageNotify = true; + Aggressors = new List(); + Aggressed = new List(); + Virtues = new VirtueInfo(); + Stabled = new List(); + DamageEntries = new List(); + + NextSkillTime = Core.TickCount; + } + + public virtual void Delta(MobileDelta flag) + { #if THREADGUARD if (Thread.CurrentThread != Core.Thread) { @@ -7646,1492 +7646,1491 @@ namespace Server } #endif - if (m_Map == null || m_Map == Map.Internal || Deleted) - { - return; - } - - m_DeltaFlags |= flag; - - if (!m_InDeltaQueue) - { - m_InDeltaQueue = true; - m_DeltaQueue.Enqueue(this); - } - } - - public static void ProcessDeltaQueue() - { - var limit = m_DeltaQueue.Count; - - while (m_DeltaQueue.Count > 0 && --limit >= 0) - { - var mob = m_DeltaQueue.Dequeue(); - - if (mob == null) - { - continue; - } - - mob.m_InDeltaQueue = false; - - try - { - mob.ProcessDelta(); - } - catch (Exception ex) - { - logger.Debug(ex, "Process Delta Queue for {Mobile} failed", mob); - } - } - - if (m_DeltaQueue.Count > 0) - { - logger.Warning("{Count} mobiles left in delta queue after processing.", m_DeltaQueue.Count); - } - } - - public virtual void OnKillsChange(int oldValue) - { - } - - public bool CheckAlive(bool message = true) - { - if (Alive) - { - return true; - } - - if (message) - { - LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. - } - - return false; - } - - public void LaunchBrowser(string url) - { - m_NetState?.LaunchBrowser(url); - } - - public void InitStats(int str, int dex, int intel) - { - m_Str = str; - m_Dex = dex; - m_Int = intel; - - Hits = HitsMax; - Stam = StamMax; - Mana = ManaMax; - - Delta(MobileDelta.Stat | MobileDelta.Hits | MobileDelta.Stam | MobileDelta.Mana); - } - - public virtual void DisplayPaperdollTo(Mobile to) - { - EventSink.InvokePaperdollRequest(to, this); - } - - /// - /// Overridable. Event invoked when the Mobile requests to open his own paperdoll via the 'Open Paperdoll' macro. - /// - public virtual void OnPaperdollRequest() - { - if (CanPaperdollBeOpenedBy(this)) - { - DisplayPaperdollTo(this); - } - } - - /// - /// Overridable. Event invoked when wants to see this Mobile's stats. - /// - /// - public virtual void OnStatsQuery(Mobile from) - { - if (from.Map == Map && Utility.InUpdateRange(Location, from.Location) && from.CanSee(this)) - { - from.m_NetState.SendMobileStatus(from, this); - } - - if (from == this) - { - m_NetState.SendStatLockInfo(this); - } - - if (Party is IParty ip) - { - ip.OnStatsQuery(from, this); - } - } - - /// - /// Overridable. Event invoked when wants to see this Mobile's skills. - /// - public virtual void OnSkillsQuery(Mobile from) - { - if (from == this) - { - m_NetState.SendSkillsUpdate(Skills); - } - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - public virtual void OnRegionChange(Region old, Region @new) - { - } - - /// - /// Overridable. Event invoked when the Mobile is single clicked. - /// - public virtual void OnSingleClick(Mobile from) - { - if (Deleted || AccessLevel == AccessLevel.Player && DisableHiddenSelfClick && Hidden && from == this) - { - return; - } - - if (GuildClickMessage) - { - var guild = m_Guild; - - if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) - { - var title = GuildTitle?.Trim() ?? ""; - string type; - - if (guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length) - { - type = m_GuildTypes[(int)guild.Type]; - } - else - { - type = ""; - } - - var text = string.Format( - title.Length <= 0 ? "[{1}]{2}" : "[{0}, {1}]{2}", - title, - guild.Abbreviation, - type - ); - - PrivateOverheadMessage(MessageType.Regular, SpeechHue, true, text, from.NetState); - } - } - - int hue; - - if (NameHue != -1) - { - hue = NameHue; - } - else if (AccessLevel > AccessLevel.Player) - { - hue = 11; - } - else - { - hue = Notoriety.GetHue(Notoriety.Compute(from, this)); - } - - var name = Name ?? ""; - - var prefix = ""; - - if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) - { - prefix = m_Female ? "Lady" : "Lord"; - } - - var suffix = ApplyNameSuffix(ClickTitle && !string.IsNullOrEmpty(Title) ? Title : ""); - - string val; - - if (prefix.Length > 0 && suffix.Length > 0) - { - val = $"{prefix} {name} {suffix}"; - } - else if (prefix.Length > 0) - { - val = $"{prefix} {name}"; - } - else if (suffix.Length > 0) - { - val = $"{name} {suffix}"; - } - else - { - val = name; - } - - PrivateOverheadMessage(MessageType.Label, hue, AsciiClickMessage, val, from.NetState); - } - - public bool CheckSkill(SkillName skill, double minSkill, double maxSkill) => - SkillCheckLocationHandler?.Invoke(this, skill, minSkill, maxSkill) == true; - - public bool CheckSkill(SkillName skill, double chance) => - SkillCheckDirectLocationHandler?.Invoke(this, skill, chance) == true; - - public bool CheckTargetSkill(SkillName skill, object target, double minSkill, double maxSkill) => - SkillCheckTargetHandler?.Invoke(this, skill, target, minSkill, maxSkill) == true; - - public bool CheckTargetSkill(SkillName skill, object target, double chance) => - SkillCheckDirectTargetHandler?.Invoke(this, skill, target, chance) == true; - - public virtual void DisruptiveAction() - { - if (Meditating) - { - Meditating = false; - SendLocalizedMessage(500134); // You stop meditating. - } - } - - /// - /// Overridable. Virtual event invoked when the sector this Mobile is in gets activated. - /// - public virtual void OnSectorActivate() - { - } - - /// - /// Overridable. Virtual event invoked when the sector this Mobile is in gets - /// deactivated. - /// - public virtual void OnSectorDeactivate() - { - } - - public static TimeSpan GetHitsRegenRate(Mobile m) => HitsRegenRateHandler?.Invoke(m) ?? DefaultHitsRate; - - public static TimeSpan GetStamRegenRate(Mobile m) => StamRegenRateHandler?.Invoke(m) ?? DefaultStamRate; - - public static TimeSpan GetManaRegenRate(Mobile m) => ManaRegenRateHandler?.Invoke(m) ?? DefaultManaRate; - - public static char[] DefaultGhostChars = { 'o', 'O' }; - - public Prompt BeginPrompt(PromptCallback callback, PromptCallback cancelCallback) => - Prompt = new SimplePrompt(callback, cancelCallback); - - public Prompt BeginPrompt(PromptCallback callback, bool callbackHandlesCancel = false) => - Prompt = new SimplePrompt(callback, callbackHandlesCancel); - - public Prompt BeginPrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) => - Prompt = new SimpleStatePrompt(callback, cancelCallback, state); - - public Prompt BeginPrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) => - Prompt = new SimpleStatePrompt(callback, callbackHandlesCancel, state); - - public Prompt BeginPrompt(PromptStateCallback callback, T state) => - BeginPrompt(callback, false, state); - - public virtual int GetAngerSound() - { - if (BaseSoundID != 0) - { - return BaseSoundID; - } - - return -1; - } - - public virtual int GetIdleSound() - { - if (BaseSoundID != 0) - { - return BaseSoundID + 1; - } - - return -1; - } - - public virtual int GetAttackSound() - { - if (BaseSoundID != 0) - { - return BaseSoundID + 2; - } - - return -1; - } - - public virtual int GetHurtSound() - { - if (BaseSoundID != 0) - { - return BaseSoundID + 3; - } - - return -1; - } - - public virtual int GetDeathSound() - { - if (BaseSoundID != 0) - { - return BaseSoundID + 4; - } - - if (m_Body.IsHuman) - { - return Utility.Random(m_Female ? 0x314 : 0x423, m_Female ? 4 : 5); - } - - return -1; - } - - public IPooledEnumerable GetItemsInRange(int range) => GetItemsInRange(range); - - public IPooledEnumerable GetItemsInRange(int range) where T : Item => - m_Map?.GetItemsInRange(m_Location, range) ?? Map.NullEnumerable.Instance; - - public IPooledEnumerable GetObjectsInRange(int range) => - m_Map?.GetObjectsInRange(m_Location, range) ?? Map.NullEnumerable.Instance; - - public IPooledEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); - - public IPooledEnumerable GetMobilesInRange(int range) where T : Mobile => - m_Map?.GetMobilesInRange(m_Location, range) ?? Map.NullEnumerable.Instance; - - public IPooledEnumerable GetClientsInRange(int range) => - m_Map?.GetClientsInRange(m_Location, range) ?? Map.NullEnumerable.Instance; - - public void SayTo(Mobile to, bool ascii, string text) => - PrivateOverheadMessage(MessageType.Regular, SpeechHue, ascii, text, to.NetState); - - public void SayTo(Mobile to, string text) => SayTo(to, false, text); - - public void SayTo(Mobile to, string format, params object[] args) => SayTo(to, false, string.Format(format, args)); - - public void SayTo(Mobile to, bool ascii, string format, params object[] args) => - SayTo(to, ascii, string.Format(format, args)); - - public void SayTo(Mobile to, int number, string args = "") => - to.NetState.SendMessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, args); - - public void Say(bool ascii, string text) => PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text); - - public void Say(string text) => PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text); - - public void Say(string format, params object[] args) => Say(string.Format(format, args)); - - public void Say(int number, AffixType type, string affix, string args) => - PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args); - - public void Say(int number, string args = "") => PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args); - - public void Emote(string text) => PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text); - - public void Emote(string format, params object[] args) => Emote(string.Format(format, args)); - - public void Emote(int number, string args = "") => PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args); - - public void Whisper(string text) => PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text); - - public void Whisper(string format, params object[] args) => Whisper(string.Format(format, args)); - - public void Whisper(int number, string args = "") => - PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args); - - public void Yell(string text) => PublicOverheadMessage(MessageType.Yell, YellHue, false, text); - - public void Yell(string format, params object[] args) => Yell(string.Format(format, args)); - - public void Yell(int number, string args = "") => - PublicOverheadMessage(MessageType.Yell, YellHue, number, args); - - public bool SendHuePicker(HuePicker p) - { - if (m_NetState != null) - { - p.SendTo(m_NetState); - return true; - } - - return false; - } - - public Gump FindGump() where T : Gump => m_NetState?.Gumps.Find(g => g is T); - - public bool CloseGump() where T : Gump - { - if (m_NetState == null) - { - return false; - } - - var gump = FindGump(); - - if (gump != null) - { - m_NetState.SendCloseGump(gump.TypeID, 0); - m_NetState.RemoveGump(gump); - gump.OnServerClose(m_NetState); - } - - return true; - } - - public bool CloseAllGumps() - { - var ns = m_NetState; - - if (ns.CannotSendPackets()) - { - return false; - } - - var gumps = new List(ns.Gumps); - - ns.ClearGumps(); - - foreach (var gump in gumps) - { - ns.SendCloseGump(gump.TypeID, 0); - - gump.OnServerClose(ns); - } - - return true; - } - - public bool HasGump() where T : Gump => FindGump() != null; - - public bool SendGump(Gump g) - { - if (m_NetState == null) - { - return false; - } - - g.SendTo(m_NetState); - return true; - } - - public bool SendMenu(IMenu m) - { - if (m_NetState == null) - { - return false; - } - - m.SendTo(m_NetState); - return true; - } - - public virtual bool CanBeBeneficial(Mobile target) => CanBeBeneficial(target, true, false); - - public virtual bool CanBeBeneficial(Mobile target, bool message) => CanBeBeneficial(target, message, false); - - public virtual bool CanBeBeneficial(Mobile target, bool message, bool allowDead) - { - if (target == null) - { - return false; - } - - if (Deleted || target.Deleted || !Alive || IsDeadBondedPet || - !allowDead && (!target.Alive || target.IsDeadBondedPet)) - { - if (message) - { - SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. - } - - return false; - } - - if (target == this) - { - return true; - } - - if ( /*m_Player &&*/!Region.AllowBeneficial(this, target)) - { - // TODO: Pets - // if (!(target.m_Player || target.Body.IsHuman || target.Body.IsAnimal)) - // { - if (message) - { - SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. - } - - return false; - // } - } - - return true; - } - - public virtual bool IsBeneficialCriminal(Mobile target) - { - if (this == target) - { - return false; - } - - var n = Notoriety.Compute(this, target); - - return n is Notoriety.Criminal or Notoriety.Murderer; - } - - /// - /// Overridable. Event invoked when the Mobile does a beneficial action. - /// - public virtual void OnBeneficialAction(Mobile target, bool isCriminal) - { - if (isCriminal) - { - CriminalAction(false); - } - } - - public virtual void DoBeneficial(Mobile target) - { - if (target == null) - { - return; - } - - OnBeneficialAction(target, IsBeneficialCriminal(target)); - - Region.OnBeneficialAction(this, target); - target.Region.OnGotBeneficialAction(this, target); - } - - public virtual bool BeneficialCheck(Mobile target) - { - if (CanBeBeneficial(target, true)) - { - DoBeneficial(target); - return true; - } - - return false; - } - - public virtual bool CanBeHarmful(Mobile target) => CanBeHarmful(target, true); - - public virtual bool CanBeHarmful(Mobile target, bool message) => CanBeHarmful(target, message, false); - - public virtual bool CanBeHarmful(Mobile target, bool message, bool ignoreOurBlessedness) - { - if (target == null) - { - return false; - } - - if (Deleted || !ignoreOurBlessedness && m_Blessed || target.Deleted || target.m_Blessed || !Alive || - IsDeadBondedPet || !target.Alive || target.IsDeadBondedPet) - { - if (message) - { - SendLocalizedMessage(1001018); // You can not perform negative acts on your target. - } - - return false; - } - - if (target == this) - { - return true; - } - - // TODO: Pets - if (!Region.AllowHarmful(this, target)) - { - if (message) - { - SendLocalizedMessage(1001018); // You can not perform negative acts on your target. - } - - return false; - } - - return true; - } - - public virtual bool IsHarmfulCriminal(Mobile target) => - this != target && Notoriety.Compute(this, target) == Notoriety.Innocent; - - /// - /// Overridable. Event invoked when the Mobile does a harmful action. - /// - public virtual void OnHarmfulAction(Mobile target, bool isCriminal) + if (m_Map == null || m_Map == Map.Internal || Deleted) { - if (isCriminal) - { - CriminalAction(false); - } + return; } - public virtual void DoHarmful(Mobile target, bool indirect = false) - { - if (target == null || Deleted) - { - return; - } - - var isCriminal = IsHarmfulCriminal(target); - - OnHarmfulAction(target, isCriminal); - target.AggressiveAction(this, isCriminal); - - Region.OnDidHarmful(this, target); - target.Region.OnGotHarmful(this, target); - - if (!indirect) - { - Combatant = target; - } - - _expireCombatantTimerToken.Cancel(); - Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken); - } - - public virtual bool HarmfulCheck(Mobile target) - { - if (CanBeHarmful(target)) - { - DoHarmful(target); - return true; - } - - return false; - } - - public bool RemoveStatMod(string name) - { - StatMods ??= new HashSet(); - - StatMod mod = GetStatMod(name); - - if (mod != null) - { - StatMods.Remove(mod); - CheckStatTimers(); - Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); - return true; - } - - return false; - } - - public StatMod GetStatMod(string name) - { - StatMods ??= new HashSet(); - - foreach (var check in StatMods) - { - if (check.Name == name) - { - return check; - } - } - - return null; - } - - public void AddStatMod(StatMod mod) - { - RemoveStatMod(mod.Name); - - StatMods.Add(mod); - Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); - CheckStatTimers(); - } - - private static MobileDelta GetStatDelta(StatType type) - { - MobileDelta delta = 0; - - if ((type & StatType.Str) != 0) - { - delta |= MobileDelta.Hits; - } - - if ((type & StatType.Dex) != 0) - { - delta |= MobileDelta.Stam; - } - - if ((type & StatType.Int) != 0) - { - delta |= MobileDelta.Mana; - } - - return delta; - } - - /// - /// Computes the total modified offset for the specified stat type. Expired instances are removed. - /// - public int GetStatOffset(StatType type) - { - var offset = 0; - - StatMods ??= new HashSet(); - - if (StatMods.Count > 0) - { - using var queue = PooledRefQueue.Create(8); - foreach (var mod in StatMods) - { - if (mod.HasElapsed()) - { - queue.Enqueue(mod); - } - else if ((mod.Type & type) != 0) - { - offset += mod.Offset; - } - } - - while (queue.Count > 0) - { - var mod = queue.Dequeue(); - StatMods.Remove(mod); - Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); - CheckStatTimers(); - } - } - - return offset; - } - - /// - /// Overridable. Virtual event invoked when the changes. - /// - /// - /// - public virtual void OnRawStrChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - /// - /// - public virtual void OnRawDexChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when the changes. - /// - /// - /// - public virtual void OnRawIntChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when the , , or - /// changes. - /// - /// - /// - /// - public virtual void OnRawStatChange(StatType stat, int oldValue) - { - } - - public virtual void OnHitsChange(int oldValue) - { - } - - public virtual void OnStamChange(int oldValue) - { - } - - public virtual void OnManaChange(int oldValue) - { - } - - /// - /// Overridable. Event invoked when a call to failed because - /// returned false: the Mobile was resistant to the poison. By default, this broadcasts an overhead message: * The poison - /// seems to have no effect. * - /// - /// - /// - /// - public virtual void OnPoisonImmunity(Mobile from, Poison poison) - { - PublicOverheadMessage(MessageType.Emote, 0x3B2, 1005534); // * The poison seems to have no effect. * - } - - /// - /// Overridable. Virtual event invoked when a call to failed because - /// returned false: the Mobile was already poisoned by an equal or greater strength poison. - /// - /// - /// - /// - public virtual void OnHigherPoison(Mobile from, Poison poison) - { - } - - /// - /// Overridable. Event invoked when a call to succeeded. By default, this broadcasts an overhead - /// message varying by the level of the poison. Example: * Zippy begins to spasm uncontrollably. * - /// - /// - /// - public virtual void OnPoisoned(Mobile from, Poison poison, Poison oldPoison) - { - if (poison != null) - { - LocalOverheadMessage(MessageType.Regular, 0x21, 1042857 + poison.Level * 2); - NonlocalOverheadMessage(MessageType.Regular, 0x21, 1042858 + poison.Level * 2, Name); - } - } - - /// - /// Overridable. Called from , this method checks if the Mobile is immune to some - /// . If true, will be invoked and - /// is returned. - /// - /// - /// - /// - public virtual bool CheckPoisonImmunity(Mobile from, Poison poison) => false; - - /// - /// Overridable. Called from , this method checks if the Mobile is already poisoned by some - /// of equal or greater strength. If true, will be invoked and - /// is returned. - /// - /// - /// - /// - public virtual bool CheckHigherPoison(Mobile from, Poison poison) => - m_Poison != null && m_Poison.Level >= poison.Level; - - /// - /// Overridable. Attempts to apply poison to the Mobile. Checks are made such that no - /// higher poison is active and that the Mobile is not - /// immune to the poison. Provided those assertions are true, the - /// is applied and is invoked. - /// - /// - /// - /// - /// One of four possible values: - /// - /// - /// - /// Cured - /// - /// The parameter was null and so was invoked. - /// - /// - /// - /// HigherPoisonActive - /// - /// The call to returned false. - /// - /// - /// - /// Immune - /// - /// The call to returned false. - /// - /// - /// - /// Poisoned - /// - /// The was successfully applied. - /// - /// - /// - public virtual ApplyPoisonResult ApplyPoison(Mobile from, Poison poison) - { - if (poison == null) - { - CurePoison(from); - return ApplyPoisonResult.Cured; - } - - if (CheckHigherPoison(from, poison)) - { - OnHigherPoison(from, poison); - return ApplyPoisonResult.HigherPoisonActive; - } - - if (CheckPoisonImmunity(from, poison)) - { - OnPoisonImmunity(from, poison); - return ApplyPoisonResult.Immune; - } - - var oldPoison = m_Poison; - Poison = poison; - - OnPoisoned(from, poison, oldPoison); - - return ApplyPoisonResult.Poisoned; - } - - /// - /// Overridable. Called from , this method checks to see that the Mobile can be cured of - /// - /// - /// - /// - public virtual bool CheckCure(Mobile from) => true; - - /// - /// Overridable. Virtual event invoked when a call to succeeded. - /// - /// - /// - /// - public virtual void OnCured(Mobile from, Poison oldPoison) - { - } - - /// - /// Overridable. Virtual event invoked when a call to failed. - /// - /// - /// - /// - public virtual void OnFailedCure(Mobile from) - { - } - - /// - /// Overridable. Attempts to cure any poison that is currently active. - /// - /// True if poison was cured, false if otherwise. - public virtual bool CurePoison(Mobile from) - { - if (CheckCure(from)) - { - var oldPoison = m_Poison; - Poison = null; - - OnCured(from, oldPoison); - - return true; - } - - OnFailedCure(from); - - return false; - } - - public void MovingEffect( - IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int hue, int renderMode - ) => - Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode); - - public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes) => - Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes); - - public void MovingParticles( - IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer, int unknown - ) => - Effects.SendMovingParticles( - this, - to, - itemID, - speed, - duration, - fixedDirection, - explodes, - hue, - renderMode, - effect, - explodeEffect, - explodeSound, - layer, - unknown - ); - - public void MovingParticles( - IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int hue, int renderMode, int effect, int explodeEffect, int explodeSound, int unknown - ) => - Effects.SendMovingParticles( - this, - to, - itemID, - speed, - duration, - fixedDirection, - explodes, - hue, - renderMode, - effect, - explodeEffect, - explodeSound, - (EffectLayer)255, - unknown - ); - - public void MovingParticles( - IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int effect, int explodeEffect, int explodeSound, int unknown - ) => - Effects.SendMovingParticles( - this, - to, - itemID, - speed, - duration, - fixedDirection, - explodes, - effect, - explodeEffect, - explodeSound, - unknown - ); - - public void MovingParticles( - IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int effect, int explodeEffect, int explodeSound - ) => - Effects.SendMovingParticles( - this, - to, - itemID, - speed, - duration, - fixedDirection, - explodes, - 0, - 0, - effect, - explodeEffect, - explodeSound, - 0 - ); - - public void FixedEffect(int itemID, int speed, int duration, int hue, int renderMode) => - Effects.SendTargetEffect(this, itemID, speed, duration, hue, renderMode); - - public void FixedEffect(int itemID, int speed, int duration) => - Effects.SendTargetEffect(this, itemID, speed, duration); - - public void FixedParticles( - int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer, int unknown - ) => - Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, unknown); - - public void FixedParticles( - int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer - ) => - Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer); - - public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer, int unknown) => - Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, unknown); - - public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer) => - Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer); - - public void BoltEffect(int hue) => Effects.SendBoltEffect(this, true, hue); - - public Direction GetDirectionTo(int x, int y, bool run = false) - { - var dx = m_Location.m_X - x; - var dy = m_Location.m_Y - y; - - var rx = (dx - dy) * 44; - var ry = (dx + dy) * 44; - - var ax = rx.Abs(); - var ay = ry.Abs(); - - Direction ret; - - if ((ay >> 1) - ax >= 0) - { - ret = ry > 0 ? Direction.Up : Direction.Down; - } - else if ((ax >> 1) - ay >= 0) - { - ret = rx > 0 ? Direction.Left : Direction.Right; - } - else - { - ret = rx switch - { - >= 0 when ry >= 0 => Direction.West, - >= 0 => Direction.South, - < 0 when ry < 0 => Direction.East, - _ => Direction.North - }; - } - - return ret | (run ? Direction.Running : 0); - } - - public Direction GetDirectionTo(Point2D p, bool run = false) => GetDirectionTo(p.m_X, p.m_Y, run); - public Direction GetDirectionTo(Point3D p, bool run = false) => GetDirectionTo(p.m_X, p.m_Y, run); - - public Direction GetDirectionTo(IPoint2D p, bool run = false) => - p == null ? Direction.North | (run ? Direction.Running : 0) : GetDirectionTo(p.X, p.Y, run); - - public void PublicOverheadMessage( - MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true, - AccessLevel accessLevel = AccessLevel.Player - ) - { - if (m_Map == null) - { - return; - } - - Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - { - if ( - state.Mobile.AccessLevel >= accessLevel && - state.Mobile.CanSee(this) && - (noLineOfSight || state.Mobile.InLOS(this)) - ) - { - var length = OutgoingMessagePackets.CreateMessage( - buffer, Serial, Body, type, hue, 3, ascii, Language, Name, text - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - state.Send(buffer); - } - } - - eable.Free(); - } - - public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "", bool noLineOfSight = true) - { - if (m_Map == null) - { - return; - } - - Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - { - if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) - { - var length = OutgoingMessagePackets.CreateMessageLocalized( - buffer, Serial, Body, type, hue, 3, number, Name, args - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - state.Send(buffer); - } - } - - eable.Free(); - } - - public void PublicOverheadMessage( - MessageType type, int hue, int number, AffixType affixType, string affix, - string args = "", bool noLineOfSight = false, - AccessLevel accessLevel = AccessLevel.Player - ) - { - if (m_Map == null) - { - return; - } - - Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedAffixLength(affix, args)].InitializePacket(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - { - if ( - state.Mobile.AccessLevel >= accessLevel && - state.Mobile.CanSee(this) && - (noLineOfSight || state.Mobile.InLOS(this)) - ) - { - var length = OutgoingMessagePackets.CreateMessageLocalizedAffix( - buffer, Serial, Body, type, hue, 3, number, Name, affixType, affix, args - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - state.Send(buffer); - } - } - - eable.Free(); - } - - public void PrivateOverheadMessage(MessageType type, int hue, bool ascii, string text, NetState state) - { - state.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text); - } - - public void PrivateOverheadMessage(MessageType type, int hue, int number, NetState state) => - PrivateOverheadMessage(type, hue, number, "", state); - - public void PrivateOverheadMessage(MessageType type, int hue, int number, string args, NetState state) => - state.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args); - - public void LocalOverheadMessage(MessageType type, int hue, bool ascii, string text) => - m_NetState.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text); - - public void LocalOverheadMessage(MessageType type, int hue, int number, string args = "") => - m_NetState.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args); - - public void NonlocalOverheadMessage(MessageType type, int hue, int number, string args = "") - { - if (m_Map == null) - { - return; - } - - Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - { - if (state != m_NetState && state.Mobile.CanSee(this)) - { - var length = OutgoingMessagePackets.CreateMessageLocalized( - buffer, Serial, Body, type, hue, 3, number, Name, args - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - state.Send(buffer); - } - } - - eable.Free(); - } - - public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, string text) - { - if (m_Map == null) - { - return; - } - - Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - { - if (state != m_NetState && state.Mobile.CanSee(this)) - { - var length = OutgoingMessagePackets.CreateMessage( - buffer, Serial, Body, type, hue, 3, ascii, Language, Name, text - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - state.Send(buffer); - } - } - - eable.Free(); - } - - public void SendLocalizedMessage(int number, string args = "", int hue = 0x3B2) => - m_NetState.SendMessageLocalized(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", args); - - public void SendLocalizedMessage(int number, bool append, string affix, string args = "", int hue = 0x3B2) => - m_NetState.SendMessageLocalizedAffix( - Serial.MinusOne, - -1, - MessageType.Regular, - hue, - 3, - number, - "System", - (append ? AffixType.Append : AffixType.Prepend) | AffixType.System, - affix, - args - ); - - public void SendMessage(string text) => SendMessage(0x3B2, text); - - public void SendMessage(string format, params object[] args) => - SendMessage(0x3B2, string.Format(format, args)); - - public void SendMessage(int hue, string text) => - m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, false, "ENU", "System", text); - - public void SendMessage(int hue, string format, params object[] args) => - SendMessage(hue, string.Format(format, args)); - - public void SendAsciiMessage(string text) => SendAsciiMessage(0x3B2, text); - - public void SendAsciiMessage(string format, params object[] args) => - SendAsciiMessage(0x3B2, string.Format(format, args)); - - public void SendAsciiMessage(int hue, string text) => - m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, true, null, "System", text); - - public void SendAsciiMessage(int hue, string format, params object[] args) => - SendAsciiMessage(hue, string.Format(format, args)); - - /// - /// Overridable. Event invoked when the Mobile is double clicked. By default, this method can either dismount or open the - /// paperdoll. - /// - /// - /// - public virtual void OnDoubleClick(Mobile from) - { - if (this == from && (!DisableDismountInWarmode || !m_Warmode)) - { - var mount = Mount; - - if (mount != null) - { - mount.Rider = null; - return; - } - } - - if (CanPaperdollBeOpenedBy(from)) - { - DisplayPaperdollTo(from); - } - } - - /// - /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who is over 18 tiles away. - /// - /// - public virtual void OnDoubleClickOutOfRange(Mobile from) - { - } - - /// - /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who can no longer see the Mobile. This - /// may - /// happen, for example, using 'Last Object' after the Mobile has hidden. - /// - /// - public virtual void OnDoubleClickCantSee(Mobile from) - { - } - - /// - /// Overridable. Event invoked when the Mobile is double clicked by someone who is not alive. Similar to - /// , this method will show the paperdoll. It does not, however, provide any dismount - /// functionality. - /// - /// - public virtual void OnDoubleClickDead(Mobile from) - { - if (CanPaperdollBeOpenedBy(from)) - { - DisplayPaperdollTo(from); - } - } - - private class ManaTimer : Timer - { - private readonly Mobile m_Owner; - - public ManaTimer(Mobile m) : base(GetManaRegenRate(m), GetManaRegenRate(m)) => m_Owner = m; - - protected override void OnTick() - { - if (m_Owner.CanRegenMana) - { - m_Owner.Mana++; - } - - Delay = Interval = GetManaRegenRate(m_Owner); - } - } - - private class HitsTimer : Timer - { - private readonly Mobile m_Owner; - - public HitsTimer(Mobile m) : base(GetHitsRegenRate(m), GetHitsRegenRate(m)) => m_Owner = m; - - protected override void OnTick() - { - if (m_Owner.CanRegenHits) - { - m_Owner.Hits++; - } - - Delay = Interval = GetHitsRegenRate(m_Owner); - } - } - - private class StamTimer : Timer - { - private readonly Mobile m_Owner; - - public StamTimer(Mobile m) : base(GetStamRegenRate(m), GetStamRegenRate(m)) => m_Owner = m; - - protected override void OnTick() - { - if (m_Owner.CanRegenStam) - { - m_Owner.Stam++; - } - - Delay = Interval = GetStamRegenRate(m_Owner); - } - } - - private void Logout() - { - if (m_Map != Map.Internal) - { - EventSink.InvokeLogout(this); - - LogoutLocation = m_Location; - LogoutMap = m_Map; - - Internalize(); - } - } + m_DeltaFlags |= flag; - private void ExpireCriminal() + if (!m_InDeltaQueue) { - Criminal = false; + m_InDeltaQueue = true; + m_DeltaQueue.Enqueue(this); } } + + public static void ProcessDeltaQueue() + { + var limit = m_DeltaQueue.Count; + + while (m_DeltaQueue.Count > 0 && --limit >= 0) + { + var mob = m_DeltaQueue.Dequeue(); + + if (mob == null) + { + continue; + } + + mob.m_InDeltaQueue = false; + + try + { + mob.ProcessDelta(); + } + catch (Exception ex) + { + logger.Debug(ex, "Process Delta Queue for {Mobile} failed", mob); + } + } + + if (m_DeltaQueue.Count > 0) + { + logger.Warning("{Count} mobiles left in delta queue after processing.", m_DeltaQueue.Count); + } + } + + public virtual void OnKillsChange(int oldValue) + { + } + + public bool CheckAlive(bool message = true) + { + if (Alive) + { + return true; + } + + if (message) + { + LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. + } + + return false; + } + + public void LaunchBrowser(string url) + { + m_NetState?.LaunchBrowser(url); + } + + public void InitStats(int str, int dex, int intel) + { + m_Str = str; + m_Dex = dex; + m_Int = intel; + + Hits = HitsMax; + Stam = StamMax; + Mana = ManaMax; + + Delta(MobileDelta.Stat | MobileDelta.Hits | MobileDelta.Stam | MobileDelta.Mana); + } + + public virtual void DisplayPaperdollTo(Mobile to) + { + EventSink.InvokePaperdollRequest(to, this); + } + + /// + /// Overridable. Event invoked when the Mobile requests to open his own paperdoll via the 'Open Paperdoll' macro. + /// + public virtual void OnPaperdollRequest() + { + if (CanPaperdollBeOpenedBy(this)) + { + DisplayPaperdollTo(this); + } + } + + /// + /// Overridable. Event invoked when wants to see this Mobile's stats. + /// + /// + public virtual void OnStatsQuery(Mobile from) + { + if (from.Map == Map && Utility.InUpdateRange(Location, from.Location) && from.CanSee(this)) + { + from.m_NetState.SendMobileStatus(from, this); + } + + if (from == this) + { + m_NetState.SendStatLockInfo(this); + } + + if (Party is IParty ip) + { + ip.OnStatsQuery(from, this); + } + } + + /// + /// Overridable. Event invoked when wants to see this Mobile's skills. + /// + public virtual void OnSkillsQuery(Mobile from) + { + if (from == this) + { + m_NetState.SendSkillsUpdate(Skills); + } + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + public virtual void OnRegionChange(Region old, Region @new) + { + } + + /// + /// Overridable. Event invoked when the Mobile is single clicked. + /// + public virtual void OnSingleClick(Mobile from) + { + if (Deleted || AccessLevel == AccessLevel.Player && DisableHiddenSelfClick && Hidden && from == this) + { + return; + } + + if (GuildClickMessage) + { + var guild = m_Guild; + + if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) + { + var title = GuildTitle?.Trim() ?? ""; + string type; + + if (guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length) + { + type = m_GuildTypes[(int)guild.Type]; + } + else + { + type = ""; + } + + var text = string.Format( + title.Length <= 0 ? "[{1}]{2}" : "[{0}, {1}]{2}", + title, + guild.Abbreviation, + type + ); + + PrivateOverheadMessage(MessageType.Regular, SpeechHue, true, text, from.NetState); + } + } + + int hue; + + if (NameHue != -1) + { + hue = NameHue; + } + else if (AccessLevel > AccessLevel.Player) + { + hue = 11; + } + else + { + hue = Notoriety.GetHue(Notoriety.Compute(from, this)); + } + + var name = Name ?? ""; + + var prefix = ""; + + if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) + { + prefix = m_Female ? "Lady" : "Lord"; + } + + var suffix = ApplyNameSuffix(ClickTitle && !string.IsNullOrEmpty(Title) ? Title : ""); + + string val; + + if (prefix.Length > 0 && suffix.Length > 0) + { + val = $"{prefix} {name} {suffix}"; + } + else if (prefix.Length > 0) + { + val = $"{prefix} {name}"; + } + else if (suffix.Length > 0) + { + val = $"{name} {suffix}"; + } + else + { + val = name; + } + + PrivateOverheadMessage(MessageType.Label, hue, AsciiClickMessage, val, from.NetState); + } + + public bool CheckSkill(SkillName skill, double minSkill, double maxSkill) => + SkillCheckLocationHandler?.Invoke(this, skill, minSkill, maxSkill) == true; + + public bool CheckSkill(SkillName skill, double chance) => + SkillCheckDirectLocationHandler?.Invoke(this, skill, chance) == true; + + public bool CheckTargetSkill(SkillName skill, object target, double minSkill, double maxSkill) => + SkillCheckTargetHandler?.Invoke(this, skill, target, minSkill, maxSkill) == true; + + public bool CheckTargetSkill(SkillName skill, object target, double chance) => + SkillCheckDirectTargetHandler?.Invoke(this, skill, target, chance) == true; + + public virtual void DisruptiveAction() + { + if (Meditating) + { + Meditating = false; + SendLocalizedMessage(500134); // You stop meditating. + } + } + + /// + /// Overridable. Virtual event invoked when the sector this Mobile is in gets activated. + /// + public virtual void OnSectorActivate() + { + } + + /// + /// Overridable. Virtual event invoked when the sector this Mobile is in gets + /// deactivated. + /// + public virtual void OnSectorDeactivate() + { + } + + public static TimeSpan GetHitsRegenRate(Mobile m) => HitsRegenRateHandler?.Invoke(m) ?? DefaultHitsRate; + + public static TimeSpan GetStamRegenRate(Mobile m) => StamRegenRateHandler?.Invoke(m) ?? DefaultStamRate; + + public static TimeSpan GetManaRegenRate(Mobile m) => ManaRegenRateHandler?.Invoke(m) ?? DefaultManaRate; + + public static char[] DefaultGhostChars = { 'o', 'O' }; + + public Prompt BeginPrompt(PromptCallback callback, PromptCallback cancelCallback) => + Prompt = new SimplePrompt(callback, cancelCallback); + + public Prompt BeginPrompt(PromptCallback callback, bool callbackHandlesCancel = false) => + Prompt = new SimplePrompt(callback, callbackHandlesCancel); + + public Prompt BeginPrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) => + Prompt = new SimpleStatePrompt(callback, cancelCallback, state); + + public Prompt BeginPrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) => + Prompt = new SimpleStatePrompt(callback, callbackHandlesCancel, state); + + public Prompt BeginPrompt(PromptStateCallback callback, T state) => + BeginPrompt(callback, false, state); + + public virtual int GetAngerSound() + { + if (BaseSoundID != 0) + { + return BaseSoundID; + } + + return -1; + } + + public virtual int GetIdleSound() + { + if (BaseSoundID != 0) + { + return BaseSoundID + 1; + } + + return -1; + } + + public virtual int GetAttackSound() + { + if (BaseSoundID != 0) + { + return BaseSoundID + 2; + } + + return -1; + } + + public virtual int GetHurtSound() + { + if (BaseSoundID != 0) + { + return BaseSoundID + 3; + } + + return -1; + } + + public virtual int GetDeathSound() + { + if (BaseSoundID != 0) + { + return BaseSoundID + 4; + } + + if (m_Body.IsHuman) + { + return Utility.Random(m_Female ? 0x314 : 0x423, m_Female ? 4 : 5); + } + + return -1; + } + + public IPooledEnumerable GetItemsInRange(int range) => GetItemsInRange(range); + + public IPooledEnumerable GetItemsInRange(int range) where T : Item => + m_Map?.GetItemsInRange(m_Location, range) ?? Map.NullEnumerable.Instance; + + public IPooledEnumerable GetObjectsInRange(int range) => + m_Map?.GetObjectsInRange(m_Location, range) ?? Map.NullEnumerable.Instance; + + public IPooledEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); + + public IPooledEnumerable GetMobilesInRange(int range) where T : Mobile => + m_Map?.GetMobilesInRange(m_Location, range) ?? Map.NullEnumerable.Instance; + + public IPooledEnumerable GetClientsInRange(int range) => + m_Map?.GetClientsInRange(m_Location, range) ?? Map.NullEnumerable.Instance; + + public void SayTo(Mobile to, bool ascii, string text) => + PrivateOverheadMessage(MessageType.Regular, SpeechHue, ascii, text, to.NetState); + + public void SayTo(Mobile to, string text) => SayTo(to, false, text); + + public void SayTo(Mobile to, string format, params object[] args) => SayTo(to, false, string.Format(format, args)); + + public void SayTo(Mobile to, bool ascii, string format, params object[] args) => + SayTo(to, ascii, string.Format(format, args)); + + public void SayTo(Mobile to, int number, string args = "") => + to.NetState.SendMessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, args); + + public void Say(bool ascii, string text) => PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text); + + public void Say(string text) => PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text); + + public void Say(string format, params object[] args) => Say(string.Format(format, args)); + + public void Say(int number, AffixType type, string affix, string args) => + PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args); + + public void Say(int number, string args = "") => PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args); + + public void Emote(string text) => PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text); + + public void Emote(string format, params object[] args) => Emote(string.Format(format, args)); + + public void Emote(int number, string args = "") => PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args); + + public void Whisper(string text) => PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text); + + public void Whisper(string format, params object[] args) => Whisper(string.Format(format, args)); + + public void Whisper(int number, string args = "") => + PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args); + + public void Yell(string text) => PublicOverheadMessage(MessageType.Yell, YellHue, false, text); + + public void Yell(string format, params object[] args) => Yell(string.Format(format, args)); + + public void Yell(int number, string args = "") => + PublicOverheadMessage(MessageType.Yell, YellHue, number, args); + + public bool SendHuePicker(HuePicker p) + { + if (m_NetState != null) + { + p.SendTo(m_NetState); + return true; + } + + return false; + } + + public Gump FindGump() where T : Gump => m_NetState?.Gumps.Find(g => g is T); + + public bool CloseGump() where T : Gump + { + if (m_NetState == null) + { + return false; + } + + var gump = FindGump(); + + if (gump != null) + { + m_NetState.SendCloseGump(gump.TypeID, 0); + m_NetState.RemoveGump(gump); + gump.OnServerClose(m_NetState); + } + + return true; + } + + public bool CloseAllGumps() + { + var ns = m_NetState; + + if (ns.CannotSendPackets()) + { + return false; + } + + var gumps = new List(ns.Gumps); + + ns.ClearGumps(); + + foreach (var gump in gumps) + { + ns.SendCloseGump(gump.TypeID, 0); + + gump.OnServerClose(ns); + } + + return true; + } + + public bool HasGump() where T : Gump => FindGump() != null; + + public bool SendGump(Gump g) + { + if (m_NetState == null) + { + return false; + } + + g.SendTo(m_NetState); + return true; + } + + public bool SendMenu(IMenu m) + { + if (m_NetState == null) + { + return false; + } + + m.SendTo(m_NetState); + return true; + } + + public virtual bool CanBeBeneficial(Mobile target) => CanBeBeneficial(target, true, false); + + public virtual bool CanBeBeneficial(Mobile target, bool message) => CanBeBeneficial(target, message, false); + + public virtual bool CanBeBeneficial(Mobile target, bool message, bool allowDead) + { + if (target == null) + { + return false; + } + + if (Deleted || target.Deleted || !Alive || IsDeadBondedPet || + !allowDead && (!target.Alive || target.IsDeadBondedPet)) + { + if (message) + { + SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. + } + + return false; + } + + if (target == this) + { + return true; + } + + if ( /*m_Player &&*/!Region.AllowBeneficial(this, target)) + { + // TODO: Pets + // if (!(target.m_Player || target.Body.IsHuman || target.Body.IsAnimal)) + // { + if (message) + { + SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. + } + + return false; + // } + } + + return true; + } + + public virtual bool IsBeneficialCriminal(Mobile target) + { + if (this == target) + { + return false; + } + + var n = Notoriety.Compute(this, target); + + return n is Notoriety.Criminal or Notoriety.Murderer; + } + + /// + /// Overridable. Event invoked when the Mobile does a beneficial action. + /// + public virtual void OnBeneficialAction(Mobile target, bool isCriminal) + { + if (isCriminal) + { + CriminalAction(false); + } + } + + public virtual void DoBeneficial(Mobile target) + { + if (target == null) + { + return; + } + + OnBeneficialAction(target, IsBeneficialCriminal(target)); + + Region.OnBeneficialAction(this, target); + target.Region.OnGotBeneficialAction(this, target); + } + + public virtual bool BeneficialCheck(Mobile target) + { + if (CanBeBeneficial(target, true)) + { + DoBeneficial(target); + return true; + } + + return false; + } + + public virtual bool CanBeHarmful(Mobile target) => CanBeHarmful(target, true); + + public virtual bool CanBeHarmful(Mobile target, bool message) => CanBeHarmful(target, message, false); + + public virtual bool CanBeHarmful(Mobile target, bool message, bool ignoreOurBlessedness) + { + if (target == null) + { + return false; + } + + if (Deleted || !ignoreOurBlessedness && m_Blessed || target.Deleted || target.m_Blessed || !Alive || + IsDeadBondedPet || !target.Alive || target.IsDeadBondedPet) + { + if (message) + { + SendLocalizedMessage(1001018); // You can not perform negative acts on your target. + } + + return false; + } + + if (target == this) + { + return true; + } + + // TODO: Pets + if (!Region.AllowHarmful(this, target)) + { + if (message) + { + SendLocalizedMessage(1001018); // You can not perform negative acts on your target. + } + + return false; + } + + return true; + } + + public virtual bool IsHarmfulCriminal(Mobile target) => + this != target && Notoriety.Compute(this, target) == Notoriety.Innocent; + + /// + /// Overridable. Event invoked when the Mobile does a harmful action. + /// + public virtual void OnHarmfulAction(Mobile target, bool isCriminal) + { + if (isCriminal) + { + CriminalAction(false); + } + } + + public virtual void DoHarmful(Mobile target, bool indirect = false) + { + if (target == null || Deleted) + { + return; + } + + var isCriminal = IsHarmfulCriminal(target); + + OnHarmfulAction(target, isCriminal); + target.AggressiveAction(this, isCriminal); + + Region.OnDidHarmful(this, target); + target.Region.OnGotHarmful(this, target); + + if (!indirect) + { + Combatant = target; + } + + _expireCombatantTimerToken.Cancel(); + Timer.StartTimer(ExpireCombatantDelay, ExpireCombatant, out _expireCombatantTimerToken); + } + + public virtual bool HarmfulCheck(Mobile target) + { + if (CanBeHarmful(target)) + { + DoHarmful(target); + return true; + } + + return false; + } + + public bool RemoveStatMod(string name) + { + StatMods ??= new HashSet(); + + StatMod mod = GetStatMod(name); + + if (mod != null) + { + StatMods.Remove(mod); + CheckStatTimers(); + Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); + return true; + } + + return false; + } + + public StatMod GetStatMod(string name) + { + StatMods ??= new HashSet(); + + foreach (var check in StatMods) + { + if (check.Name == name) + { + return check; + } + } + + return null; + } + + public void AddStatMod(StatMod mod) + { + RemoveStatMod(mod.Name); + + StatMods.Add(mod); + Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); + CheckStatTimers(); + } + + private static MobileDelta GetStatDelta(StatType type) + { + MobileDelta delta = 0; + + if ((type & StatType.Str) != 0) + { + delta |= MobileDelta.Hits; + } + + if ((type & StatType.Dex) != 0) + { + delta |= MobileDelta.Stam; + } + + if ((type & StatType.Int) != 0) + { + delta |= MobileDelta.Mana; + } + + return delta; + } + + /// + /// Computes the total modified offset for the specified stat type. Expired instances are removed. + /// + public int GetStatOffset(StatType type) + { + var offset = 0; + + StatMods ??= new HashSet(); + + if (StatMods.Count > 0) + { + using var queue = PooledRefQueue.Create(8); + foreach (var mod in StatMods) + { + if (mod.HasElapsed()) + { + queue.Enqueue(mod); + } + else if ((mod.Type & type) != 0) + { + offset += mod.Offset; + } + } + + while (queue.Count > 0) + { + var mod = queue.Dequeue(); + StatMods.Remove(mod); + Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); + CheckStatTimers(); + } + } + + return offset; + } + + /// + /// Overridable. Virtual event invoked when the changes. + /// + /// + /// + public virtual void OnRawStrChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + /// + /// + public virtual void OnRawDexChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when the changes. + /// + /// + /// + public virtual void OnRawIntChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when the , , or + /// changes. + /// + /// + /// + /// + public virtual void OnRawStatChange(StatType stat, int oldValue) + { + } + + public virtual void OnHitsChange(int oldValue) + { + } + + public virtual void OnStamChange(int oldValue) + { + } + + public virtual void OnManaChange(int oldValue) + { + } + + /// + /// Overridable. Event invoked when a call to failed because + /// returned false: the Mobile was resistant to the poison. By default, this broadcasts an overhead message: * The poison + /// seems to have no effect. * + /// + /// + /// + /// + public virtual void OnPoisonImmunity(Mobile from, Poison poison) + { + PublicOverheadMessage(MessageType.Emote, 0x3B2, 1005534); // * The poison seems to have no effect. * + } + + /// + /// Overridable. Virtual event invoked when a call to failed because + /// returned false: the Mobile was already poisoned by an equal or greater strength poison. + /// + /// + /// + /// + public virtual void OnHigherPoison(Mobile from, Poison poison) + { + } + + /// + /// Overridable. Event invoked when a call to succeeded. By default, this broadcasts an overhead + /// message varying by the level of the poison. Example: * Zippy begins to spasm uncontrollably. * + /// + /// + /// + public virtual void OnPoisoned(Mobile from, Poison poison, Poison oldPoison) + { + if (poison != null) + { + LocalOverheadMessage(MessageType.Regular, 0x21, 1042857 + poison.Level * 2); + NonlocalOverheadMessage(MessageType.Regular, 0x21, 1042858 + poison.Level * 2, Name); + } + } + + /// + /// Overridable. Called from , this method checks if the Mobile is immune to some + /// . If true, will be invoked and + /// is returned. + /// + /// + /// + /// + public virtual bool CheckPoisonImmunity(Mobile from, Poison poison) => false; + + /// + /// Overridable. Called from , this method checks if the Mobile is already poisoned by some + /// of equal or greater strength. If true, will be invoked and + /// is returned. + /// + /// + /// + /// + public virtual bool CheckHigherPoison(Mobile from, Poison poison) => + m_Poison != null && m_Poison.Level >= poison.Level; + + /// + /// Overridable. Attempts to apply poison to the Mobile. Checks are made such that no + /// higher poison is active and that the Mobile is not + /// immune to the poison. Provided those assertions are true, the + /// is applied and is invoked. + /// + /// + /// + /// + /// One of four possible values: + /// + /// + /// + /// Cured + /// + /// The parameter was null and so was invoked. + /// + /// + /// + /// HigherPoisonActive + /// + /// The call to returned false. + /// + /// + /// + /// Immune + /// + /// The call to returned false. + /// + /// + /// + /// Poisoned + /// + /// The was successfully applied. + /// + /// + /// + public virtual ApplyPoisonResult ApplyPoison(Mobile from, Poison poison) + { + if (poison == null) + { + CurePoison(from); + return ApplyPoisonResult.Cured; + } + + if (CheckHigherPoison(from, poison)) + { + OnHigherPoison(from, poison); + return ApplyPoisonResult.HigherPoisonActive; + } + + if (CheckPoisonImmunity(from, poison)) + { + OnPoisonImmunity(from, poison); + return ApplyPoisonResult.Immune; + } + + var oldPoison = m_Poison; + Poison = poison; + + OnPoisoned(from, poison, oldPoison); + + return ApplyPoisonResult.Poisoned; + } + + /// + /// Overridable. Called from , this method checks to see that the Mobile can be cured of + /// + /// + /// + /// + public virtual bool CheckCure(Mobile from) => true; + + /// + /// Overridable. Virtual event invoked when a call to succeeded. + /// + /// + /// + /// + public virtual void OnCured(Mobile from, Poison oldPoison) + { + } + + /// + /// Overridable. Virtual event invoked when a call to failed. + /// + /// + /// + /// + public virtual void OnFailedCure(Mobile from) + { + } + + /// + /// Overridable. Attempts to cure any poison that is currently active. + /// + /// True if poison was cured, false if otherwise. + public virtual bool CurePoison(Mobile from) + { + if (CheckCure(from)) + { + var oldPoison = m_Poison; + Poison = null; + + OnCured(from, oldPoison); + + return true; + } + + OnFailedCure(from); + + return false; + } + + public void MovingEffect( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int hue, int renderMode + ) => + Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode); + + public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes) => + Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes); + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer, int unknown + ) => + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + layer, + unknown + ); + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int hue, int renderMode, int effect, int explodeEffect, int explodeSound, int unknown + ) => + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + (EffectLayer)255, + unknown + ); + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int effect, int explodeEffect, int explodeSound, int unknown + ) => + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + effect, + explodeEffect, + explodeSound, + unknown + ); + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int effect, int explodeEffect, int explodeSound + ) => + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + 0, + 0, + effect, + explodeEffect, + explodeSound, + 0 + ); + + public void FixedEffect(int itemID, int speed, int duration, int hue, int renderMode) => + Effects.SendTargetEffect(this, itemID, speed, duration, hue, renderMode); + + public void FixedEffect(int itemID, int speed, int duration) => + Effects.SendTargetEffect(this, itemID, speed, duration); + + public void FixedParticles( + int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer, int unknown + ) => + Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, unknown); + + public void FixedParticles( + int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer + ) => + Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer); + + public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer, int unknown) => + Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, unknown); + + public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer) => + Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer); + + public void BoltEffect(int hue) => Effects.SendBoltEffect(this, true, hue); + + public Direction GetDirectionTo(int x, int y, bool run = false) + { + var dx = m_Location.m_X - x; + var dy = m_Location.m_Y - y; + + var rx = (dx - dy) * 44; + var ry = (dx + dy) * 44; + + var ax = rx.Abs(); + var ay = ry.Abs(); + + Direction ret; + + if ((ay >> 1) - ax >= 0) + { + ret = ry > 0 ? Direction.Up : Direction.Down; + } + else if ((ax >> 1) - ay >= 0) + { + ret = rx > 0 ? Direction.Left : Direction.Right; + } + else + { + ret = rx switch + { + >= 0 when ry >= 0 => Direction.West, + >= 0 => Direction.South, + < 0 when ry < 0 => Direction.East, + _ => Direction.North + }; + } + + return ret | (run ? Direction.Running : 0); + } + + public Direction GetDirectionTo(Point2D p, bool run = false) => GetDirectionTo(p.m_X, p.m_Y, run); + public Direction GetDirectionTo(Point3D p, bool run = false) => GetDirectionTo(p.m_X, p.m_Y, run); + + public Direction GetDirectionTo(IPoint2D p, bool run = false) => + p == null ? Direction.North | (run ? Direction.Running : 0) : GetDirectionTo(p.X, p.Y, run); + + public void PublicOverheadMessage( + MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true, + AccessLevel accessLevel = AccessLevel.Player + ) + { + if (m_Map == null) + { + return; + } + + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + { + if ( + state.Mobile.AccessLevel >= accessLevel && + state.Mobile.CanSee(this) && + (noLineOfSight || state.Mobile.InLOS(this)) + ) + { + var length = OutgoingMessagePackets.CreateMessage( + buffer, Serial, Body, type, hue, 3, ascii, Language, Name, text + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + state.Send(buffer); + } + } + + eable.Free(); + } + + public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "", bool noLineOfSight = true) + { + if (m_Map == null) + { + return; + } + + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + { + if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) + { + var length = OutgoingMessagePackets.CreateMessageLocalized( + buffer, Serial, Body, type, hue, 3, number, Name, args + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + state.Send(buffer); + } + } + + eable.Free(); + } + + public void PublicOverheadMessage( + MessageType type, int hue, int number, AffixType affixType, string affix, + string args = "", bool noLineOfSight = false, + AccessLevel accessLevel = AccessLevel.Player + ) + { + if (m_Map == null) + { + return; + } + + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedAffixLength(affix, args)].InitializePacket(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + { + if ( + state.Mobile.AccessLevel >= accessLevel && + state.Mobile.CanSee(this) && + (noLineOfSight || state.Mobile.InLOS(this)) + ) + { + var length = OutgoingMessagePackets.CreateMessageLocalizedAffix( + buffer, Serial, Body, type, hue, 3, number, Name, affixType, affix, args + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + state.Send(buffer); + } + } + + eable.Free(); + } + + public void PrivateOverheadMessage(MessageType type, int hue, bool ascii, string text, NetState state) + { + state.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text); + } + + public void PrivateOverheadMessage(MessageType type, int hue, int number, NetState state) => + PrivateOverheadMessage(type, hue, number, "", state); + + public void PrivateOverheadMessage(MessageType type, int hue, int number, string args, NetState state) => + state.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args); + + public void LocalOverheadMessage(MessageType type, int hue, bool ascii, string text) => + m_NetState.SendMessage(Serial, Body, type, hue, 3, ascii, m_Language, Name, text); + + public void LocalOverheadMessage(MessageType type, int hue, int number, string args = "") => + m_NetState.SendMessageLocalized(Serial, Body, type, hue, 3, number, Name, args); + + public void NonlocalOverheadMessage(MessageType type, int hue, int number, string args = "") + { + if (m_Map == null) + { + return; + } + + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLocalizedLength(args)].InitializePacket(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + { + if (state != m_NetState && state.Mobile.CanSee(this)) + { + var length = OutgoingMessagePackets.CreateMessageLocalized( + buffer, Serial, Body, type, hue, 3, number, Name, args + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + state.Send(buffer); + } + } + + eable.Free(); + } + + public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, string text) + { + if (m_Map == null) + { + return; + } + + Span buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + { + if (state != m_NetState && state.Mobile.CanSee(this)) + { + var length = OutgoingMessagePackets.CreateMessage( + buffer, Serial, Body, type, hue, 3, ascii, Language, Name, text + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + state.Send(buffer); + } + } + + eable.Free(); + } + + public void SendLocalizedMessage(int number, string args = "", int hue = 0x3B2) => + m_NetState.SendMessageLocalized(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", args); + + public void SendLocalizedMessage(int number, bool append, string affix, string args = "", int hue = 0x3B2) => + m_NetState.SendMessageLocalizedAffix( + Serial.MinusOne, + -1, + MessageType.Regular, + hue, + 3, + number, + "System", + (append ? AffixType.Append : AffixType.Prepend) | AffixType.System, + affix, + args + ); + + public void SendMessage(string text) => SendMessage(0x3B2, text); + + public void SendMessage(string format, params object[] args) => + SendMessage(0x3B2, string.Format(format, args)); + + public void SendMessage(int hue, string text) => + m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, false, "ENU", "System", text); + + public void SendMessage(int hue, string format, params object[] args) => + SendMessage(hue, string.Format(format, args)); + + public void SendAsciiMessage(string text) => SendAsciiMessage(0x3B2, text); + + public void SendAsciiMessage(string format, params object[] args) => + SendAsciiMessage(0x3B2, string.Format(format, args)); + + public void SendAsciiMessage(int hue, string text) => + m_NetState.SendMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, true, null, "System", text); + + public void SendAsciiMessage(int hue, string format, params object[] args) => + SendAsciiMessage(hue, string.Format(format, args)); + + /// + /// Overridable. Event invoked when the Mobile is double clicked. By default, this method can either dismount or open the + /// paperdoll. + /// + /// + /// + public virtual void OnDoubleClick(Mobile from) + { + if (this == from && (!DisableDismountInWarmode || !m_Warmode)) + { + var mount = Mount; + + if (mount != null) + { + mount.Rider = null; + return; + } + } + + if (CanPaperdollBeOpenedBy(from)) + { + DisplayPaperdollTo(from); + } + } + + /// + /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who is over 18 tiles away. + /// + /// + public virtual void OnDoubleClickOutOfRange(Mobile from) + { + } + + /// + /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who can no longer see the Mobile. This + /// may + /// happen, for example, using 'Last Object' after the Mobile has hidden. + /// + /// + public virtual void OnDoubleClickCantSee(Mobile from) + { + } + + /// + /// Overridable. Event invoked when the Mobile is double clicked by someone who is not alive. Similar to + /// , this method will show the paperdoll. It does not, however, provide any dismount + /// functionality. + /// + /// + public virtual void OnDoubleClickDead(Mobile from) + { + if (CanPaperdollBeOpenedBy(from)) + { + DisplayPaperdollTo(from); + } + } + + private class ManaTimer : Timer + { + private readonly Mobile m_Owner; + + public ManaTimer(Mobile m) : base(GetManaRegenRate(m), GetManaRegenRate(m)) => m_Owner = m; + + protected override void OnTick() + { + if (m_Owner.CanRegenMana) + { + m_Owner.Mana++; + } + + Delay = Interval = GetManaRegenRate(m_Owner); + } + } + + private class HitsTimer : Timer + { + private readonly Mobile m_Owner; + + public HitsTimer(Mobile m) : base(GetHitsRegenRate(m), GetHitsRegenRate(m)) => m_Owner = m; + + protected override void OnTick() + { + if (m_Owner.CanRegenHits) + { + m_Owner.Hits++; + } + + Delay = Interval = GetHitsRegenRate(m_Owner); + } + } + + private class StamTimer : Timer + { + private readonly Mobile m_Owner; + + public StamTimer(Mobile m) : base(GetStamRegenRate(m), GetStamRegenRate(m)) => m_Owner = m; + + protected override void OnTick() + { + if (m_Owner.CanRegenStam) + { + m_Owner.Stam++; + } + + Delay = Interval = GetStamRegenRate(m_Owner); + } + } + + private void Logout() + { + if (m_Map != Map.Internal) + { + EventSink.InvokeLogout(this); + + LogoutLocation = m_Location; + LogoutMap = m_Map; + + Internalize(); + } + } + + private void ExpireCriminal() + { + Criminal = false; + } } diff --git a/Projects/Server/Mobiles/Movement.cs b/Projects/Server/Mobiles/Movement.cs index 44cfc473d..377e0675c 100644 --- a/Projects/Server/Mobiles/Movement.cs +++ b/Projects/Server/Mobiles/Movement.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Movement.cs * * * @@ -13,95 +13,94 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server.Movement +namespace Server.Movement; + +public static class Movement { - public static class Movement + // Movement implementation algorithm + public static IMovementImpl Impl { get; set; } + public static int WalkFootDelay { get; set; } = 400; + public static int RunFootDelay { get; set; } = 200; + public static int WalkMountDelay { get; set; } = 200; + public static int RunMountDelay { get; set; } = 100; + + public static bool EnableFastwalkPrevention { get; set; } = true; + public static AccessLevel FastwalkExemptionLevel { get; set; } = AccessLevel.Counselor; + + // If this is changed during runtime, then the steps array needs resizing. + public static int MaxSteps { get; private set; } = 3; + + public static void Configure() { - // Movement implementation algorithm - public static IMovementImpl Impl { get; set; } - public static int WalkFootDelay { get; set; } = 400; - public static int RunFootDelay { get; set; } = 200; - public static int WalkMountDelay { get; set; } = 200; - public static int RunMountDelay { get; set; } = 100; - - public static bool EnableFastwalkPrevention { get; set; } = true; - public static AccessLevel FastwalkExemptionLevel { get; set; } = AccessLevel.Counselor; - - // If this is changed during runtime, then the steps array needs resizing. - public static int MaxSteps { get; private set; } = 3; - - public static void Configure() - { - EnableFastwalkPrevention = ServerConfiguration.GetOrUpdateSetting("movement.enableFastWalkPrevention", EnableFastwalkPrevention); - MaxSteps = ServerConfiguration.GetOrUpdateSetting("movement.maxSteps", MaxSteps); - FastwalkExemptionLevel = ServerConfiguration.GetOrUpdateSetting("movement.fastwalkExemptionLevel", FastwalkExemptionLevel); - WalkFootDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.walkFoot", WalkFootDelay); - RunFootDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.runFoot", RunFootDelay); - WalkMountDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.walkMount", WalkMountDelay); - RunMountDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.runMount", RunMountDelay); - } - - public static bool CheckMovement(Mobile m, Direction d, out int newZ) - { - if (Impl != null) - { - return Impl.CheckMovement(m, d, out newZ); - } - - newZ = m.Z; - return false; - } - - public static bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ) - { - if (Impl != null) - { - return Impl.CheckMovement(m, map, loc, d, out newZ); - } - - newZ = m.Z; - return false; - } - - public static void Offset(Direction d, ref int x, ref int y) - { - switch (d & Direction.Mask) - { - case Direction.North: - --y; - break; - case Direction.South: - ++y; - break; - case Direction.West: - --x; - break; - case Direction.East: - ++x; - break; - case Direction.Right: - ++x; - --y; - break; - case Direction.Left: - --x; - ++y; - break; - case Direction.Down: - ++x; - ++y; - break; - case Direction.Up: - --x; - --y; - break; - } - } + EnableFastwalkPrevention = ServerConfiguration.GetOrUpdateSetting("movement.enableFastWalkPrevention", EnableFastwalkPrevention); + MaxSteps = ServerConfiguration.GetOrUpdateSetting("movement.maxSteps", MaxSteps); + FastwalkExemptionLevel = ServerConfiguration.GetOrUpdateSetting("movement.fastwalkExemptionLevel", FastwalkExemptionLevel); + WalkFootDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.walkFoot", WalkFootDelay); + RunFootDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.runFoot", RunFootDelay); + WalkMountDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.walkMount", WalkMountDelay); + RunMountDelay = ServerConfiguration.GetOrUpdateSetting("movement.delay.runMount", RunMountDelay); } - public interface IMovementImpl + public static bool CheckMovement(Mobile m, Direction d, out int newZ) { - bool CheckMovement(Mobile m, Direction d, out int newZ); - bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ); + if (Impl != null) + { + return Impl.CheckMovement(m, d, out newZ); + } + + newZ = m.Z; + return false; + } + + public static bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ) + { + if (Impl != null) + { + return Impl.CheckMovement(m, map, loc, d, out newZ); + } + + newZ = m.Z; + return false; + } + + public static void Offset(Direction d, ref int x, ref int y) + { + switch (d & Direction.Mask) + { + case Direction.North: + --y; + break; + case Direction.South: + ++y; + break; + case Direction.West: + --x; + break; + case Direction.East: + ++x; + break; + case Direction.Right: + ++x; + --y; + break; + case Direction.Left: + --x; + ++y; + break; + case Direction.Down: + ++x; + ++y; + break; + case Direction.Up: + --x; + --y; + break; + } } } + +public interface IMovementImpl +{ + bool CheckMovement(Mobile m, Direction d, out int newZ); + bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ); +} diff --git a/Projects/Server/Mobiles/Notoriety.cs b/Projects/Server/Mobiles/Notoriety.cs index c575a258d..19e45e001 100644 --- a/Projects/Server/Mobiles/Notoriety.cs +++ b/Projects/Server/Mobiles/Notoriety.cs @@ -1,33 +1,32 @@ -namespace Server +namespace Server; + +public delegate int NotorietyHandler(Mobile source, Mobile target); + +public static class Notoriety { - public delegate int NotorietyHandler(Mobile source, Mobile target); + public const int Innocent = 1; + public const int Ally = 2; + public const int CanBeAttacked = 3; + public const int Criminal = 4; + public const int Enemy = 5; + public const int Murderer = 6; + public const int Invulnerable = 7; - public static class Notoriety + public static NotorietyHandler Handler { get; set; } + + public static int[] Hues { get; set; } = { - public const int Innocent = 1; - public const int Ally = 2; - public const int CanBeAttacked = 3; - public const int Criminal = 4; - public const int Enemy = 5; - public const int Murderer = 6; - public const int Invulnerable = 7; + 0x000, + 0x059, + 0x03F, + 0x3B2, + 0x3B2, + 0x090, + 0x022, + 0x035 + }; - public static NotorietyHandler Handler { get; set; } + public static int GetHue(int noto) => noto < 0 || noto >= Hues.Length ? 0 : Hues[noto]; - public static int[] Hues { get; set; } = - { - 0x000, - 0x059, - 0x03F, - 0x3B2, - 0x3B2, - 0x090, - 0x022, - 0x035 - }; - - public static int GetHue(int noto) => noto < 0 || noto >= Hues.Length ? 0 : Hues[noto]; - - public static int Compute(Mobile source, Mobile target) => Handler?.Invoke(source, target) ?? CanBeAttacked; - } + public static int Compute(Mobile source, Mobile target) => Handler?.Invoke(source, target) ?? CanBeAttacked; } diff --git a/Projects/Server/Mobiles/SimplePrompt.cs b/Projects/Server/Mobiles/SimplePrompt.cs index 736feb8fb..b0cdec0bf 100644 --- a/Projects/Server/Mobiles/SimplePrompt.cs +++ b/Projects/Server/Mobiles/SimplePrompt.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SimplePrompt.cs * * * @@ -15,77 +15,76 @@ using Server.Prompts; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class SimplePrompt : Prompt { - public class SimplePrompt : Prompt + private readonly PromptCallback m_Callback; + private readonly bool m_CallbackHandlesCancel; + private readonly PromptCallback m_CancelCallback; + + public SimplePrompt(PromptCallback callback, PromptCallback cancelCallback) { - private readonly PromptCallback m_Callback; - private readonly bool m_CallbackHandlesCancel; - private readonly PromptCallback m_CancelCallback; - - public SimplePrompt(PromptCallback callback, PromptCallback cancelCallback) - { - m_Callback = callback; - m_CancelCallback = cancelCallback; - } - - public SimplePrompt(PromptCallback callback, bool callbackHandlesCancel = false) - { - m_Callback = callback; - m_CallbackHandlesCancel = callbackHandlesCancel; - } - - public override void OnResponse(Mobile from, string text) - { - m_Callback?.Invoke(from, text); - } - - public override void OnCancel(Mobile from) - { - if (m_CallbackHandlesCancel && m_Callback != null) - { - m_Callback(from, ""); - } - else - { - m_CancelCallback?.Invoke(from, ""); - } - } + m_Callback = callback; + m_CancelCallback = cancelCallback; } - public class SimpleStatePrompt : Prompt + public SimplePrompt(PromptCallback callback, bool callbackHandlesCancel = false) { - private readonly PromptStateCallback m_Callback; - private readonly PromptStateCallback m_CancelCallback; + m_Callback = callback; + m_CallbackHandlesCancel = callbackHandlesCancel; + } - private readonly T m_State; + public override void OnResponse(Mobile from, string text) + { + m_Callback?.Invoke(from, text); + } - public SimpleStatePrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) + public override void OnCancel(Mobile from) + { + if (m_CallbackHandlesCancel && m_Callback != null) { - m_Callback = callback; - m_CancelCallback = cancelCallback; - m_State = state; + m_Callback(from, ""); } - - public SimpleStatePrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) + else { - m_Callback = callback; - m_State = state; - m_CancelCallback = callbackHandlesCancel ? callback : null; - } - - public SimpleStatePrompt(PromptStateCallback callback, T state) : this(callback, false, state) - { - } - - public override void OnResponse(Mobile from, string text) - { - m_Callback?.Invoke(from, text, m_State); - } - - public override void OnCancel(Mobile from) - { - m_CancelCallback?.Invoke(from, "", m_State); + m_CancelCallback?.Invoke(from, ""); } } } + +public class SimpleStatePrompt : Prompt +{ + private readonly PromptStateCallback m_Callback; + private readonly PromptStateCallback m_CancelCallback; + + private readonly T m_State; + + public SimpleStatePrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) + { + m_Callback = callback; + m_CancelCallback = cancelCallback; + m_State = state; + } + + public SimpleStatePrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) + { + m_Callback = callback; + m_State = state; + m_CancelCallback = callbackHandlesCancel ? callback : null; + } + + public SimpleStatePrompt(PromptStateCallback callback, T state) : this(callback, false, state) + { + } + + public override void OnResponse(Mobile from, string text) + { + m_Callback?.Invoke(from, text, m_State); + } + + public override void OnCancel(Mobile from) + { + m_CancelCallback?.Invoke(from, "", m_State); + } +} diff --git a/Projects/Server/Mobiles/SimpleTarget.cs b/Projects/Server/Mobiles/SimpleTarget.cs index 014adef48..3207e89af 100644 --- a/Projects/Server/Mobiles/SimpleTarget.cs +++ b/Projects/Server/Mobiles/SimpleTarget.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SimpleTarget.cs * * * @@ -15,40 +15,39 @@ using Server.Targeting; -namespace Server.Mobiles +namespace Server.Mobiles; + +public class SimpleTarget : Target { - public class SimpleTarget : Target + private readonly TargetCallback m_Callback; + + public SimpleTarget(int range, TargetFlags flags, bool allowGround, TargetCallback callback) + : base(range, allowGround, flags) => + m_Callback = callback; + + protected override void OnTarget(Mobile from, object targeted) { - private readonly TargetCallback m_Callback; - - public SimpleTarget(int range, TargetFlags flags, bool allowGround, TargetCallback callback) - : base(range, allowGround, flags) => - m_Callback = callback; - - protected override void OnTarget(Mobile from, object targeted) - { - m_Callback?.Invoke(from, targeted); - } - } - - public class SimpleStateTarget : Target - { - private readonly TargetStateCallback m_Callback; - private readonly T m_State; - - public SimpleStateTarget( - int range, TargetFlags flags, bool allowGround, TargetStateCallback callback, - T state - ) - : base(range, allowGround, flags) - { - m_Callback = callback; - m_State = state; - } - - protected override void OnTarget(Mobile from, object targeted) - { - m_Callback?.Invoke(from, targeted, m_State); - } + m_Callback?.Invoke(from, targeted); + } +} + +public class SimpleStateTarget : Target +{ + private readonly TargetStateCallback m_Callback; + private readonly T m_State; + + public SimpleStateTarget( + int range, TargetFlags flags, bool allowGround, TargetStateCallback callback, + T state + ) + : base(range, allowGround, flags) + { + m_Callback = callback; + m_State = state; + } + + protected override void OnTarget(Mobile from, object targeted) + { + m_Callback?.Invoke(from, targeted, m_State); } } diff --git a/Projects/Server/Network/NetState/DumpNetStates.cs b/Projects/Server/Network/NetState/DumpNetStates.cs index 6992254dc..871976ed8 100644 --- a/Projects/Server/Network/NetState/DumpNetStates.cs +++ b/Projects/Server/Network/NetState/DumpNetStates.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: DumpNetStates.cs * * * @@ -15,25 +15,24 @@ using System.IO; -namespace Server.Network +namespace Server.Network; + +public static class DumpNetStates { - public static class DumpNetStates + public static void Initialize() { - public static void Initialize() + CommandSystem.Register("DumpNetStates", AccessLevel.Administrator, DumpNetStatesCommand); + } + + public static void DumpNetStatesCommand(CommandEventArgs args) + { + using var file = new StreamWriter("netstatedump.csv"); + + file.WriteLine("NetState, RecvTask, SendTask, ProtocolState, ParserState"); + + foreach (var ns in TcpServer.Instances) { - CommandSystem.Register("DumpNetStates", AccessLevel.Administrator, DumpNetStatesCommand); - } - - public static void DumpNetStatesCommand(CommandEventArgs args) - { - using var file = new StreamWriter("netstatedump.csv"); - - file.WriteLine("NetState, RecvTask, SendTask, ProtocolState, ParserState"); - - foreach (var ns in TcpServer.Instances) - { - file.WriteLine($"{ns}, {ns._protocolState}, {ns._parserState}"); - } + file.WriteLine($"{ns}, {ns._protocolState}, {ns._parserState}"); } } } diff --git a/Projects/Server/Network/NetState/NetState.ClientVersion.cs b/Projects/Server/Network/NetState/NetState.ClientVersion.cs index 844c9fb21..fdbc0b4cc 100644 --- a/Projects/Server/Network/NetState/NetState.ClientVersion.cs +++ b/Projects/Server/Network/NetState/NetState.ClientVersion.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: NetState.ClientVersion.cs * * * @@ -15,98 +15,97 @@ using System.Runtime.CompilerServices; -namespace Server.Network +namespace Server.Network; + +public partial class NetState { - public partial class NetState + public ProtocolChanges ProtocolChanges { get; set; } + public ClientFlags Flags { get; set; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasFlag(ClientFlags flag) => (Flags & flag) != 0; + + public ClientVersion Version { - public ProtocolChanges ProtocolChanges { get; set; } - public ClientFlags Flags { get; set; } + get => _version; + set => ProtocolChanges = ProtocolChangesByVersion(_version = value); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasFlag(ClientFlags flag) => (Flags & flag) != 0; - - public ClientVersion Version + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ProtocolChanges ProtocolChangesByVersion(ClientVersion version) => + version switch { - get => _version; - set => ProtocolChanges = ProtocolChangesByVersion(_version = value); - } + var v when v >= ClientVersion.Version70610 => ProtocolChanges.Version70610, + var v when v >= ClientVersion.Version70500 => ProtocolChanges.Version70500, + var v when v >= ClientVersion.Version704565 => ProtocolChanges.Version704565, + var v when v >= ClientVersion.Version70331 => ProtocolChanges.Version70331, + var v when v >= ClientVersion.Version70300 => ProtocolChanges.Version70300, + var v when v >= ClientVersion.Version70160 => ProtocolChanges.Version70160, + var v when v >= ClientVersion.Version70130 => ProtocolChanges.Version70130, + var v when v >= ClientVersion.Version7090 => ProtocolChanges.Version7090, + var v when v >= ClientVersion.Version7000 => ProtocolChanges.Version7000, + var v when v >= ClientVersion.Version60142 => ProtocolChanges.Version60142, + var v when v >= ClientVersion.Version6017 => ProtocolChanges.Version6017, + var v when v >= ClientVersion.Version6000 => ProtocolChanges.Version6000, + var v when v >= ClientVersion.Version502b => ProtocolChanges.Version502b, + var v when v >= ClientVersion.Version500a => ProtocolChanges.Version500a, + var v when v >= ClientVersion.Version407a => ProtocolChanges.Version407a, + var v when v >= ClientVersion.Version400a => ProtocolChanges.Version400a, + _ => ProtocolChanges.None + }; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ProtocolChanges ProtocolChangesByVersion(ClientVersion version) => - version switch - { - var v when v >= ClientVersion.Version70610 => ProtocolChanges.Version70610, - var v when v >= ClientVersion.Version70500 => ProtocolChanges.Version70500, - var v when v >= ClientVersion.Version704565 => ProtocolChanges.Version704565, - var v when v >= ClientVersion.Version70331 => ProtocolChanges.Version70331, - var v when v >= ClientVersion.Version70300 => ProtocolChanges.Version70300, - var v when v >= ClientVersion.Version70160 => ProtocolChanges.Version70160, - var v when v >= ClientVersion.Version70130 => ProtocolChanges.Version70130, - var v when v >= ClientVersion.Version7090 => ProtocolChanges.Version7090, - var v when v >= ClientVersion.Version7000 => ProtocolChanges.Version7000, - var v when v >= ClientVersion.Version60142 => ProtocolChanges.Version60142, - var v when v >= ClientVersion.Version6017 => ProtocolChanges.Version6017, - var v when v >= ClientVersion.Version6000 => ProtocolChanges.Version6000, - var v when v >= ClientVersion.Version502b => ProtocolChanges.Version502b, - var v when v >= ClientVersion.Version500a => ProtocolChanges.Version500a, - var v when v >= ClientVersion.Version407a => ProtocolChanges.Version407a, - var v when v >= ClientVersion.Version400a => ProtocolChanges.Version400a, - _ => ProtocolChanges.None - }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasProtocolChanges(ProtocolChanges changes) => (ProtocolChanges & changes) != 0; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasProtocolChanges(ProtocolChanges changes) => (ProtocolChanges & changes) != 0; + public bool NewSpellbook => HasProtocolChanges(ProtocolChanges.NewSpellbook); + public bool DamagePacket => HasProtocolChanges(ProtocolChanges.DamagePacket); + public bool Unpack => HasProtocolChanges(ProtocolChanges.Unpack); + public bool BuffIcon => HasProtocolChanges(ProtocolChanges.BuffIcon); + public bool NewHaven => HasProtocolChanges(ProtocolChanges.NewHaven); + public bool ContainerGridLines => HasProtocolChanges(ProtocolChanges.ContainerGridLines); + public bool ExtendedSupportedFeatures => HasProtocolChanges(ProtocolChanges.ExtendedSupportedFeatures); + public bool StygianAbyss => HasProtocolChanges(ProtocolChanges.StygianAbyss); + public bool HighSeas => HasProtocolChanges(ProtocolChanges.HighSeas); + public bool NewCharacterList => HasProtocolChanges(ProtocolChanges.NewCharacterList); + public bool NewCharacterCreation => HasProtocolChanges(ProtocolChanges.NewCharacterCreation); + public bool ExtendedStatus => HasProtocolChanges(ProtocolChanges.ExtendedStatus); + public bool NewMobileIncoming => HasProtocolChanges(ProtocolChanges.NewMobileIncoming); + public bool NewSecureTrading => HasProtocolChanges(ProtocolChanges.NewSecureTrading); - public bool NewSpellbook => HasProtocolChanges(ProtocolChanges.NewSpellbook); - public bool DamagePacket => HasProtocolChanges(ProtocolChanges.DamagePacket); - public bool Unpack => HasProtocolChanges(ProtocolChanges.Unpack); - public bool BuffIcon => HasProtocolChanges(ProtocolChanges.BuffIcon); - public bool NewHaven => HasProtocolChanges(ProtocolChanges.NewHaven); - public bool ContainerGridLines => HasProtocolChanges(ProtocolChanges.ContainerGridLines); - public bool ExtendedSupportedFeatures => HasProtocolChanges(ProtocolChanges.ExtendedSupportedFeatures); - public bool StygianAbyss => HasProtocolChanges(ProtocolChanges.StygianAbyss); - public bool HighSeas => HasProtocolChanges(ProtocolChanges.HighSeas); - public bool NewCharacterList => HasProtocolChanges(ProtocolChanges.NewCharacterList); - public bool NewCharacterCreation => HasProtocolChanges(ProtocolChanges.NewCharacterCreation); - public bool ExtendedStatus => HasProtocolChanges(ProtocolChanges.ExtendedStatus); - public bool NewMobileIncoming => HasProtocolChanges(ProtocolChanges.NewMobileIncoming); - public bool NewSecureTrading => HasProtocolChanges(ProtocolChanges.NewSecureTrading); + public bool IsUOTDClient => + (Flags & ClientFlags.UOTD) != 0 || _version?.Type == ClientType.UOTD; - public bool IsUOTDClient => - (Flags & ClientFlags.UOTD) != 0 || _version?.Type == ClientType.UOTD; + public bool IsSAClient => _version?.Type == ClientType.SA; - public bool IsSAClient => _version?.Type == ClientType.SA; + private ExpansionInfo m_Expansion; - private ExpansionInfo m_Expansion; - - public ExpansionInfo ExpansionInfo + public ExpansionInfo ExpansionInfo + { + get { - get + if (m_Expansion == null) { - if (m_Expansion == null) + for (var i = ExpansionInfo.Table.Length - 1; i >= 0; i--) { - for (var i = ExpansionInfo.Table.Length - 1; i >= 0; i--) + var info = ExpansionInfo.Table[i]; + + if (info.RequiredClient != null && Version >= info.RequiredClient || (Flags & info.ClientFlags) != 0) { - var info = ExpansionInfo.Table[i]; - - if (info.RequiredClient != null && Version >= info.RequiredClient || (Flags & info.ClientFlags) != 0) - { - m_Expansion = info; - break; - } + m_Expansion = info; + break; } - - m_Expansion ??= ExpansionInfo.GetInfo(Expansion.None); } - return m_Expansion; + m_Expansion ??= ExpansionInfo.GetInfo(Expansion.None); } + + return m_Expansion; } - - public bool SupportsExpansion(ExpansionInfo info, bool checkCoreExpansion = true) => - info != null && (!checkCoreExpansion || (int)Core.Expansion >= info.ID) && ExpansionInfo.ID >= info.ID; - - public bool SupportsExpansion(Expansion ex, bool checkCoreExpansion = true) => - SupportsExpansion(ExpansionInfo.GetInfo(ex), checkCoreExpansion); } + + public bool SupportsExpansion(ExpansionInfo info, bool checkCoreExpansion = true) => + info != null && (!checkCoreExpansion || (int)Core.Expansion >= info.ID) && ExpansionInfo.ID >= info.ID; + + public bool SupportsExpansion(Expansion ex, bool checkCoreExpansion = true) => + SupportsExpansion(ExpansionInfo.GetInfo(ex), checkCoreExpansion); } diff --git a/Projects/Server/Network/NetState/NetState.Fastwalk.cs b/Projects/Server/Network/NetState/NetState.Fastwalk.cs index 3b1036254..38591ae83 100644 --- a/Projects/Server/Network/NetState/NetState.Fastwalk.cs +++ b/Projects/Server/Network/NetState/NetState.Fastwalk.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: NetState.Fastwalk.cs * * * diff --git a/Projects/Server/Network/NetState/NetState.cs b/Projects/Server/Network/NetState/NetState.cs index 0282c0504..4a7602995 100755 --- a/Projects/Server/Network/NetState/NetState.cs +++ b/Projects/Server/Network/NetState/NetState.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: NetState.cs * * * diff --git a/Projects/Server/Network/NetState/ProtocolChanges.cs b/Projects/Server/Network/NetState/ProtocolChanges.cs index 5a866dfdd..54ab8f5a0 100644 --- a/Projects/Server/Network/NetState/ProtocolChanges.cs +++ b/Projects/Server/Network/NetState/ProtocolChanges.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ProtocolChanges.cs * * * @@ -15,44 +15,43 @@ using System; -namespace Server.Network -{ - [Flags] - public enum ProtocolChanges - { - None = 0x00000000, - NewSpellbook = 0x00000001, - DamagePacket = 0x00000002, - Unpack = 0x00000004, - BuffIcon = 0x00000008, - NewHaven = 0x00000010, - ContainerGridLines = 0x00000020, - ExtendedSupportedFeatures = 0x00000040, - StygianAbyss = 0x00000080, - HighSeas = 0x00000100, - NewCharacterList = 0x00000200, - NewCharacterCreation = 0x00000400, - ExtendedStatus = 0x00000800, - NewMobileIncoming = 0x00001000, - NewSecureTrading = 0x00002000, - UltimaStore = 0x00004000, - EndlessJourney = 0x00008000, +namespace Server.Network; - Version400a = NewSpellbook, - Version407a = Version400a | DamagePacket, - Version500a = Version407a | Unpack, - Version502b = Version500a | BuffIcon, - Version6000 = Version502b | NewHaven, - Version6017 = Version6000 | ContainerGridLines, - Version60142 = Version6017 | ExtendedSupportedFeatures, - Version7000 = Version60142 | StygianAbyss, - Version7090 = Version7000 | HighSeas, - Version70130 = Version7090 | NewCharacterList, - Version70160 = Version70130 | NewCharacterCreation, - Version70300 = Version70160 | ExtendedStatus, - Version70331 = Version70300 | NewMobileIncoming, - Version704565 = Version70331 | NewSecureTrading, - Version70500 = Version704565 | UltimaStore, - Version70610 = Version70500 | EndlessJourney - } +[Flags] +public enum ProtocolChanges +{ + None = 0x00000000, + NewSpellbook = 0x00000001, + DamagePacket = 0x00000002, + Unpack = 0x00000004, + BuffIcon = 0x00000008, + NewHaven = 0x00000010, + ContainerGridLines = 0x00000020, + ExtendedSupportedFeatures = 0x00000040, + StygianAbyss = 0x00000080, + HighSeas = 0x00000100, + NewCharacterList = 0x00000200, + NewCharacterCreation = 0x00000400, + ExtendedStatus = 0x00000800, + NewMobileIncoming = 0x00001000, + NewSecureTrading = 0x00002000, + UltimaStore = 0x00004000, + EndlessJourney = 0x00008000, + + Version400a = NewSpellbook, + Version407a = Version400a | DamagePacket, + Version500a = Version407a | Unpack, + Version502b = Version500a | BuffIcon, + Version6000 = Version502b | NewHaven, + Version6017 = Version6000 | ContainerGridLines, + Version60142 = Version6017 | ExtendedSupportedFeatures, + Version7000 = Version60142 | StygianAbyss, + Version7090 = Version7000 | HighSeas, + Version70130 = Version7090 | NewCharacterList, + Version70160 = Version70130 | NewCharacterCreation, + Version70300 = Version70160 | ExtendedStatus, + Version70331 = Version70300 | NewMobileIncoming, + Version704565 = Version70331 | NewSecureTrading, + Version70500 = Version704565 | UltimaStore, + Version70610 = Version70500 | EndlessJourney } diff --git a/Projects/Server/Network/NetworkCompression.cs b/Projects/Server/Network/NetworkCompression.cs index 53fe87d2c..1b9d24de5 100644 --- a/Projects/Server/Network/NetworkCompression.cs +++ b/Projects/Server/Network/NetworkCompression.cs @@ -1,114 +1,90 @@ using System; using System.Buffers; -namespace Server.Network +namespace Server.Network; + +/// +/// Handles outgoing packet compression for the network. +/// +public static class NetworkCompression { - /// - /// Handles outgoing packet compression for the network. - /// - public static class NetworkCompression + private const int CountIndex = 0; + private const int ValueIndex = 1; + + // UO packets may not exceed 64kb in length + public const int BufferSize = 0x10000; + + // Optimal compression ratio is 2 / 8; worst compression ratio is 11 / 8 + private const int MinimalCodeLength = 2; + private const int MaximalCodeLength = 11; + + // Fixed overhead, in bits, per compression call + private const int TerminalCodeLength = 4; + + // If our input exceeds this length, we cannot possibly compress it within the buffer + private const int DefiniteOverflow = (BufferSize * 8 - TerminalCodeLength) / MinimalCodeLength; + + private static readonly int[] _huffmanTable = { - private const int CountIndex = 0; - private const int ValueIndex = 1; + 0x2, 0x000, 0x5, 0x01F, 0x6, 0x022, 0x7, 0x034, 0x7, 0x075, 0x6, 0x028, 0x6, 0x03B, 0x7, 0x032, + 0x8, 0x0E0, 0x8, 0x062, 0x7, 0x056, 0x8, 0x079, 0x9, 0x19D, 0x8, 0x097, 0x6, 0x02A, 0x7, 0x057, + 0x8, 0x071, 0x8, 0x05B, 0x9, 0x1CC, 0x8, 0x0A7, 0x7, 0x025, 0x7, 0x04F, 0x8, 0x066, 0x8, 0x07D, + 0x9, 0x191, 0x9, 0x1CE, 0x7, 0x03F, 0x9, 0x090, 0x8, 0x059, 0x8, 0x07B, 0x8, 0x091, 0x8, 0x0C6, + 0x6, 0x02D, 0x9, 0x186, 0x8, 0x06F, 0x9, 0x093, 0xA, 0x1CC, 0x8, 0x05A, 0xA, 0x1AE, 0xA, 0x1C0, + 0x9, 0x148, 0x9, 0x14A, 0x9, 0x082, 0xA, 0x19F, 0x9, 0x171, 0x9, 0x120, 0x9, 0x0E7, 0xA, 0x1F3, + 0x9, 0x14B, 0x9, 0x100, 0x9, 0x190, 0x6, 0x013, 0x9, 0x161, 0x9, 0x125, 0x9, 0x133, 0x9, 0x195, + 0x9, 0x173, 0x9, 0x1CA, 0x9, 0x086, 0x9, 0x1E9, 0x9, 0x0DB, 0x9, 0x1EC, 0x9, 0x08B, 0x9, 0x085, + 0x5, 0x00A, 0x8, 0x096, 0x8, 0x09C, 0x9, 0x1C3, 0x9, 0x19C, 0x9, 0x08F, 0x9, 0x18F, 0x9, 0x091, + 0x9, 0x087, 0x9, 0x0C6, 0x9, 0x177, 0x9, 0x089, 0x9, 0x0D6, 0x9, 0x08C, 0x9, 0x1EE, 0x9, 0x1EB, + 0x9, 0x084, 0x9, 0x164, 0x9, 0x175, 0x9, 0x1CD, 0x8, 0x05E, 0x9, 0x088, 0x9, 0x12B, 0x9, 0x172, + 0x9, 0x10A, 0x9, 0x08D, 0x9, 0x13A, 0x9, 0x11C, 0xA, 0x1E1, 0xA, 0x1E0, 0x9, 0x187, 0xA, 0x1DC, + 0xA, 0x1DF, 0x7, 0x074, 0x9, 0x19F, 0x8, 0x08D, 0x8, 0x0E4, 0x7, 0x079, 0x9, 0x0EA, 0x9, 0x0E1, + 0x8, 0x040, 0x7, 0x041, 0x9, 0x10B, 0x9, 0x0B0, 0x8, 0x06A, 0x8, 0x0C1, 0x7, 0x071, 0x7, 0x078, + 0x8, 0x0B1, 0x9, 0x14C, 0x7, 0x043, 0x8, 0x076, 0x7, 0x066, 0x7, 0x04D, 0x9, 0x08A, 0x6, 0x02F, + 0x8, 0x0C9, 0x9, 0x0CE, 0x9, 0x149, 0x9, 0x160, 0xA, 0x1BA, 0xA, 0x19E, 0xA, 0x39F, 0x9, 0x0E5, + 0x9, 0x194, 0x9, 0x184, 0x9, 0x126, 0x7, 0x030, 0x8, 0x06C, 0x9, 0x121, 0x9, 0x1E8, 0xA, 0x1C1, + 0xA, 0x11D, 0xA, 0x163, 0xA, 0x385, 0xA, 0x3DB, 0xA, 0x17D, 0xA, 0x106, 0xA, 0x397, 0xA, 0x24E, + 0x7, 0x02E, 0x8, 0x098, 0xA, 0x33C, 0xA, 0x32E, 0xA, 0x1E9, 0x9, 0x0BF, 0xA, 0x3DF, 0xA, 0x1DD, + 0xA, 0x32D, 0xA, 0x2ED, 0xA, 0x30B, 0xA, 0x107, 0xA, 0x2E8, 0xA, 0x3DE, 0xA, 0x125, 0xA, 0x1E8, + 0x9, 0x0E9, 0xA, 0x1CD, 0xA, 0x1B5, 0x9, 0x165, 0xA, 0x232, 0xA, 0x2E1, 0xB, 0x3AE, 0xB, 0x3C6, + 0xB, 0x3E2, 0xA, 0x205, 0xA, 0x29A, 0xA, 0x248, 0xA, 0x2CD, 0xA, 0x23B, 0xB, 0x3C5, 0xA, 0x251, + 0xA, 0x2E9, 0xA, 0x252, 0x9, 0x1EA, 0xB, 0x3A0, 0xB, 0x391, 0xA, 0x23C, 0xB, 0x392, 0xB, 0x3D5, + 0xA, 0x233, 0xA, 0x2CC, 0xB, 0x390, 0xA, 0x1BB, 0xB, 0x3A1, 0xB, 0x3C4, 0xA, 0x211, 0xA, 0x203, + 0x9, 0x12A, 0xA, 0x231, 0xB, 0x3E0, 0xA, 0x29B, 0xB, 0x3D7, 0xA, 0x202, 0xB, 0x3AD, 0xA, 0x213, + 0xA, 0x253, 0xA, 0x32C, 0xA, 0x23D, 0xA, 0x23F, 0xA, 0x32F, 0xA, 0x11C, 0xA, 0x384, 0xA, 0x31C, + 0xA, 0x17C, 0xA, 0x30A, 0xA, 0x2E0, 0xA, 0x276, 0xA, 0x250, 0xB, 0x3E3, 0xA, 0x396, 0xA, 0x18F, + 0xA, 0x204, 0xA, 0x206, 0xA, 0x230, 0xA, 0x265, 0xA, 0x212, 0xA, 0x23E, 0xB, 0x3AC, 0xB, 0x393, + 0xB, 0x3E1, 0xA, 0x1DE, 0xB, 0x3D6, 0xA, 0x31D, 0xB, 0x3E5, 0xB, 0x3E4, 0xA, 0x207, 0xB, 0x3C7, + 0xA, 0x277, 0xB, 0x3D4, 0x8, 0x0C0, 0xA, 0x162, 0xA, 0x3DA, 0xA, 0x124, 0xA, 0x1B4, 0xA, 0x264, + 0xA, 0x33D, 0xA, 0x1D1, 0xA, 0x1AF, 0xA, 0x39E, 0xA, 0x24F, 0xB, 0x373, 0xA, 0x249, 0xB, 0x372, + 0x9, 0x167, 0xA, 0x210, 0xA, 0x23A, 0xA, 0x1B8, 0xB, 0x3AF, 0xA, 0x18E, 0xA, 0x2EC, 0x7, 0x062, + 0x4, 0x00D + }; - // UO packets may not exceed 64kb in length - public const int BufferSize = 0x10000; + public static void Compress(ReadOnlySpan input, CircularBuffer output, out int length) + { + length = Compress(input, output); + } - // Optimal compression ratio is 2 / 8; worst compression ratio is 11 / 8 - private const int MinimalCodeLength = 2; - private const int MaximalCodeLength = 11; - - // Fixed overhead, in bits, per compression call - private const int TerminalCodeLength = 4; - - // If our input exceeds this length, we cannot possibly compress it within the buffer - private const int DefiniteOverflow = (BufferSize * 8 - TerminalCodeLength) / MinimalCodeLength; - - private static readonly int[] _huffmanTable = + public static int Compress(ReadOnlySpan input, CircularBuffer output) + { + if (input.Length > DefiniteOverflow) { - 0x2, 0x000, 0x5, 0x01F, 0x6, 0x022, 0x7, 0x034, 0x7, 0x075, 0x6, 0x028, 0x6, 0x03B, 0x7, 0x032, - 0x8, 0x0E0, 0x8, 0x062, 0x7, 0x056, 0x8, 0x079, 0x9, 0x19D, 0x8, 0x097, 0x6, 0x02A, 0x7, 0x057, - 0x8, 0x071, 0x8, 0x05B, 0x9, 0x1CC, 0x8, 0x0A7, 0x7, 0x025, 0x7, 0x04F, 0x8, 0x066, 0x8, 0x07D, - 0x9, 0x191, 0x9, 0x1CE, 0x7, 0x03F, 0x9, 0x090, 0x8, 0x059, 0x8, 0x07B, 0x8, 0x091, 0x8, 0x0C6, - 0x6, 0x02D, 0x9, 0x186, 0x8, 0x06F, 0x9, 0x093, 0xA, 0x1CC, 0x8, 0x05A, 0xA, 0x1AE, 0xA, 0x1C0, - 0x9, 0x148, 0x9, 0x14A, 0x9, 0x082, 0xA, 0x19F, 0x9, 0x171, 0x9, 0x120, 0x9, 0x0E7, 0xA, 0x1F3, - 0x9, 0x14B, 0x9, 0x100, 0x9, 0x190, 0x6, 0x013, 0x9, 0x161, 0x9, 0x125, 0x9, 0x133, 0x9, 0x195, - 0x9, 0x173, 0x9, 0x1CA, 0x9, 0x086, 0x9, 0x1E9, 0x9, 0x0DB, 0x9, 0x1EC, 0x9, 0x08B, 0x9, 0x085, - 0x5, 0x00A, 0x8, 0x096, 0x8, 0x09C, 0x9, 0x1C3, 0x9, 0x19C, 0x9, 0x08F, 0x9, 0x18F, 0x9, 0x091, - 0x9, 0x087, 0x9, 0x0C6, 0x9, 0x177, 0x9, 0x089, 0x9, 0x0D6, 0x9, 0x08C, 0x9, 0x1EE, 0x9, 0x1EB, - 0x9, 0x084, 0x9, 0x164, 0x9, 0x175, 0x9, 0x1CD, 0x8, 0x05E, 0x9, 0x088, 0x9, 0x12B, 0x9, 0x172, - 0x9, 0x10A, 0x9, 0x08D, 0x9, 0x13A, 0x9, 0x11C, 0xA, 0x1E1, 0xA, 0x1E0, 0x9, 0x187, 0xA, 0x1DC, - 0xA, 0x1DF, 0x7, 0x074, 0x9, 0x19F, 0x8, 0x08D, 0x8, 0x0E4, 0x7, 0x079, 0x9, 0x0EA, 0x9, 0x0E1, - 0x8, 0x040, 0x7, 0x041, 0x9, 0x10B, 0x9, 0x0B0, 0x8, 0x06A, 0x8, 0x0C1, 0x7, 0x071, 0x7, 0x078, - 0x8, 0x0B1, 0x9, 0x14C, 0x7, 0x043, 0x8, 0x076, 0x7, 0x066, 0x7, 0x04D, 0x9, 0x08A, 0x6, 0x02F, - 0x8, 0x0C9, 0x9, 0x0CE, 0x9, 0x149, 0x9, 0x160, 0xA, 0x1BA, 0xA, 0x19E, 0xA, 0x39F, 0x9, 0x0E5, - 0x9, 0x194, 0x9, 0x184, 0x9, 0x126, 0x7, 0x030, 0x8, 0x06C, 0x9, 0x121, 0x9, 0x1E8, 0xA, 0x1C1, - 0xA, 0x11D, 0xA, 0x163, 0xA, 0x385, 0xA, 0x3DB, 0xA, 0x17D, 0xA, 0x106, 0xA, 0x397, 0xA, 0x24E, - 0x7, 0x02E, 0x8, 0x098, 0xA, 0x33C, 0xA, 0x32E, 0xA, 0x1E9, 0x9, 0x0BF, 0xA, 0x3DF, 0xA, 0x1DD, - 0xA, 0x32D, 0xA, 0x2ED, 0xA, 0x30B, 0xA, 0x107, 0xA, 0x2E8, 0xA, 0x3DE, 0xA, 0x125, 0xA, 0x1E8, - 0x9, 0x0E9, 0xA, 0x1CD, 0xA, 0x1B5, 0x9, 0x165, 0xA, 0x232, 0xA, 0x2E1, 0xB, 0x3AE, 0xB, 0x3C6, - 0xB, 0x3E2, 0xA, 0x205, 0xA, 0x29A, 0xA, 0x248, 0xA, 0x2CD, 0xA, 0x23B, 0xB, 0x3C5, 0xA, 0x251, - 0xA, 0x2E9, 0xA, 0x252, 0x9, 0x1EA, 0xB, 0x3A0, 0xB, 0x391, 0xA, 0x23C, 0xB, 0x392, 0xB, 0x3D5, - 0xA, 0x233, 0xA, 0x2CC, 0xB, 0x390, 0xA, 0x1BB, 0xB, 0x3A1, 0xB, 0x3C4, 0xA, 0x211, 0xA, 0x203, - 0x9, 0x12A, 0xA, 0x231, 0xB, 0x3E0, 0xA, 0x29B, 0xB, 0x3D7, 0xA, 0x202, 0xB, 0x3AD, 0xA, 0x213, - 0xA, 0x253, 0xA, 0x32C, 0xA, 0x23D, 0xA, 0x23F, 0xA, 0x32F, 0xA, 0x11C, 0xA, 0x384, 0xA, 0x31C, - 0xA, 0x17C, 0xA, 0x30A, 0xA, 0x2E0, 0xA, 0x276, 0xA, 0x250, 0xB, 0x3E3, 0xA, 0x396, 0xA, 0x18F, - 0xA, 0x204, 0xA, 0x206, 0xA, 0x230, 0xA, 0x265, 0xA, 0x212, 0xA, 0x23E, 0xB, 0x3AC, 0xB, 0x393, - 0xB, 0x3E1, 0xA, 0x1DE, 0xB, 0x3D6, 0xA, 0x31D, 0xB, 0x3E5, 0xB, 0x3E4, 0xA, 0x207, 0xB, 0x3C7, - 0xA, 0x277, 0xB, 0x3D4, 0x8, 0x0C0, 0xA, 0x162, 0xA, 0x3DA, 0xA, 0x124, 0xA, 0x1B4, 0xA, 0x264, - 0xA, 0x33D, 0xA, 0x1D1, 0xA, 0x1AF, 0xA, 0x39E, 0xA, 0x24F, 0xB, 0x373, 0xA, 0x249, 0xB, 0x372, - 0x9, 0x167, 0xA, 0x210, 0xA, 0x23A, 0xA, 0x1B8, 0xB, 0x3AF, 0xA, 0x18E, 0xA, 0x2EC, 0x7, 0x062, - 0x4, 0x00D - }; - - public static void Compress(ReadOnlySpan input, CircularBuffer output, out int length) - { - length = Compress(input, output); + return 0; } - public static int Compress(ReadOnlySpan input, CircularBuffer output) + int bitCount = 0; + int bitValue = 0; + + int inputIdx = 0; + int outputIdx = 0; + + while (inputIdx < input.Length) { - if (input.Length > DefiniteOverflow) - { - return 0; - } + int i = input[inputIdx++] << 1; - int bitCount = 0; - int bitValue = 0; - - int inputIdx = 0; - int outputIdx = 0; - - while (inputIdx < input.Length) - { - int i = input[inputIdx++] << 1; - - bitCount += _huffmanTable[i]; - bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; - - while (bitCount >= 8) - { - bitCount -= 8; - - if (output.Length < outputIdx + 1) - { - return 0; - } - - output[outputIdx++] = (byte)(bitValue >> bitCount); - } - } - - // terminal code - bitCount += _huffmanTable[0x200]; - bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; - - // align on byte boundary - if ((bitCount & 7) != 0) - { - bitValue <<= 8 - (bitCount & 7); - bitCount += 8 - (bitCount & 7); - } + bitCount += _huffmanTable[i]; + bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; while (bitCount >= 8) { @@ -121,53 +97,53 @@ namespace Server.Network output[outputIdx++] = (byte)(bitValue >> bitCount); } - - return outputIdx; } - public static int Compress(CircularBuffer input, CircularBuffer output) + // terminal code + bitCount += _huffmanTable[0x200]; + bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; + + // align on byte boundary + if ((bitCount & 7) != 0) { - if (input.Length > DefiniteOverflow) + bitValue <<= 8 - (bitCount & 7); + bitCount += 8 - (bitCount & 7); + } + + while (bitCount >= 8) + { + bitCount -= 8; + + if (output.Length < outputIdx + 1) { return 0; } - int bitCount = 0; - int bitValue = 0; + output[outputIdx++] = (byte)(bitValue >> bitCount); + } - int inputIdx = 0; - int outputIdx = 0; + return outputIdx; + } - while (inputIdx < input.Length) - { - int i = input[inputIdx++] << 1; + public static int Compress(CircularBuffer input, CircularBuffer output) + { + if (input.Length > DefiniteOverflow) + { + return 0; + } - bitCount += _huffmanTable[i]; - bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; + int bitCount = 0; + int bitValue = 0; - while (bitCount >= 8) - { - bitCount -= 8; + int inputIdx = 0; + int outputIdx = 0; - if (output.Length < outputIdx + 1) - { - return 0; - } + while (inputIdx < input.Length) + { + int i = input[inputIdx++] << 1; - output[outputIdx++] = (byte)(bitValue >> bitCount); - } - } - - // terminal code - bitCount += _huffmanTable[0x200]; - bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; - - // align on byte boundary - if ((bitCount & 7) != 0) - { - bitValue <<= 8 - (bitCount & 7); - bitCount += 8 - (bitCount & 7); - } + bitCount += _huffmanTable[i]; + bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; while (bitCount >= 8) { @@ -180,53 +156,53 @@ namespace Server.Network output[outputIdx++] = (byte)(bitValue >> bitCount); } - - return outputIdx; } - public static int Compress(ReadOnlySpan input, Span output) + // terminal code + bitCount += _huffmanTable[0x200]; + bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; + + // align on byte boundary + if ((bitCount & 7) != 0) { - if (input.Length > DefiniteOverflow) + bitValue <<= 8 - (bitCount & 7); + bitCount += 8 - (bitCount & 7); + } + + while (bitCount >= 8) + { + bitCount -= 8; + + if (output.Length < outputIdx + 1) { return 0; } - int bitCount = 0; - int bitValue = 0; + output[outputIdx++] = (byte)(bitValue >> bitCount); + } - int inputIdx = 0; - int outputIdx = 0; + return outputIdx; + } - while (inputIdx < input.Length) - { - int i = input[inputIdx++] << 1; + public static int Compress(ReadOnlySpan input, Span output) + { + if (input.Length > DefiniteOverflow) + { + return 0; + } - bitCount += _huffmanTable[i]; - bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; + int bitCount = 0; + int bitValue = 0; - while (bitCount >= 8) - { - bitCount -= 8; + int inputIdx = 0; + int outputIdx = 0; - if (output.Length < outputIdx + 1) - { - return 0; - } + while (inputIdx < input.Length) + { + int i = input[inputIdx++] << 1; - output[outputIdx++] = (byte)(bitValue >> bitCount); - } - } - - // terminal code - bitCount += _huffmanTable[0x200]; - bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; - - // align on byte boundary - if ((bitCount & 7) != 0) - { - bitValue <<= 8 - (bitCount & 7); - bitCount += 8 - (bitCount & 7); - } + bitCount += _huffmanTable[i]; + bitValue = (bitValue << _huffmanTable[i]) | _huffmanTable[i + 1]; while (bitCount >= 8) { @@ -239,8 +215,31 @@ namespace Server.Network output[outputIdx++] = (byte)(bitValue >> bitCount); } - - return outputIdx; } + + // terminal code + bitCount += _huffmanTable[0x200]; + bitValue = (bitValue << _huffmanTable[0x200]) | _huffmanTable[0x201]; + + // align on byte boundary + if ((bitCount & 7) != 0) + { + bitValue <<= 8 - (bitCount & 7); + bitCount += 8 - (bitCount & 7); + } + + while (bitCount >= 8) + { + bitCount -= 8; + + if (output.Length < outputIdx + 1) + { + return 0; + } + + output[outputIdx++] = (byte)(bitValue >> bitCount); + } + + return outputIdx; } } diff --git a/Projects/Server/Network/PacketUtilities.cs b/Projects/Server/Network/PacketUtilities.cs index 320cda166..b26fd232a 100644 --- a/Projects/Server/Network/PacketUtilities.cs +++ b/Projects/Server/Network/PacketUtilities.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PacketUtilities.cs * * * @@ -18,51 +18,50 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; -namespace Server.Network +namespace Server.Network; + +public static class PacketUtilities { - public static class PacketUtilities + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WritePacketLength(this ref CircularBufferWriter writer) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WritePacketLength(this ref CircularBufferWriter writer) - { - var length = writer.Position; - writer.Seek(1, SeekOrigin.Begin); - writer.Write((ushort)length); - writer.Seek(length, SeekOrigin.Begin); - } + var length = writer.Position; + writer.Seek(1, SeekOrigin.Begin); + writer.Write((ushort)length); + writer.Seek(length, SeekOrigin.Begin); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WritePacketLength(this ref SpanWriter writer) - { - writer.Seek(1, SeekOrigin.Begin); - writer.Write((ushort)writer.BytesWritten); - writer.Seek(0, SeekOrigin.End); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WritePacketLength(this ref SpanWriter writer) + { + writer.Seek(1, SeekOrigin.Begin); + writer.Write((ushort)writer.BytesWritten); + writer.Seek(0, SeekOrigin.End); + } - // If LOCAL INIT is off, then stack/heap allocations have garbage data - // Initializes the first byte (Packet ID) so it can be used as a flag. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span InitializePacket(this Span buffer) - { + // If LOCAL INIT is off, then stack/heap allocations have garbage data + // Initializes the first byte (Packet ID) so it can be used as a flag. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span InitializePacket(this Span buffer) + { #if NO_LOCAL_INIT if (buffer != null) { buffer[0] = 0; } #endif - return buffer; - } + return buffer; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span InitializePackets(this Span buffer, int width) - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span InitializePackets(this Span buffer, int width) + { #if NO_LOCAL_INIT for (var i = 0; i < buffer.Length; i += width) { buffer[i] = 0; } #endif - return buffer; - } + return buffer; } } diff --git a/Projects/Server/Network/Packets/IncomingAccountPackets.cs b/Projects/Server/Network/Packets/IncomingAccountPackets.cs index 54775eb4d..c67c50c0c 100644 --- a/Projects/Server/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/Server/Network/Packets/IncomingAccountPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingAccountPackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingEntityPackets.cs b/Projects/Server/Network/Packets/IncomingEntityPackets.cs index 30f56b7ef..c4ec34fcf 100644 --- a/Projects/Server/Network/Packets/IncomingEntityPackets.cs +++ b/Projects/Server/Network/Packets/IncomingEntityPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingEntityPackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingHousePackets.cs b/Projects/Server/Network/Packets/IncomingHousePackets.cs index a1ba476ab..64cea7e1f 100644 --- a/Projects/Server/Network/Packets/IncomingHousePackets.cs +++ b/Projects/Server/Network/Packets/IncomingHousePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingHousePackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingItemPackets.cs b/Projects/Server/Network/Packets/IncomingItemPackets.cs index beb58b189..c508c148c 100644 --- a/Projects/Server/Network/Packets/IncomingItemPackets.cs +++ b/Projects/Server/Network/Packets/IncomingItemPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingItemPackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingMessagePackets.cs b/Projects/Server/Network/Packets/IncomingMessagePackets.cs index 5404bc2ed..212a32d6c 100644 --- a/Projects/Server/Network/Packets/IncomingMessagePackets.cs +++ b/Projects/Server/Network/Packets/IncomingMessagePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingMessagePackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingMobilePackets.cs b/Projects/Server/Network/Packets/IncomingMobilePackets.cs index 1cdcb7c62..81d9b24ac 100644 --- a/Projects/Server/Network/Packets/IncomingMobilePackets.cs +++ b/Projects/Server/Network/Packets/IncomingMobilePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingMobilePackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingMovementPackets.cs b/Projects/Server/Network/Packets/IncomingMovementPackets.cs index 9fc1bb0a3..729b20841 100644 --- a/Projects/Server/Network/Packets/IncomingMovementPackets.cs +++ b/Projects/Server/Network/Packets/IncomingMovementPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingMovementPackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingPlayerPackets.cs b/Projects/Server/Network/Packets/IncomingPlayerPackets.cs index d49b3107c..fd6c143eb 100644 --- a/Projects/Server/Network/Packets/IncomingPlayerPackets.cs +++ b/Projects/Server/Network/Packets/IncomingPlayerPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingPlayerPackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingTargetingPackets.cs b/Projects/Server/Network/Packets/IncomingTargetingPackets.cs index b4efa9e81..27dd1c251 100644 --- a/Projects/Server/Network/Packets/IncomingTargetingPackets.cs +++ b/Projects/Server/Network/Packets/IncomingTargetingPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingTargetingPackets.cs * * * diff --git a/Projects/Server/Network/Packets/IncomingVendorPackets.cs b/Projects/Server/Network/Packets/IncomingVendorPackets.cs index 2f11b20e4..8a165953c 100644 --- a/Projects/Server/Network/Packets/IncomingVendorPackets.cs +++ b/Projects/Server/Network/Packets/IncomingVendorPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IncomingVendorPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs index 1fa8ba71d..db41472ba 100644 --- a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingAccountPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingCombatPackets.cs b/Projects/Server/Network/Packets/OutgoingCombatPackets.cs index c5b23631b..700bfad0c 100644 --- a/Projects/Server/Network/Packets/OutgoingCombatPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingCombatPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingCombatPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingContainerPackets.cs b/Projects/Server/Network/Packets/OutgoingContainerPackets.cs index f3071c135..efda030f7 100644 --- a/Projects/Server/Network/Packets/OutgoingContainerPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingContainerPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingContainerPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingDamagePackets.cs b/Projects/Server/Network/Packets/OutgoingDamagePackets.cs index 41d99d0b9..30032794a 100644 --- a/Projects/Server/Network/Packets/OutgoingDamagePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingDamagePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingDamagePackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingEffectPackets.cs b/Projects/Server/Network/Packets/OutgoingEffectPackets.cs index 1b3d80c8c..96a27368b 100644 --- a/Projects/Server/Network/Packets/OutgoingEffectPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingEffectPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingEffectPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingEntityPackets.cs b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs index 1e546c962..1ccb65255 100644 --- a/Projects/Server/Network/Packets/OutgoingEntityPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingEntityPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingEntityPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingEquipmentPackets.cs b/Projects/Server/Network/Packets/OutgoingEquipmentPackets.cs index 543203f6e..d0a35ce93 100644 --- a/Projects/Server/Network/Packets/OutgoingEquipmentPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingEquipmentPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingEquipmentPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingItemPackets.cs b/Projects/Server/Network/Packets/OutgoingItemPackets.cs index 7f72363ce..cfdaf6b0a 100644 --- a/Projects/Server/Network/Packets/OutgoingItemPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingItemPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingItemPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingLightPackets.cs b/Projects/Server/Network/Packets/OutgoingLightPackets.cs index f47042f0f..fe1246ebf 100644 --- a/Projects/Server/Network/Packets/OutgoingLightPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingLightPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingLightPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingMapPackets.cs b/Projects/Server/Network/Packets/OutgoingMapPackets.cs index a0e71abb7..3640aa137 100644 --- a/Projects/Server/Network/Packets/OutgoingMapPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMapPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingMapPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingMessagePackets.cs b/Projects/Server/Network/Packets/OutgoingMessagePackets.cs index 77763f92f..686cbdf1e 100644 --- a/Projects/Server/Network/Packets/OutgoingMessagePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMessagePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingMessagePackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs index 876ce54ea..e32ea0823 100644 --- a/Projects/Server/Network/Packets/OutgoingMobilePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMobilePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingMobilePackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingMovementPackets.cs b/Projects/Server/Network/Packets/OutgoingMovementPackets.cs index b366a7dec..a5cdf61d3 100644 --- a/Projects/Server/Network/Packets/OutgoingMovementPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMovementPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingMovementPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs b/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs index af33c444e..3550b0303 100644 --- a/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingPlayerPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingPlayerPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingSecureTradePackets.cs b/Projects/Server/Network/Packets/OutgoingSecureTradePackets.cs index 4459d3038..5f15fc8a1 100644 --- a/Projects/Server/Network/Packets/OutgoingSecureTradePackets.cs +++ b/Projects/Server/Network/Packets/OutgoingSecureTradePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingSecureTradePackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingTargetPackets.cs b/Projects/Server/Network/Packets/OutgoingTargetPackets.cs index 3de19ae85..ac33da781 100644 --- a/Projects/Server/Network/Packets/OutgoingTargetPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingTargetPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingTargetPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingVendorBuyPackets.cs b/Projects/Server/Network/Packets/OutgoingVendorBuyPackets.cs index 429792d6d..f5b13fdd2 100644 --- a/Projects/Server/Network/Packets/OutgoingVendorBuyPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingVendorBuyPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingVendorBuyPackets.cs * * * diff --git a/Projects/Server/Network/Packets/OutgoingVendorSellPackets.cs b/Projects/Server/Network/Packets/OutgoingVendorSellPackets.cs index 90489b668..116734bf1 100644 --- a/Projects/Server/Network/Packets/OutgoingVendorSellPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingVendorSellPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingVendorSellPackets.cs * * * diff --git a/Projects/Server/Network/Pipe.cs b/Projects/Server/Network/Pipe.cs index d8cbfe290..a0cac2cd1 100644 --- a/Projects/Server/Network/Pipe.cs +++ b/Projects/Server/Network/Pipe.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Pipe.cs * * * @@ -17,489 +17,488 @@ using System; using System.Runtime.CompilerServices; using System.Threading; -namespace Server.Network +namespace Server.Network; + +public interface IPipeTask : INotifyCompletion { - public interface IPipeTask : INotifyCompletion + public IPipeTask GetAwaiter(); + + public bool IsCompleted { get; } + + public T GetResult(); +} + +public class Pipe +{ + public struct Result { - public IPipeTask GetAwaiter(); + public ArraySegment[] Buffer { get; } + public bool IsClosed { get; set; } - public bool IsCompleted { get; } - - public T GetResult(); - } - - public class Pipe - { - public struct Result + public int Length { - public ArraySegment[] Buffer { get; } - public bool IsClosed { get; set; } - - public int Length + get { - get + var length = 0; + for (int i = 0; i < Buffer.Length; i++) { - var length = 0; - for (int i = 0; i < Buffer.Length; i++) - { - length += Buffer[i].Count; - } - - return length; + length += Buffer[i].Count; } + + return length; + } + } + + public void CopyFrom(ReadOnlySpan bytes) + { + var remaining = bytes.Length; + var offset = 0; + + if (remaining == 0) + { + return; } - public void CopyFrom(ReadOnlySpan bytes) + for (int i = 0; i < 2; i++) { - var remaining = bytes.Length; - var offset = 0; + var buffer = Buffer[i]; + var sz = Math.Min(remaining, buffer.Count); + bytes.Slice(offset, sz).CopyTo(buffer); + + remaining -= sz; + offset += sz; if (remaining == 0) { return; } - - for (int i = 0; i < 2; i++) - { - var buffer = Buffer[i]; - var sz = Math.Min(remaining, buffer.Count); - bytes.Slice(offset, sz).CopyTo(buffer); - - remaining -= sz; - offset += sz; - - if (remaining == 0) - { - return; - } - } - - throw new OutOfMemoryException(); } - public Result(int segments) - { - IsClosed = false; - Buffer = new ArraySegment[segments]; - } + throw new OutOfMemoryException(); } - public class PipeWriter : IPipeTask + public Result(int segments) { - private readonly Pipe _pipe; + IsClosed = false; + Buffer = new ArraySegment[segments]; + } + } - private Result _result = new(2); + public class PipeWriter : IPipeTask + { + private readonly Pipe _pipe; - internal PipeWriter(Pipe pipe) => _pipe = pipe; + private Result _result = new(2); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetAvailable() + internal PipeWriter(Pipe pipe) => _pipe = pipe; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetAvailable() + { + var read = _pipe._readIdx; + var write = _pipe._writeIdx; + + if (read <= write) { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; - - if (read <= write) + if (read == 0) { - if (read == 0) - { - return _pipe.Size - write - 1; - } - - return _pipe.Size - write + (read - 1); + return _pipe.Size - write - 1; } - return read - write - 1; + return _pipe.Size - write + (read - 1); } - public Result TryGetMemory() + return read - write - 1; + } + + public Result TryGetMemory() + { + var read = _pipe._readIdx; + var write = _pipe._writeIdx; + + _result.IsClosed = _pipe._closed; + + if (read <= write) { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; + var readZero = read == 0; + var sz = _pipe.Size - write - (readZero ? 1 : 0); - _result.IsClosed = _pipe._closed; + _result.Buffer[0] = sz == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)write, (int)sz); + _result.Buffer[1] = readZero ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, 0, (int)read - 1); + } + else + { + var sz = read - write - 1; - if (read <= write) - { - var readZero = read == 0; - var sz = _pipe.Size - write - (readZero ? 1 : 0); - - _result.Buffer[0] = sz == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)write, (int)sz); - _result.Buffer[1] = readZero ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, 0, (int)read - 1); - } - else - { - var sz = read - write - 1; - - _result.Buffer[0] = sz == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)write, (int)sz); - _result.Buffer[1] = ArraySegment.Empty; - } - - return _result; + _result.Buffer[0] = sz == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)write, (int)sz); + _result.Buffer[1] = ArraySegment.Empty; } - public IPipeTask GetMemory() - { - if (_pipe._writeAwaitBeginning) - { - throw new Exception("Double await on writer"); - } + return _result; + } - return this; + public IPipeTask GetMemory() + { + if (_pipe._writeAwaitBeginning) + { + throw new Exception("Double await on writer"); } - public void Advance(uint count) + return this; + } + + public void Advance(uint count) + { + var read = _pipe._readIdx; + var write = _pipe._writeIdx; + + if (count == 0) { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; + return; + } - if (count == 0) - { - return; - } + if (count > _pipe.Size - 1) + { + throw new InvalidOperationException(); + } - if (count > _pipe.Size - 1) + if (read <= write) + { + if (count > read + _pipe.Size - write - 1) { throw new InvalidOperationException(); } - if (read <= write) + var sz = Math.Min(count, _pipe.Size - write); + + write += sz; + if (write > _pipe.Size - 1) { - if (count > read + _pipe.Size - write - 1) + write = 0; + } + count -= sz; + + if (count > 0) + { + if (count >= read) { throw new InvalidOperationException(); } - var sz = Math.Min(count, _pipe.Size - write); - - write += sz; - if (write > _pipe.Size - 1) - { - write = 0; - } - count -= sz; - - if (count > 0) - { - if (count >= read) - { - throw new InvalidOperationException(); - } - - write = count; - } + write = count; } - else - { - if (count > read - write - 1) - { - throw new InvalidOperationException(); - } - - write += count; - } - - // It's never valid to advance the write pointer to become equal to - // the read pointer. Check that here. - if (write == read) - { - throw new InvalidOperationException("Write index equals read index after advance"); - } - - _pipe._writeIdx = write; } - - public void Close() + else { - _pipe._closed = true; - - var waiting = _pipe._readAwaitBeginning; - - if (!waiting) + if (count > read - write - 1) { - return; + throw new InvalidOperationException(); } - Action continuation; - - do - { - continuation = _pipe._readContinuation; - } while (continuation == null); - - _pipe._readContinuation = null; - _pipe._readAwaitBeginning = false; - - ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); + write += count; } - public void Flush() + // It's never valid to advance the write pointer to become equal to + // the read pointer. Check that here. + if (write == read) { - if (_pipe._readIdx == _pipe._writeIdx) - { - return; - } - - var waiting = _pipe._readAwaitBeginning; - - if (!waiting) - { - return; - } - - Action continuation; - - do - { - continuation = _pipe._readContinuation; - } while (continuation == null); - - _pipe._readContinuation = null; - _pipe._readAwaitBeginning = false; - - ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); + throw new InvalidOperationException("Write index equals read index after advance"); } - #region Awaitable - - // The following makes it possible to await the writer. Do not use any of this directly. - - public IPipeTask GetAwaiter() => this; - - public bool IsCompleted - { - get - { - if (GetAvailable() > 0) - { - return true; - } - - if (_pipe._closed) - { - return true; - } - - _pipe._writeAwaitBeginning = true; - return false; - } - } - - public Result GetResult() => TryGetMemory(); - - public void OnCompleted(Action continuation) => _pipe._writeContinuation = continuation; - - #endregion + _pipe._writeIdx = write; } - public class PipeReader : IPipeTask + public void Close() { - private readonly Pipe _pipe; + _pipe._closed = true; - private Result _result = new(2); + var waiting = _pipe._readAwaitBeginning; - internal PipeReader(Pipe pipe) => _pipe = pipe; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetAvailable() + if (!waiting) { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; - - if (read <= write) - { - return write - read; - } - - return write + _pipe.Size - read; + return; } - public Result TryRead() + Action continuation; + + do { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; + continuation = _pipe._readContinuation; + } while (continuation == null); - _result.IsClosed = _pipe._closed; + _pipe._readContinuation = null; + _pipe._readAwaitBeginning = false; - if (read <= write) - { - _result.Buffer[0] = write - read == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)read, (int)(write - read)); - _result.Buffer[1] = ArraySegment.Empty; - } - else - { - _result.Buffer[0] = _pipe.Size - read == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)read, (int)(_pipe.Size - read)); - _result.Buffer[1] = write == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, 0, (int)write); - } - - return _result; - } - - public IPipeTask Read() - { - if (_pipe._readAwaitBeginning) - { - throw new Exception("Double await on reader"); - } - - return this; - } - - public void Advance(uint count) - { - var read = _pipe._readIdx; - var write = _pipe._writeIdx; - - if (read <= write) - { - if (count > write - read) - { - throw new InvalidOperationException(); - } - - read += count; - } - else - { - var sz = Math.Min(count, _pipe.Size - read); - - read += sz; - if (read > _pipe.Size - 1) - { - read = 0; - } - count -= sz; - - if (count > 0) - { - if (count > write) - { - throw new InvalidOperationException(); - } - - read = count; - } - } - - _pipe._readIdx = read; - } - - public void Commit() - { - if (_pipe._readIdx == ((_pipe._writeIdx + 1) & (_pipe.Size - 1))) - { - return; - } - - var waiting = _pipe._writeAwaitBeginning; - - if (!waiting) - { - return; - } - - Action continuation; - - do - { - continuation = _pipe._writeContinuation; - } while (continuation == null); - - _pipe._writeContinuation = null; - _pipe._writeAwaitBeginning = false; - - ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); - } - - public void Close() - { - _pipe._closed = true; - - var waiting = _pipe._writeAwaitBeginning; - - if (!waiting) - { - return; - } - - Action continuation; - - do - { - continuation = _pipe._writeContinuation; - } while (continuation == null); - - _pipe._writeContinuation = null; - _pipe._writeAwaitBeginning = false; - - ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); - } - - #region Awaitable - - // The following makes it possible to await the reader. Do not use any of this directly. - - public IPipeTask GetAwaiter() => this; - - public bool IsCompleted - { - get - { - if (GetAvailable() > 0) - { - return true; - } - - if (_pipe._closed) - { - return true; - } - - _pipe._readAwaitBeginning = true; - return false; - } - } - - public Result GetResult() => TryRead(); - - public void OnCompleted(Action continuation) => _pipe._readContinuation = continuation; - - #endregion + ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); } - private readonly T[] _buffer; - private volatile uint _writeIdx; - private volatile uint _readIdx; - private bool _closed; - - public PipeWriter Writer { get; } - public PipeReader Reader { get; } - - public uint Size => (uint)_buffer.Length; - - public Pipe(T[] buf) + public void Flush() { - // Test if the buffer is a power of two - if (buf.Length == 0 || (buf.Length & (buf.Length - 1)) != 0) + if (_pipe._readIdx == _pipe._writeIdx) { - throw new ArgumentOutOfRangeException(nameof(buf), "Pipe buffers must have a length that is a power of two"); + return; } - _buffer = buf; - _writeIdx = 0; - _readIdx = 0; - _closed = false; + var waiting = _pipe._readAwaitBeginning; - Writer = new PipeWriter(this); - Reader = new PipeReader(this); + if (!waiting) + { + return; + } + + Action continuation; + + do + { + continuation = _pipe._readContinuation; + } while (continuation == null); + + _pipe._readContinuation = null; + _pipe._readAwaitBeginning = false; + + ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); } #region Awaitable - private volatile bool _readAwaitBeginning; - private volatile Action _readContinuation; - private volatile bool _writeAwaitBeginning; - private volatile Action _writeContinuation; + // The following makes it possible to await the writer. Do not use any of this directly. + + public IPipeTask GetAwaiter() => this; + + public bool IsCompleted + { + get + { + if (GetAvailable() > 0) + { + return true; + } + + if (_pipe._closed) + { + return true; + } + + _pipe._writeAwaitBeginning = true; + return false; + } + } + + public Result GetResult() => TryGetMemory(); + + public void OnCompleted(Action continuation) => _pipe._writeContinuation = continuation; #endregion } + + public class PipeReader : IPipeTask + { + private readonly Pipe _pipe; + + private Result _result = new(2); + + internal PipeReader(Pipe pipe) => _pipe = pipe; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetAvailable() + { + var read = _pipe._readIdx; + var write = _pipe._writeIdx; + + if (read <= write) + { + return write - read; + } + + return write + _pipe.Size - read; + } + + public Result TryRead() + { + var read = _pipe._readIdx; + var write = _pipe._writeIdx; + + _result.IsClosed = _pipe._closed; + + if (read <= write) + { + _result.Buffer[0] = write - read == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)read, (int)(write - read)); + _result.Buffer[1] = ArraySegment.Empty; + } + else + { + _result.Buffer[0] = _pipe.Size - read == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, (int)read, (int)(_pipe.Size - read)); + _result.Buffer[1] = write == 0 ? ArraySegment.Empty : new ArraySegment(_pipe._buffer, 0, (int)write); + } + + return _result; + } + + public IPipeTask Read() + { + if (_pipe._readAwaitBeginning) + { + throw new Exception("Double await on reader"); + } + + return this; + } + + public void Advance(uint count) + { + var read = _pipe._readIdx; + var write = _pipe._writeIdx; + + if (read <= write) + { + if (count > write - read) + { + throw new InvalidOperationException(); + } + + read += count; + } + else + { + var sz = Math.Min(count, _pipe.Size - read); + + read += sz; + if (read > _pipe.Size - 1) + { + read = 0; + } + count -= sz; + + if (count > 0) + { + if (count > write) + { + throw new InvalidOperationException(); + } + + read = count; + } + } + + _pipe._readIdx = read; + } + + public void Commit() + { + if (_pipe._readIdx == ((_pipe._writeIdx + 1) & (_pipe.Size - 1))) + { + return; + } + + var waiting = _pipe._writeAwaitBeginning; + + if (!waiting) + { + return; + } + + Action continuation; + + do + { + continuation = _pipe._writeContinuation; + } while (continuation == null); + + _pipe._writeContinuation = null; + _pipe._writeAwaitBeginning = false; + + ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); + } + + public void Close() + { + _pipe._closed = true; + + var waiting = _pipe._writeAwaitBeginning; + + if (!waiting) + { + return; + } + + Action continuation; + + do + { + continuation = _pipe._writeContinuation; + } while (continuation == null); + + _pipe._writeContinuation = null; + _pipe._writeAwaitBeginning = false; + + ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), true); + } + + #region Awaitable + + // The following makes it possible to await the reader. Do not use any of this directly. + + public IPipeTask GetAwaiter() => this; + + public bool IsCompleted + { + get + { + if (GetAvailable() > 0) + { + return true; + } + + if (_pipe._closed) + { + return true; + } + + _pipe._readAwaitBeginning = true; + return false; + } + } + + public Result GetResult() => TryRead(); + + public void OnCompleted(Action continuation) => _pipe._readContinuation = continuation; + + #endregion + } + + private readonly T[] _buffer; + private volatile uint _writeIdx; + private volatile uint _readIdx; + private bool _closed; + + public PipeWriter Writer { get; } + public PipeReader Reader { get; } + + public uint Size => (uint)_buffer.Length; + + public Pipe(T[] buf) + { + // Test if the buffer is a power of two + if (buf.Length == 0 || (buf.Length & (buf.Length - 1)) != 0) + { + throw new ArgumentOutOfRangeException(nameof(buf), "Pipe buffers must have a length that is a power of two"); + } + + _buffer = buf; + _writeIdx = 0; + _readIdx = 0; + _closed = false; + + Writer = new PipeWriter(this); + Reader = new PipeReader(this); + } + + #region Awaitable + private volatile bool _readAwaitBeginning; + private volatile Action _readContinuation; + + private volatile bool _writeAwaitBeginning; + private volatile Action _writeContinuation; + + #endregion } diff --git a/Projects/Server/Network/ServerInfo.cs b/Projects/Server/Network/ServerInfo.cs index d88233a41..9f3578a64 100644 --- a/Projects/Server/Network/ServerInfo.cs +++ b/Projects/Server/Network/ServerInfo.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ServerInfo.cs * * * @@ -17,44 +17,43 @@ using System; using System.Buffers.Binary; using System.Net; -namespace Server.Network +namespace Server.Network; + +public sealed class ServerInfo { - public sealed class ServerInfo + private readonly IPEndPoint m_Address; + + public ServerInfo(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address) { - private readonly IPEndPoint m_Address; - - public ServerInfo(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address) - { - Name = name; - FullPercent = fullPercent; - TimeZone = tz.GetUtcOffset(DateTime.Now).Hours; - Address = address; - } - - public string Name { get; set; } - - public int FullPercent { get; set; } - - public int TimeZone { get; set; } - - public IPEndPoint Address - { - get => m_Address; - init - { - m_Address = value; - Span integer = stackalloc byte[4]; - value.Address.MapToIPv4().TryWriteBytes(integer, out var bytesWritten); - if (bytesWritten != 4) - { - throw new InvalidOperationException("IP Address could not be serialized to an integer"); - } - - RawAddress = BinaryPrimitives.ReadUInt32LittleEndian(integer); - } - } - - // UO doesn't support IPv6 servers - public uint RawAddress { get; private set; } + Name = name; + FullPercent = fullPercent; + TimeZone = tz.GetUtcOffset(DateTime.Now).Hours; + Address = address; } + + public string Name { get; set; } + + public int FullPercent { get; set; } + + public int TimeZone { get; set; } + + public IPEndPoint Address + { + get => m_Address; + init + { + m_Address = value; + Span integer = stackalloc byte[4]; + value.Address.MapToIPv4().TryWriteBytes(integer, out var bytesWritten); + if (bytesWritten != 4) + { + throw new InvalidOperationException("IP Address could not be serialized to an integer"); + } + + RawAddress = BinaryPrimitives.ReadUInt32LittleEndian(integer); + } + } + + // UO doesn't support IPv6 servers + public uint RawAddress { get; private set; } } diff --git a/Projects/Server/Network/TcpServer.cs b/Projects/Server/Network/TcpServer.cs index c82a92537..efb6c9002 100644 --- a/Projects/Server/Network/TcpServer.cs +++ b/Projects/Server/Network/TcpServer.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TcpServer.cs * * * @@ -21,188 +21,187 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using Server.Logging; -namespace Server.Network +namespace Server.Network; + +public static class TcpServer { - public static class TcpServer + private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer)); + + private const int MaxConnectionsPerLoop = 250; + + // Sanity. 256 * 1024 * 4096 = ~1.3GB of ram + public static int MaxConnections { get; set; } = 4096; + + private const long _listenerErrorMessageDelay = 10000; // 10 seconds + private static long _nextMaximumSocketsReachedMessage; + + // 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 HashSet Instances { get; } = new(2048); + + private static readonly ConcurrentQueue _connectedQueue = new(); + + public static void Configure() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer)); + MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", MaxConnections); + } - private const int MaxConnectionsPerLoop = 250; + public static void Start() + { + HashSet listeningAddresses = new HashSet(); + List listeners = new List(); - // Sanity. 256 * 1024 * 4096 = ~1.3GB of ram - public static int MaxConnections { get; set; } = 4096; - - private const long _listenerErrorMessageDelay = 10000; // 10 seconds - private static long _nextMaximumSocketsReachedMessage; - - // 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 HashSet Instances { get; } = new(2048); - - private static readonly ConcurrentQueue _connectedQueue = new(); - - public static void Configure() + foreach (var ipep in ServerConfiguration.Listeners) { - MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", MaxConnections); - } - - public static void Start() - { - HashSet listeningAddresses = new HashSet(); - List listeners = new List(); - - foreach (var ipep in ServerConfiguration.Listeners) + var listener = CreateListener(ipep); + if (listener == null) { - 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); - listener.BeginAcceptingSockets(); + continue; } - foreach (var ipep in listeningAddresses) + if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) { - logger.Information("Listening: {Address}:{Port}", ipep.Address, ipep.Port); + listeningAddresses.UnionWith(GetListeningAddresses(ipep)); + } + else + { + listeningAddresses.Add(ipep); } - ListeningAddresses = listeningAddresses.ToArray(); - Listeners = listeners.ToArray(); + listeners.Add(listener); + listener.BeginAcceptingSockets(); } - public static void Shutdown() + foreach (var ipep in listeningAddresses) { - foreach (var listener in Listeners) + logger.Information("Listening: {Address}:{Port}", ipep.Address, ipep.Port); + } + + ListeningAddresses = listeningAddresses.ToArray(); + Listeners = listeners.ToArray(); + } + + public static void Shutdown() + { + foreach (var listener in Listeners) + { + listener.Close(); + } + } + + public static IEnumerable GetListeningAddresses(IPEndPoint ipep) => + NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter => + adapter.GetIPProperties().UnicastAddresses + .Where(uip => ipep.AddressFamily == uip.Address.AddressFamily) + .Select(uip => new IPEndPoint(uip.Address, ipep.Port)) + ); + + public static Socket CreateListener(IPEndPoint ipep) + { + var listener = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) + { + LingerState = new LingerOption(false, 0), + ExclusiveAddressUse = true, + NoDelay = true + }; + + try + { + listener.Bind(ipep); + listener.Listen(32); + return listener; + } + catch (SocketException se) + { + // WSAEADDRINUSE + if (se.ErrorCode == 10048) { - listener.Close(); + logger.Warning("Listener: {Address}:{Port}: Failed (In Use)", ipep.Address, ipep.Port); + } + // WSAEADDRNOTAVAIL + else if (se.ErrorCode == 10049) + { + logger.Warning("Listener {Address}:{Port}: Failed (Unavailable)", ipep.Address, ipep.Port); + } + else + { + logger.Warning(se, "Listener Exception:"); } } - public static IEnumerable GetListeningAddresses(IPEndPoint ipep) => - NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter => - adapter.GetIPProperties().UnicastAddresses - .Where(uip => ipep.AddressFamily == uip.Address.AddressFamily) - .Select(uip => new IPEndPoint(uip.Address, ipep.Port)) - ); + return null; + } - public static Socket CreateListener(IPEndPoint ipep) + public static void Slice() + { + int count = 0; + + while (++count <= MaxConnectionsPerLoop && _connectedQueue.TryDequeue(out var ns)) { - var listener = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp) - { - LingerState = new LingerOption(false, 0), - ExclusiveAddressUse = true, - NoDelay = true - }; + Instances.Add(ns); + ns.LogInfo($"Connected. [{Instances.Count} Online]"); + } + } + private static async void BeginAcceptingSockets(this Socket listener) + { + while (true) + { try { - listener.Bind(ipep); - listener.Listen(32); - return listener; - } - catch (SocketException se) - { - // WSAEADDRINUSE - if (se.ErrorCode == 10048) + var socket = await listener.AcceptAsync(); + + var rejected = false; + if (Instances.Count >= MaxConnections) { - logger.Warning("Listener: {Address}:{Port}: Failed (In Use)", ipep.Address, ipep.Port); - } - // WSAEADDRNOTAVAIL - else if (se.ErrorCode == 10049) - { - logger.Warning("Listener {Address}:{Port}: Failed (Unavailable)", ipep.Address, ipep.Port); - } - else - { - logger.Warning(se, "Listener Exception:"); - } - } + rejected = true; - return null; - } + var ticks = Core.TickCount; - public static void Slice() - { - int count = 0; - - while (++count <= MaxConnectionsPerLoop && _connectedQueue.TryDequeue(out var ns)) - { - Instances.Add(ns); - ns.LogInfo($"Connected. [{Instances.Count} Online]"); - } - } - - private static async void BeginAcceptingSockets(this Socket listener) - { - while (true) - { - try - { - var socket = await listener.AcceptAsync(); - - var rejected = false; - if (Instances.Count >= MaxConnections) + if (_nextMaximumSocketsReachedMessage <= ticks) { - rejected = true; - - var ticks = Core.TickCount; - - if (_nextMaximumSocketsReachedMessage <= ticks) - { - if (socket.RemoteEndPoint is IPEndPoint ipep) - { - var ip = ipep.Address.ToString(); - logger.Warning("Listener {Address}: Failed (Maximum connections reached)", ip); - NetState.TraceDisconnect("Maximum connections reached.", ip); - } - - _nextMaximumSocketsReachedMessage = ticks + _listenerErrorMessageDelay; - } - } - - var args = new SocketConnectEventArgs(socket); - EventSink.InvokeSocketConnect(args); - - if (!args.AllowConnection) - { - rejected = true; if (socket.RemoteEndPoint is IPEndPoint ipep) { var ip = ipep.Address.ToString(); - NetState.TraceDisconnect("Rejected by socket event handler", ip); + logger.Warning("Listener {Address}: Failed (Maximum connections reached)", ip); + NetState.TraceDisconnect("Maximum connections reached.", ip); } - } - if (rejected) - { - socket.Send(_socketRejected, SocketFlags.None); - socket.Shutdown(SocketShutdown.Both); - socket.Close(); - } - else - { - var ns = new NetState(socket); - _connectedQueue.Enqueue(ns); + _nextMaximumSocketsReachedMessage = ticks + _listenerErrorMessageDelay; } } - catch + + var args = new SocketConnectEventArgs(socket); + EventSink.InvokeSocketConnect(args); + + if (!args.AllowConnection) { - // ignored + rejected = true; + if (socket.RemoteEndPoint is IPEndPoint ipep) + { + var ip = ipep.Address.ToString(); + NetState.TraceDisconnect("Rejected by socket event handler", ip); + } } + + if (rejected) + { + socket.Send(_socketRejected, SocketFlags.None); + socket.Shutdown(SocketShutdown.Both); + socket.Close(); + } + else + { + var ns = new NetState(socket); + _connectedQueue.Enqueue(ns); + } + } + catch + { + // ignored } } } diff --git a/Projects/Server/Party.cs b/Projects/Server/Party.cs index 64b30e5c7..32ef39a01 100644 --- a/Projects/Server/Party.cs +++ b/Projects/Server/Party.cs @@ -1,15 +1,14 @@ -namespace Server -{ - public abstract class PartyCommands - { - public static PartyCommands Handler { get; set; } +namespace Server; - public abstract void OnAdd(Mobile from); - public abstract void OnRemove(Mobile from, Mobile target); - public abstract void OnPrivateMessage(Mobile from, Mobile target, string text); - public abstract void OnPublicMessage(Mobile from, string text); - public abstract void OnSetCanLoot(Mobile from, bool canLoot); - public abstract void OnAccept(Mobile from, Mobile leader); - public abstract void OnDecline(Mobile from, Mobile leader); - } +public abstract class PartyCommands +{ + public static PartyCommands Handler { get; set; } + + public abstract void OnAdd(Mobile from); + public abstract void OnRemove(Mobile from, Mobile target); + public abstract void OnPrivateMessage(Mobile from, Mobile target, string text); + public abstract void OnPublicMessage(Mobile from, string text); + public abstract void OnSetCanLoot(Mobile from, bool canLoot); + public abstract void OnAccept(Mobile from, Mobile leader); + public abstract void OnDecline(Mobile from, Mobile leader); } diff --git a/Projects/Server/Poison.cs b/Projects/Server/Poison.cs index f309370a5..f97d15467 100644 --- a/Projects/Server/Poison.cs +++ b/Projects/Server/Poison.cs @@ -1,80 +1,79 @@ using System; using System.Collections.Generic; -namespace Server +namespace Server; + +[Parsable] +public abstract class Poison { - [Parsable] - public abstract class Poison + /*public abstract TimeSpan Interval{ get; } + public abstract TimeSpan Duration{ get; }*/ + public abstract string Name { get; } + public abstract int Level { get; } + + public static Poison Lesser => GetPoison("Lesser"); + public static Poison Regular => GetPoison("Regular"); + public static Poison Greater => GetPoison("Greater"); + public static Poison Deadly => GetPoison("Deadly"); + public static Poison Lethal => GetPoison("Lethal"); + + public static List Poisons { get; } = new(); + + public abstract Timer ConstructTimer(Mobile m); + /*public abstract void OnDamage( Mobile m, ref object state );*/ + + public override string ToString() => Name; + + public static void Register(Poison reg) { - /*public abstract TimeSpan Interval{ get; } - public abstract TimeSpan Duration{ get; }*/ - public abstract string Name { get; } - public abstract int Level { get; } + var regName = reg.Name.ToLower(); - public static Poison Lesser => GetPoison("Lesser"); - public static Poison Regular => GetPoison("Regular"); - public static Poison Greater => GetPoison("Greater"); - public static Poison Deadly => GetPoison("Deadly"); - public static Poison Lethal => GetPoison("Lethal"); - - public static List Poisons { get; } = new(); - - public abstract Timer ConstructTimer(Mobile m); - /*public abstract void OnDamage( Mobile m, ref object state );*/ - - public override string ToString() => Name; - - public static void Register(Poison reg) + for (var i = 0; i < Poisons.Count; i++) { - var regName = reg.Name.ToLower(); - - for (var i = 0; i < Poisons.Count; i++) + if (reg.Level == Poisons[i].Level) { - if (reg.Level == Poisons[i].Level) - { - throw new Exception("A poison with that level already exists."); - } - - if (regName == Poisons[i].Name.ToLower()) - { - throw new Exception("A poison with that name already exists."); - } + throw new Exception("A poison with that level already exists."); } - Poisons.Add(reg); - } - - public static Poison Parse(string value) => - (int.TryParse(value, out var plevel) ? GetPoison(plevel) : null) ?? GetPoison(value); - - public static Poison GetPoison(int level) - { - for (var i = 0; i < Poisons.Count; ++i) + if (regName == Poisons[i].Name.ToLower()) { - var p = Poisons[i]; - - if (p.Level == level) - { - return p; - } + throw new Exception("A poison with that name already exists."); } - - return null; } - public static Poison GetPoison(string name) + Poisons.Add(reg); + } + + public static Poison Parse(string value) => + (int.TryParse(value, out var plevel) ? GetPoison(plevel) : null) ?? GetPoison(value); + + public static Poison GetPoison(int level) + { + for (var i = 0; i < Poisons.Count; ++i) { - for (var i = 0; i < Poisons.Count; ++i) + var p = Poisons[i]; + + if (p.Level == level) { - var p = Poisons[i]; - - if (Utility.InsensitiveCompare(p.Name, name) == 0) - { - return p; - } + return p; } - - return null; } + + return null; + } + + public static Poison GetPoison(string name) + { + for (var i = 0; i < Poisons.Count; ++i) + { + var p = Poisons[i]; + + if (Utility.InsensitiveCompare(p.Name, name) == 0) + { + return p; + } + } + + return null; } } diff --git a/Projects/Server/Prompt.cs b/Projects/Server/Prompt.cs index eb91edf31..639719e0e 100644 --- a/Projects/Server/Prompt.cs +++ b/Projects/Server/Prompt.cs @@ -1,25 +1,24 @@ -namespace Server.Prompts +namespace Server.Prompts; + +public abstract class Prompt { - public abstract class Prompt + private static int m_Serials; + + protected Prompt() { - private static int m_Serials; - - protected Prompt() + do { - do - { - Serial = ++m_Serials; - } while (Serial == 0); - } + Serial = ++m_Serials; + } while (Serial == 0); + } - public int Serial { get; } + public int Serial { get; } - public virtual void OnCancel(Mobile from) - { - } + public virtual void OnCancel(Mobile from) + { + } - public virtual void OnResponse(Mobile from, string text) - { - } + public virtual void OnResponse(Mobile from, string text) + { } } diff --git a/Projects/Server/Race.cs b/Projects/Server/Race.cs index d0e5c4cc9..d0c3e49af 100644 --- a/Projects/Server/Race.cs +++ b/Projects/Server/Race.cs @@ -2,156 +2,155 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +[Parsable] +public abstract class Race { - [Parsable] - public abstract class Race + private static string[] m_RaceNames; + private static Race[] m_RaceValues; + + protected Race( + int raceID, int raceIndex, string name, string pluralName, int maleBody, int femaleBody, + int maleGhostBody, int femaleGhostBody, Expansion requiredExpansion + ) { - private static string[] m_RaceNames; - private static Race[] m_RaceValues; + RaceID = raceID; + RaceIndex = raceIndex; + RaceFlag = 1 << raceIndex; - protected Race( - int raceID, int raceIndex, string name, string pluralName, int maleBody, int femaleBody, - int maleGhostBody, int femaleGhostBody, Expansion requiredExpansion - ) - { - RaceID = raceID; - RaceIndex = raceIndex; - RaceFlag = 1 << raceIndex; + Name = name; - Name = name; + MaleBody = maleBody; + FemaleBody = femaleBody; + MaleGhostBody = maleGhostBody; + FemaleGhostBody = femaleGhostBody; - MaleBody = maleBody; - FemaleBody = femaleBody; - MaleGhostBody = maleGhostBody; - FemaleGhostBody = femaleGhostBody; - - RequiredExpansion = requiredExpansion; - PluralName = pluralName; - } - - public static Race[] Races { get; } = new Race[0x100]; - - public static Race DefaultRace => Races[0]; - - public static Race Human => Races[0]; - public static Race Elf => Races[1]; - public static Race Gargoyle => Races[2]; - - public static List AllRaces { get; } = new(); - - public const int AllowAllRaces = 0x7; // Race.Human.RaceFlag | Race.Elf.RaceFlag | Race.Gargoyle.RaceFlag - public const int AllowHumanOrElves = 0x3; // Race.Human.RaceFlag | Race.Elf.RaceFlag - public const int AllowElvesOnly = 0x2; // Race.Elf.RaceFlag - public const int AllowGargoylesOnly = 0x4; // Race.Gargoyle.RaceFlag - - public Expansion RequiredExpansion { get; } - - public int MaleBody { get; } - - public int MaleGhostBody { get; } - - public int FemaleBody { get; } - - public int FemaleGhostBody { get; } - - public int RaceID { get; } - - public int RaceIndex { get; } - - public int RaceFlag { get; } - - public string Name { get; set; } - - public string PluralName { get; set; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsAllowedRace(Race race, int allowedRaceFlags) => (allowedRaceFlags & race.RaceFlag) != 0; - - public static string[] GetRaceNames() - { - CheckNamesAndValues(); - return m_RaceNames; - } - - public static Race[] GetRaceValues() - { - CheckNamesAndValues(); - return m_RaceValues; - } - - public static Race Parse(string value) - { - CheckNamesAndValues(); - - for (var i = 0; i < m_RaceNames.Length; ++i) - { - if (m_RaceNames[i].InsensitiveEquals(value)) - { - return m_RaceValues[i]; - } - } - - if (int.TryParse(value, out var index) && index >= 0 && index < Races.Length && - Races[index] != null) - { - return Races[index]; - } - - throw new ArgumentException("Invalid race name"); - } - - private static void CheckNamesAndValues() - { - if (m_RaceNames?.Length == AllRaces.Count) - { - return; - } - - m_RaceNames = new string[AllRaces.Count]; - m_RaceValues = new Race[AllRaces.Count]; - - for (var i = 0; i < AllRaces.Count; ++i) - { - var race = AllRaces[i]; - - m_RaceNames[i] = race.Name; - m_RaceValues[i] = race; - } - } - - public override string ToString() => Name; - - public virtual bool ValidateHair(Mobile m, int itemID) => ValidateHair(m.Female, itemID); - - public abstract bool ValidateHair(bool female, int itemID); - - public virtual int RandomHair(Mobile m) => RandomHair(m.Female); - - public abstract int RandomHair(bool female); - - public virtual bool ValidateFacialHair(Mobile m, int itemID) => ValidateFacialHair(m.Female, itemID); - - public abstract bool ValidateFacialHair(bool female, int itemID); - - public virtual int RandomFacialHair(Mobile m) => RandomFacialHair(m.Female); - - public abstract int RandomFacialHair(bool female); // For the *ahem* bearded ladies - - public abstract int ClipSkinHue(int hue); - public abstract int RandomSkinHue(); - - public abstract int ClipHairHue(int hue); - public abstract int RandomHairHue(); - - public virtual int Body(Mobile m) => m.Alive ? AliveBody(m.Female) : GhostBody(m.Female); - - public virtual int AliveBody(Mobile m) => AliveBody(m.Female); - - public virtual int AliveBody(bool female) => female ? FemaleBody : MaleBody; - - public virtual int GhostBody(Mobile m) => GhostBody(m.Female); - - public virtual int GhostBody(bool female) => female ? FemaleGhostBody : MaleGhostBody; + RequiredExpansion = requiredExpansion; + PluralName = pluralName; } + + public static Race[] Races { get; } = new Race[0x100]; + + public static Race DefaultRace => Races[0]; + + public static Race Human => Races[0]; + public static Race Elf => Races[1]; + public static Race Gargoyle => Races[2]; + + public static List AllRaces { get; } = new(); + + public const int AllowAllRaces = 0x7; // Race.Human.RaceFlag | Race.Elf.RaceFlag | Race.Gargoyle.RaceFlag + public const int AllowHumanOrElves = 0x3; // Race.Human.RaceFlag | Race.Elf.RaceFlag + public const int AllowElvesOnly = 0x2; // Race.Elf.RaceFlag + public const int AllowGargoylesOnly = 0x4; // Race.Gargoyle.RaceFlag + + public Expansion RequiredExpansion { get; } + + public int MaleBody { get; } + + public int MaleGhostBody { get; } + + public int FemaleBody { get; } + + public int FemaleGhostBody { get; } + + public int RaceID { get; } + + public int RaceIndex { get; } + + public int RaceFlag { get; } + + public string Name { get; set; } + + public string PluralName { get; set; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAllowedRace(Race race, int allowedRaceFlags) => (allowedRaceFlags & race.RaceFlag) != 0; + + public static string[] GetRaceNames() + { + CheckNamesAndValues(); + return m_RaceNames; + } + + public static Race[] GetRaceValues() + { + CheckNamesAndValues(); + return m_RaceValues; + } + + public static Race Parse(string value) + { + CheckNamesAndValues(); + + for (var i = 0; i < m_RaceNames.Length; ++i) + { + if (m_RaceNames[i].InsensitiveEquals(value)) + { + return m_RaceValues[i]; + } + } + + if (int.TryParse(value, out var index) && index >= 0 && index < Races.Length && + Races[index] != null) + { + return Races[index]; + } + + throw new ArgumentException("Invalid race name"); + } + + private static void CheckNamesAndValues() + { + if (m_RaceNames?.Length == AllRaces.Count) + { + return; + } + + m_RaceNames = new string[AllRaces.Count]; + m_RaceValues = new Race[AllRaces.Count]; + + for (var i = 0; i < AllRaces.Count; ++i) + { + var race = AllRaces[i]; + + m_RaceNames[i] = race.Name; + m_RaceValues[i] = race; + } + } + + public override string ToString() => Name; + + public virtual bool ValidateHair(Mobile m, int itemID) => ValidateHair(m.Female, itemID); + + public abstract bool ValidateHair(bool female, int itemID); + + public virtual int RandomHair(Mobile m) => RandomHair(m.Female); + + public abstract int RandomHair(bool female); + + public virtual bool ValidateFacialHair(Mobile m, int itemID) => ValidateFacialHair(m.Female, itemID); + + public abstract bool ValidateFacialHair(bool female, int itemID); + + public virtual int RandomFacialHair(Mobile m) => RandomFacialHair(m.Female); + + public abstract int RandomFacialHair(bool female); // For the *ahem* bearded ladies + + public abstract int ClipSkinHue(int hue); + public abstract int RandomSkinHue(); + + public abstract int ClipHairHue(int hue); + public abstract int RandomHairHue(); + + public virtual int Body(Mobile m) => m.Alive ? AliveBody(m.Female) : GhostBody(m.Female); + + public virtual int AliveBody(Mobile m) => AliveBody(m.Female); + + public virtual int AliveBody(bool female) => female ? FemaleBody : MaleBody; + + public virtual int GhostBody(Mobile m) => GhostBody(m.Female); + + public virtual int GhostBody(bool female) => female ? FemaleGhostBody : MaleGhostBody; } diff --git a/Projects/Server/Random/BaseRandomSource.cs b/Projects/Server/Random/BaseRandomSource.cs index a50197598..19d9efc0c 100644 --- a/Projects/Server/Random/BaseRandomSource.cs +++ b/Projects/Server/Random/BaseRandomSource.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BaseRandomSource.cs * * * @@ -18,174 +18,173 @@ using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; -namespace Server.Random +namespace Server.Random; + +public abstract class BaseRandomSource : IRandomSource { - public abstract class BaseRandomSource : IRandomSource + private const double INCR_DOUBLE = 1.0 / (1UL << 53); + private const float INCR_FLOAT = 1f / (1U << 24); + + public abstract ulong NextULong(); + public abstract void NextBytes(Span buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next() { - private const double INCR_DOUBLE = 1.0 / (1UL << 53); - private const float INCR_FLOAT = 1f / (1U << 24); - - public abstract ulong NextULong(); - public abstract void NextBytes(Span buffer); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Next() + ulong rtn; + do { - ulong rtn; - do - { - rtn = NextULong() >> 33; - } while (rtn == 0x7fff_ffffUL); + rtn = NextULong() >> 33; + } while (rtn == 0x7fff_ffffUL); - return (int)rtn; + return (int)rtn; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next(int count) + { + Debug.Assert(count != 0, $"{nameof(count)} must not be 0"); + + if (count is -1 or 0 or 1) + { + return 0; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Next(int count) - { - Debug.Assert(count != 0, $"{nameof(count)} must not be 0"); + var negative = count < 0; - if (count is -1 or 0 or 1) + var max = negative ? -count : count; + + var bits = Log2((uint)max); + + int x; + do + { + x = (int)(NextULong() >> (64 - bits)); + } while (x >= max); + + return negative ? -x : x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next(int minValue, int count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next(uint count) + { + Debug.Assert(count != 0, $"{nameof(count)} must not be 0"); + + if (count is 0 or 1) + { + return 0; + } + + var bits = Log2(count); + + uint x; + do + { + x = (uint)(NextULong() >> (64 - bits)); + } while (x >= count); + + return x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next(uint minValue, uint count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Next(long count) + { + Debug.Assert(count != 0, $"{nameof(count)} must not be 0"); + + if (count is -1 or 0 or 1) + { + return 0; + } + + var negative = count < 0; + + var max = negative ? -count : count; + + var bits = Log2((ulong)max); + + long x; + do + { + x = (long)(NextULong() >> (64 - bits)); + } while (x >= max); + + return negative ? -x : x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Next(long minValue, long count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDouble() => (NextULong() >> 11) * INCR_DOUBLE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int NextInt() => (int)(NextULong() >> 33); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint NextUInt() => (uint)NextULong(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool NextBool() => (NextULong() & 0x8000000000000000) != 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte NextByte() => (byte)(NextULong() >> 56); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float NextFloat() => (NextULong() >> 40) * INCR_FLOAT; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float NextFloatNonZero() => NextFloat() + INCR_FLOAT; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDoubleNonZero() => NextDouble() + INCR_DOUBLE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDoubleHighRes() + { + var exponent = -64; + ulong significand; + int shift; + + while ((significand = NextULong()) == 0) + { + exponent -= 64; + + if (exponent < -1074) { return 0; } - - var negative = count < 0; - - var max = negative ? -count : count; - - var bits = Log2((uint)max); - - int x; - do - { - x = (int)(NextULong() >> (64 - bits)); - } while (x >= max); - - return negative ? -x : x; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Next(int minValue, int count) => minValue + Next(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Next(uint count) + shift = BitOperations.LeadingZeroCount(significand); + if (shift != 0) { - Debug.Assert(count != 0, $"{nameof(count)} must not be 0"); - - if (count is 0 or 1) - { - return 0; - } - - var bits = Log2(count); - - uint x; - do - { - x = (uint)(NextULong() >> (64 - bits)); - } while (x >= count); - - return x; + exponent -= shift; + significand <<= shift; + significand |= NextULong() >> (64 - shift); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Next(uint minValue, uint count) => minValue + Next(count); + significand |= 1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long Next(long count) - { - Debug.Assert(count != 0, $"{nameof(count)} must not be 0"); + return significand * Math.Pow(2, exponent); + } - if (count is -1 or 0 or 1) - { - return 0; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Log2(uint v) + { + var exp = BitOperations.Log2(v); + return v == 1 << exp ? exp : exp + 1; + } - var negative = count < 0; - - var max = negative ? -count : count; - - var bits = Log2((ulong)max); - - long x; - do - { - x = (long)(NextULong() >> (64 - bits)); - } while (x >= max); - - return negative ? -x : x; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long Next(long minValue, long count) => minValue + Next(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double NextDouble() => (NextULong() >> 11) * INCR_DOUBLE; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int NextInt() => (int)(NextULong() >> 33); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint NextUInt() => (uint)NextULong(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool NextBool() => (NextULong() & 0x8000000000000000) != 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte NextByte() => (byte)(NextULong() >> 56); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float NextFloat() => (NextULong() >> 40) * INCR_FLOAT; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float NextFloatNonZero() => NextFloat() + INCR_FLOAT; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double NextDoubleNonZero() => NextDouble() + INCR_DOUBLE; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double NextDoubleHighRes() - { - var exponent = -64; - ulong significand; - int shift; - - while ((significand = NextULong()) == 0) - { - exponent -= 64; - - if (exponent < -1074) - { - return 0; - } - } - - shift = BitOperations.LeadingZeroCount(significand); - if (shift != 0) - { - exponent -= shift; - significand <<= shift; - significand |= NextULong() >> (64 - shift); - } - - significand |= 1; - - return significand * Math.Pow(2, exponent); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Log2(uint v) - { - var exp = BitOperations.Log2(v); - return v == 1 << exp ? exp : exp + 1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Log2(ulong v) - { - var exp = BitOperations.Log2(v); - return v == 1UL << exp ? exp : exp + 1; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Log2(ulong v) + { + var exp = BitOperations.Log2(v); + return v == 1UL << exp ? exp : exp + 1; } } diff --git a/Projects/Server/Random/IRandomSource.cs b/Projects/Server/Random/IRandomSource.cs index 3d590b79e..f4495e75b 100644 --- a/Projects/Server/Random/IRandomSource.cs +++ b/Projects/Server/Random/IRandomSource.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IRandomSource.cs * * * @@ -15,27 +15,26 @@ using System; -namespace Server.Random +namespace Server.Random; + +public interface IRandomSource { - public interface IRandomSource - { - int Next(); - int Next(int maxValue); - int Next(int minValue, int count); - uint Next(uint maxValue); - uint Next(uint minValue, uint count); - long Next(long maxValue); - long Next(long minValue, long count); - double NextDouble(); - void NextBytes(Span buffer); - int NextInt(); - uint NextUInt(); - ulong NextULong(); - bool NextBool(); - byte NextByte(); - float NextFloat(); - float NextFloatNonZero(); - double NextDoubleNonZero(); - double NextDoubleHighRes(); - } + int Next(); + int Next(int maxValue); + int Next(int minValue, int count); + uint Next(uint maxValue); + uint Next(uint minValue, uint count); + long Next(long maxValue); + long Next(long minValue, long count); + double NextDouble(); + void NextBytes(Span buffer); + int NextInt(); + uint NextUInt(); + ulong NextULong(); + bool NextBool(); + byte NextByte(); + float NextFloat(); + float NextFloatNonZero(); + double NextDoubleNonZero(); + double NextDoubleHighRes(); } diff --git a/Projects/Server/Random/RandomSources.cs b/Projects/Server/Random/RandomSources.cs index 49f8a6188..342e236ff 100644 --- a/Projects/Server/Random/RandomSources.cs +++ b/Projects/Server/Random/RandomSources.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: RandomSources.cs * * * @@ -13,18 +13,17 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server.Random +namespace Server.Random; + +public static class RandomSources { - public static class RandomSources - { - private static IRandomSource _source; - private static IRandomSource _secureSource; + private static IRandomSource _source; + private static IRandomSource _secureSource; - public static IRandomSource Source => _source ??= new Xoshiro256PlusPlus(); - public static IRandomSource SecureSource => _secureSource ??= new SecureRandom(); + public static IRandomSource Source => _source ??= new Xoshiro256PlusPlus(); + public static IRandomSource SecureSource => _secureSource ??= new SecureRandom(); - public static void SetRng(IRandomSource newSource) => _source = newSource; + public static void SetRng(IRandomSource newSource) => _source = newSource; - public static void SetSecureRng(IRandomSource newSource) => _secureSource = newSource; - } + public static void SetSecureRng(IRandomSource newSource) => _secureSource = newSource; } diff --git a/Projects/Server/Random/SecureRandom.cs b/Projects/Server/Random/SecureRandom.cs index 30006b871..03309a873 100644 --- a/Projects/Server/Random/SecureRandom.cs +++ b/Projects/Server/Random/SecureRandom.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SecureRandom.cs * * * @@ -19,23 +19,22 @@ using System.Runtime.CompilerServices; using System.Security.Cryptography; using Server.Random; -namespace Server +namespace Server; + +public class SecureRandom : BaseRandomSource { - public class SecureRandom : BaseRandomSource + private RandomNumberGenerator m_Random; + + public RandomNumberGenerator Generator => m_Random ??= RandomNumberGenerator.Create(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override ulong NextULong() { - private RandomNumberGenerator m_Random; - - public RandomNumberGenerator Generator => m_Random ??= RandomNumberGenerator.Create(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override ulong NextULong() - { - Span buffer = stackalloc byte[sizeof(ulong)]; - NextBytes(buffer); - return BinaryPrimitives.ReadUInt64BigEndian(buffer); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void NextBytes(Span buffer) => Generator.GetBytes(buffer); + Span buffer = stackalloc byte[sizeof(ulong)]; + NextBytes(buffer); + return BinaryPrimitives.ReadUInt64BigEndian(buffer); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void NextBytes(Span buffer) => Generator.GetBytes(buffer); } diff --git a/Projects/Server/Random/Xoshiro256PlusPlus.cs b/Projects/Server/Random/Xoshiro256PlusPlus.cs index 5f0f0eb9c..205196f66 100644 --- a/Projects/Server/Random/Xoshiro256PlusPlus.cs +++ b/Projects/Server/Random/Xoshiro256PlusPlus.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Xoshiro256PlusPlus.cs * * * @@ -16,109 +16,88 @@ using System; using System.Runtime.CompilerServices; -namespace Server.Random +namespace Server.Random; + +public class Xoshiro256PlusPlus : BaseRandomSource { - public class Xoshiro256PlusPlus : BaseRandomSource + private static readonly ulong[] JUMP = + { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + + private static readonly ulong[] LONG_JUMP = + { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + + private ulong _s0, _s1, _s2, _s3; + + public Xoshiro256PlusPlus() : this((ulong)Environment.TickCount64) { - private static readonly ulong[] JUMP = - { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + } - private static readonly ulong[] LONG_JUMP = - { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + public Xoshiro256PlusPlus(ulong seed) + { + var mix = new SplitMix64(seed); + var state = new ulong[4]; + mix.FillArray(state); + _s0 = state[0]; + _s1 = state[1]; + _s2 = state[2]; + _s3 = state[3]; + } - private ulong _s0, _s1, _s2, _s3; + private Xoshiro256PlusPlus(ulong s0, ulong s1, ulong s2, ulong s3) + { + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; + } - public Xoshiro256PlusPlus() : this((ulong)Environment.TickCount64) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override ulong NextULong() + { + var r1 = (_s1 << 2) + _s1; + var r2 = (r1 << 7) | (r1 >> 57); + var rslt = (r2 << 3) + r2; + + var t = _s1 << 17; + + _s2 ^= _s0; + _s3 ^= _s1; + _s1 ^= _s2; + _s0 ^= _s3; + + _s2 ^= t; + + _s3 = (_s3 << 45) | (_s3 >> 19); + + return rslt; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override unsafe void NextBytes(Span b) + { + if (b.Length == 0) { + return; } - public Xoshiro256PlusPlus(ulong seed) + var s0 = _s0; + var s1 = _s1; + var s2 = _s2; + var s3 = _s3; + + var i = 0; + + fixed (byte* pBuffer = b) { - var mix = new SplitMix64(seed); - var state = new ulong[4]; - mix.FillArray(state); - _s0 = state[0]; - _s1 = state[1]; - _s2 = state[2]; - _s3 = state[3]; - } + var pULong = (ulong*)pBuffer; - private Xoshiro256PlusPlus(ulong s0, ulong s1, ulong s2, ulong s3) - { - _s0 = s0; - _s1 = s1; - _s2 = s2; - _s3 = s3; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override ulong NextULong() - { - var r1 = (_s1 << 2) + _s1; - var r2 = (r1 << 7) | (r1 >> 57); - var rslt = (r2 << 3) + r2; - - var t = _s1 << 17; - - _s2 ^= _s0; - _s3 ^= _s1; - _s1 ^= _s2; - _s0 ^= _s3; - - _s2 ^= t; - - _s3 = (_s3 << 45) | (_s3 >> 19); - - return rslt; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override unsafe void NextBytes(Span b) - { - if (b.Length == 0) - { - return; - } - - var s0 = _s0; - var s1 = _s1; - var s2 = _s2; - var s3 = _s3; - - var i = 0; - - fixed (byte* pBuffer = b) - { - var pULong = (ulong*)pBuffer; - - for (var bound = b.Length / sizeof(ulong); i < bound; i++) - { - var r1 = (s1 << 2) + s1; - var r2 = (r1 << 7) | (r1 >> 57); - pULong[i] = (r2 << 3) + r2; - - var t = s1 << 17; - s2 ^= s0; - s3 ^= s1; - s1 ^= s2; - s0 ^= s3; - - s2 ^= t; - - s3 = (s3 << 45) | (s3 >> 19); - } - } - - i *= 8; - - if (i < b.Length) + for (var bound = b.Length / sizeof(ulong); i < bound; i++) { var r1 = (s1 << 2) + s1; var r2 = (r1 << 7) | (r1 >> 57); - var rslt = (r2 << 3) + r2; + pULong[i] = (r2 << 3) + r2; var t = s1 << 17; - s2 ^= s0; s3 ^= s1; s1 ^= s2; @@ -127,90 +106,110 @@ namespace Server.Random s2 ^= t; s3 = (s3 << 45) | (s3 >> 19); - - while (i < b.Length) - { - b[i++] = (byte)rslt; - rslt >>= 8; - } } - - _s0 = s0; - _s1 = s1; - _s2 = s2; - _s3 = s3; } - public void Jump() => Jump(JUMP); + i *= 8; - public void LongJump() => Jump(LONG_JUMP); - - private void Jump(in ulong[] jumps) + if (i < b.Length) { - ulong s0 = 0; - ulong s1 = 0; - ulong s2 = 0; - ulong s3 = 0; + var r1 = (s1 << 2) + s1; + var r2 = (r1 << 7) | (r1 >> 57); + var rslt = (r2 << 3) + r2; - for (var i = 0; i < jumps.Length; i++) + var t = s1 << 17; + + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + + s2 ^= t; + + s3 = (s3 << 45) | (s3 >> 19); + + while (i < b.Length) { - for (var b = 0; b < 64; b++) - { - if ((jumps[i] & (1ul << b)) != 0) - { - s0 ^= _s0; - s1 ^= _s1; - s2 ^= _s2; - s3 ^= _s3; - } - - NextULong(); - } + b[i++] = (byte)rslt; + rslt >>= 8; } - - _s0 = s0; - _s1 = s1; - _s2 = s2; - _s3 = s3; } - public Xoshiro256PlusPlus Split() - { - var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); - rng.Jump(); - return rng; - } - - public Xoshiro256PlusPlus LongSplit() - { - var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); - rng.LongJump(); - return rng; - } + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; } - public class SplitMix64 + public void Jump() => Jump(JUMP); + + public void LongJump() => Jump(LONG_JUMP); + + private void Jump(in ulong[] jumps) { - private ulong x; + ulong s0 = 0; + ulong s1 = 0; + ulong s2 = 0; + ulong s3 = 0; - public SplitMix64(ulong seed) => x = seed; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong Next() + for (var i = 0; i < jumps.Length; i++) { - var z = x += 0x9e3779b97f4a7c15; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); + for (var b = 0; b < 64; b++) + { + if ((jumps[i] & (1ul << b)) != 0) + { + s0 ^= _s0; + s1 ^= _s1; + s2 ^= _s2; + s3 ^= _s3; + } + + NextULong(); + } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FillArray(ulong[] arr) + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; + } + + public Xoshiro256PlusPlus Split() + { + var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); + rng.Jump(); + return rng; + } + + public Xoshiro256PlusPlus LongSplit() + { + var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); + rng.LongJump(); + return rng; + } +} + +public class SplitMix64 +{ + private ulong x; + + public SplitMix64(ulong seed) => x = seed; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong Next() + { + var z = x += 0x9e3779b97f4a7c15; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FillArray(ulong[] arr) + { + for (var i = 0; i < arr.Length; i++) { - for (var i = 0; i < arr.Length; i++) - { - arr[i] = Next(); - } + arr[i] = Next(); } } } diff --git a/Projects/Server/Regions/Region.cs b/Projects/Server/Regions/Region.cs index 6f1b903e5..a177751f5 100644 --- a/Projects/Server/Regions/Region.cs +++ b/Projects/Server/Regions/Region.cs @@ -6,833 +6,832 @@ using Server.Logging; using Server.Network; using Server.Targeting; -namespace Server +namespace Server; + +public enum MusicName { - public enum MusicName + Invalid = -1, + OldUlt01 = 0, + Create1, + DragFlit, + OldUlt02, + OldUlt03, + OldUlt04, + OldUlt05, + OldUlt06, + Stones2, + Britain1, + Britain2, + Bucsden, + Jhelom, + LBCastle, + Linelle, + Magincia, + Minoc, + Ocllo, + Samlethe, + Serpents, + Skarabra, + Trinsic, + Vesper, + Wind, + Yew, + Cave01, + Dungeon9, + Forest_a, + InTown01, + Jungle_a, + Mountn_a, + Plains_a, + Sailing, + Swamp_a, + Tavern01, + Tavern02, + Tavern03, + Tavern04, + Combat1, + Combat2, + Combat3, + Approach, + Death, + Victory, + BTCastle, + Nujelm, + Dungeon2, + Cove, + Moonglow, + Zento, + TokunoDungeon, + Taiko, + DreadHornArea, + ElfCity, + GrizzleDungeon, + MelisandesLair, + ParoxysmusLair, + GwennoConversation, + GoodEndGame, + GoodVsEvil, + GreatEarthSerpents, + Humanoids_U9, + MinocNegative, + Paws, + SelimsBar, + SerpentIsleCombat_U7, + ValoriaShips, + NoMusic = 0x1FFF +} + +public class Region : IComparable +{ + private static readonly ILogger logger = LogFactory.GetLogger(typeof(Region)); + + public const int DefaultPriority = 50; + public const int MinZ = sbyte.MinValue; + public const int MaxZ = sbyte.MaxValue + 1; + + public Region(string name, Map map, int priority, params Rectangle2D[] area) : this( + name, + map, + priority, + ConvertTo3D(area) + ) { - Invalid = -1, - OldUlt01 = 0, - Create1, - DragFlit, - OldUlt02, - OldUlt03, - OldUlt04, - OldUlt05, - OldUlt06, - Stones2, - Britain1, - Britain2, - Bucsden, - Jhelom, - LBCastle, - Linelle, - Magincia, - Minoc, - Ocllo, - Samlethe, - Serpents, - Skarabra, - Trinsic, - Vesper, - Wind, - Yew, - Cave01, - Dungeon9, - Forest_a, - InTown01, - Jungle_a, - Mountn_a, - Plains_a, - Sailing, - Swamp_a, - Tavern01, - Tavern02, - Tavern03, - Tavern04, - Combat1, - Combat2, - Combat3, - Approach, - Death, - Victory, - BTCastle, - Nujelm, - Dungeon2, - Cove, - Moonglow, - Zento, - TokunoDungeon, - Taiko, - DreadHornArea, - ElfCity, - GrizzleDungeon, - MelisandesLair, - ParoxysmusLair, - GwennoConversation, - GoodEndGame, - GoodVsEvil, - GreatEarthSerpents, - Humanoids_U9, - MinocNegative, - Paws, - SelimsBar, - SerpentIsleCombat_U7, - ValoriaShips, - NoMusic = 0x1FFF } - public class Region : IComparable + public Region(string name, Map map, int priority, params Rectangle3D[] area) : this(name, map, null, area) => + Priority = priority; + + public Region(string name, Map map, Region parent, params Rectangle2D[] area) : this( + name, + map, + parent, + ConvertTo3D(area) + ) { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(Region)); + } - public const int DefaultPriority = 50; - public const int MinZ = sbyte.MinValue; - public const int MaxZ = sbyte.MaxValue + 1; + public Region(string name, Map map, Region parent, params Rectangle3D[] area) + { + Name = name; + Map = map; + Parent = parent; + Area = area; + Dynamic = true; + Music = DefaultMusic; - public Region(string name, Map map, int priority, params Rectangle2D[] area) : this( - name, - map, - priority, - ConvertTo3D(area) - ) + if (Parent == null) { + ChildLevel = 0; + Priority = DefaultPriority; + } + else + { + ChildLevel = Parent.ChildLevel + 1; + Priority = Parent.Priority; + } + } + + public Region(DynamicJson json, JsonSerializerOptions options) + { + Map = json.GetProperty("map", options, out Map map) ? map : null; + Parent = json.GetProperty("parent", options, out string parent) ? Find(parent, Map) : null; + Name = json.GetProperty("name", options, out string name) ? name : null; + + Dynamic = false; + + if (Parent == null) + { + ChildLevel = 0; + Priority = DefaultPriority; + } + else + { + ChildLevel = Parent.ChildLevel + 1; + Priority = Parent.Priority; } - public Region(string name, Map map, int priority, params Rectangle3D[] area) : this(name, map, null, area) => - Priority = priority; + Priority = json.GetProperty("priority", options, out int priority) ? priority : Priority; - public Region(string name, Map map, Region parent, params Rectangle2D[] area) : this( - name, - map, - parent, - ConvertTo3D(area) - ) + Area = json.GetProperty("rects", options, out List rects) + ? rects.ToArray() + : Array.Empty(); + + if (Area.Length == 0) { + logger.Debug("Empty area for region '{Region}'", this); } - public Region(string name, Map map, Region parent, params Rectangle3D[] area) + if (json.GetProperty("go", options, out Point3D go)) { - Name = name; - Map = map; - Parent = parent; - Area = area; - Dynamic = true; - Music = DefaultMusic; + GoLocation = go; + } + else if (Area.Length > 0) + { + var start = Area[0].Start; + var end = Area[0].End; - if (Parent == null) - { - ChildLevel = 0; - Priority = DefaultPriority; - } - else - { - ChildLevel = Parent.ChildLevel + 1; - Priority = Parent.Priority; - } + var x = start.X + (end.X - start.X) / 2; + var y = start.Y + (end.Y - start.Y) / 2; + + GoLocation = new Point3D(x, y, Map?.GetAverageZ(x, y) ?? start.Z + (end.Z - start.Z) / 2); } - public Region(DynamicJson json, JsonSerializerOptions options) + Music = json.GetEnumProperty("music", options, out MusicName music) ? music : DefaultMusic; + } + + public static List Regions { get; } = new(); + + public static TimeSpan StaffLogoutDelay { get; set; } = TimeSpan.Zero; + + public static TimeSpan DefaultLogoutDelay { get; set; } = TimeSpan.FromMinutes(5.0); + + public string Name { get; } + + public Map Map { get; } + + public Region Parent { get; } + + public List Children { get; } = new(); + + public Rectangle3D[] Area { get; } + + public Sector[] Sectors { get; private set; } + + public bool Dynamic { get; } + + public int Priority { get; } + + public int ChildLevel { get; } + + public bool Registered { get; private set; } + + public Point3D GoLocation { get; set; } + + public MusicName Music { get; set; } + + public bool IsDefault => Map.DefaultRegion == this; + public virtual MusicName DefaultMusic => Parent?.Music ?? MusicName.Invalid; + + public int CompareTo(Region reg) + { + if (reg == null) { - Map = json.GetProperty("map", options, out Map map) ? map : null; - Parent = json.GetProperty("parent", options, out string parent) ? Find(parent, Map) : null; - Name = json.GetProperty("name", options, out string name) ? name : null; - - Dynamic = false; - - if (Parent == null) - { - ChildLevel = 0; - Priority = DefaultPriority; - } - else - { - ChildLevel = Parent.ChildLevel + 1; - Priority = Parent.Priority; - } - - Priority = json.GetProperty("priority", options, out int priority) ? priority : Priority; - - Area = json.GetProperty("rects", options, out List rects) - ? rects.ToArray() - : Array.Empty(); - - if (Area.Length == 0) - { - logger.Debug("Empty area for region '{Region}'", this); - } - - if (json.GetProperty("go", options, out Point3D go)) - { - GoLocation = go; - } - else if (Area.Length > 0) - { - var start = Area[0].Start; - var end = Area[0].End; - - var x = start.X + (end.X - start.X) / 2; - var y = start.Y + (end.Y - start.Y) / 2; - - GoLocation = new Point3D(x, y, Map?.GetAverageZ(x, y) ?? start.Z + (end.Z - start.Z) / 2); - } - - Music = json.GetEnumProperty("music", options, out MusicName music) ? music : DefaultMusic; + return 1; } - public static List Regions { get; } = new(); - - public static TimeSpan StaffLogoutDelay { get; set; } = TimeSpan.Zero; - - public static TimeSpan DefaultLogoutDelay { get; set; } = TimeSpan.FromMinutes(5.0); - - public string Name { get; } - - public Map Map { get; } - - public Region Parent { get; } - - public List Children { get; } = new(); - - public Rectangle3D[] Area { get; } - - public Sector[] Sectors { get; private set; } - - public bool Dynamic { get; } - - public int Priority { get; } - - public int ChildLevel { get; } - - public bool Registered { get; private set; } - - public Point3D GoLocation { get; set; } - - public MusicName Music { get; set; } - - public bool IsDefault => Map.DefaultRegion == this; - public virtual MusicName DefaultMusic => Parent?.Music ?? MusicName.Invalid; - - public int CompareTo(Region reg) + // Dynamic regions go first + if (Dynamic) { - if (reg == null) + if (!reg.Dynamic) { - return 1; + return -1; } - - // Dynamic regions go first - if (Dynamic) - { - if (!reg.Dynamic) - { - return -1; - } - } - else if (reg.Dynamic) - { - return 1; - } - - var regPriority = reg.Priority; - return Priority != regPriority ? reg.Priority - Priority : reg.ChildLevel - ChildLevel; + } + else if (reg.Dynamic) + { + return 1; } - // This is not optimized. Use sparingly - public static Region Find(string name, Map map, bool insensitive = false) + var regPriority = reg.Priority; + return Priority != regPriority ? reg.Priority - Priority : reg.ChildLevel - ChildLevel; + } + + // This is not optimized. Use sparingly + public static Region Find(string name, Map map, bool insensitive = false) + { + if (insensitive) { + name = name.ToLower(); + } + + for (var i = 0; i < Regions.Count; i++) + { + var region = Regions[i]; + if (region.Map != map) + { + continue; + } + + var rName = region.Name; if (insensitive) { - name = name.ToLower(); + rName = rName.ToLower(); } - for (var i = 0; i < Regions.Count; i++) + if (rName == name) { - var region = Regions[i]; - if (region.Map != map) - { - continue; - } - - var rName = region.Name; - if (insensitive) - { - rName = rName.ToLower(); - } - - if (rName == name) - { - return region; - } + return region; } - - return null; } - public static Region Find(Point3D p, Map map) + return null; + } + + public static Region Find(Point3D p, Map map) + { + if (map == null) { - if (map == null) - { - return Map.Internal.DefaultRegion; - } - - var sector = map.GetSector(p); - var list = sector.RegionRects; - - for (var i = 0; i < list.Count; ++i) - { - var regRect = list[i]; - - if (regRect.Contains(p)) - { - return regRect.Region; - } - } - - return map.DefaultRegion; + return Map.Internal.DefaultRegion; } - public static Rectangle3D ConvertTo3D(Rectangle2D rect) => - new(new Point3D(rect.Start, MinZ), new Point3D(rect.End, MaxZ)); + var sector = map.GetSector(p); + var list = sector.RegionRects; - public static Rectangle3D[] ConvertTo3D(Rectangle2D[] rects) + for (var i = 0; i < list.Count; ++i) { - var ret = new Rectangle3D[rects.Length]; + var regRect = list[i]; - for (var i = 0; i < ret.Length; i++) + if (regRect.Contains(p)) { - ret[i] = ConvertTo3D(rects[i]); + return regRect.Region; } - - return ret; } - public void Register() + return map.DefaultRegion; + } + + public static Rectangle3D ConvertTo3D(Rectangle2D rect) => + new(new Point3D(rect.Start, MinZ), new Point3D(rect.End, MaxZ)); + + public static Rectangle3D[] ConvertTo3D(Rectangle2D[] rects) + { + var ret = new Rectangle3D[rects.Length]; + + for (var i = 0; i < ret.Length; i++) { - if (Registered) + ret[i] = ConvertTo3D(rects[i]); + } + + return ret; + } + + public void Register() + { + if (Registered) + { + return; + } + + OnRegister(); + + Registered = true; + + if (Parent != null) + { + Parent.Children.Add(this); + Parent.OnChildAdded(this); + } + + Regions.Add(this); + + Map.RegisterRegion(this); + + var sectors = new List(); + + for (var i = 0; i < Area.Length; i++) + { + var rect = Area[i]; + + var start = Map.Bound(new Point2D(rect.Start.X, rect.Start.Y)); + var end = Map.Bound(new Point2D(rect.End.X, rect.End.Y)); + + var startSector = Map.GetSector(start); + var endSector = Map.GetSector(end); + + for (var x = startSector.X; x <= endSector.X; x++) { - return; - } - - OnRegister(); - - Registered = true; - - if (Parent != null) - { - Parent.Children.Add(this); - Parent.OnChildAdded(this); - } - - Regions.Add(this); - - Map.RegisterRegion(this); - - var sectors = new List(); - - for (var i = 0; i < Area.Length; i++) - { - var rect = Area[i]; - - var start = Map.Bound(new Point2D(rect.Start.X, rect.Start.Y)); - var end = Map.Bound(new Point2D(rect.End.X, rect.End.Y)); - - var startSector = Map.GetSector(start); - var endSector = Map.GetSector(end); - - for (var x = startSector.X; x <= endSector.X; x++) + for (var y = startSector.Y; y <= endSector.Y; y++) { - for (var y = startSector.Y; y <= endSector.Y; y++) + var sector = Map.GetRealSector(x, y); + + sector.OnEnter(this, rect); + + if (!sectors.Contains(sector)) { - var sector = Map.GetRealSector(x, y); - - sector.OnEnter(this, rect); - - if (!sectors.Contains(sector)) - { - sectors.Add(sector); - } + sectors.Add(sector); } } } - - Sectors = sectors.ToArray(); } - public void Unregister() + Sectors = sectors.ToArray(); + } + + public void Unregister() + { + if (!Registered) { - if (!Registered) - { - return; - } - - OnUnregister(); - - Registered = false; - - if (Children.Count > 0) - { - logger.Warning("Unregistering region '{Region}' with children", this); - } - - if (Parent != null) - { - Parent.Children.Remove(this); - Parent.OnChildRemoved(this); - } - - Regions.Remove(this); - - Map.UnregisterRegion(this); - - if (Sectors != null) - { - for (var i = 0; i < Sectors.Length; i++) - { - Sectors[i].OnLeave(this); - } - } - - Sectors = null; + return; } - public bool Contains(Point3D p) + OnUnregister(); + + Registered = false; + + if (Children.Count > 0) { - for (var i = 0; i < Area.Length; i++) + logger.Warning("Unregistering region '{Region}' with children", this); + } + + if (Parent != null) + { + Parent.Children.Remove(this); + Parent.OnChildRemoved(this); + } + + Regions.Remove(this); + + Map.UnregisterRegion(this); + + if (Sectors != null) + { + for (var i = 0; i < Sectors.Length; i++) { - var rect = Area[i]; - - if (rect.Contains(p)) - { - return true; - } + Sectors[i].OnLeave(this); } + } + Sectors = null; + } + + public bool Contains(Point3D p) + { + for (var i = 0; i < Area.Length; i++) + { + var rect = Area[i]; + + if (rect.Contains(p)) + { + return true; + } + } + + return false; + } + + // TODO: Memoize this + public bool IsChildOf(Region region) + { + if (region == null) + { return false; } - // TODO: Memoize this - public bool IsChildOf(Region region) + var p = Parent; + + while (p != null) { - if (region == null) + if (p == region) + { + return true; + } + + p = p.Parent; + } + + return false; + } + + // TODO: Memoize this + public T GetRegion() where T : Region + { + var r = this; + + do + { + if (r is T tr) + { + return tr; + } + + r = r.Parent; + } while (r != null); + + return null; + } + + public Region GetRegion(Type regionType) + { + if (regionType == null) + { + return null; + } + + var r = this; + + do + { + if (regionType.IsInstanceOfType(r)) + { + return r; + } + + r = r.Parent; + } while (r != null); + + return null; + } + + public Region GetRegion(string regionName) + { + if (regionName == null) + { + return null; + } + + var r = this; + + do + { + if (r.Name == regionName) + { + return r; + } + + r = r.Parent; + } while (r != null); + + return null; + } + + public bool IsPartOf() where T : Region => GetRegion() != null; + + public bool IsPartOf(Region region) => this == region || IsChildOf(region); + + public bool IsPartOf(string regionName) => GetRegion(regionName) != null; + + public virtual bool AcceptsSpawnsFrom(Region region) => + AllowSpawn() && (region == this || Parent?.AcceptsSpawnsFrom(region) == true); + + public List GetPlayers() + { + var list = new List(); + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var ns in sector.Clients) + { + var player = ns.Mobile; + if (player?.Deleted == false && player.Region.IsPartOf(this)) + { + list.Add(ns.Mobile); + } + } + } + + return list; + } + + public int GetPlayerCount() + { + var count = 0; + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var ns in sector.Clients) + { + var player = ns.Mobile; + if (player?.Deleted == false && player.Region.IsPartOf(this)) + { + count++; + } + } + } + + return count; + } + + public List GetMobiles() + { + var list = new List(); + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var mobile in sector.Mobiles) + { + if (mobile.Region.IsPartOf(this)) + { + list.Add(mobile); + } + } + } + + return list; + } + + public int GetMobileCount() + { + var count = 0; + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var mobile in sector.Mobiles) + { + if (mobile.Region.IsPartOf(this)) + { + count++; + } + } + } + + return count; + } + + public List GetItems() + { + var list = new List(); + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var item in sector.Items) + { + if (Find(item.Location, item.Map).IsPartOf(this)) + { + list.Add(item); + } + } + } + + return list; + } + + public int GetItemCount() + { + var count = 0; + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var item in sector.Items) + { + if (Find(item.Location, item.Map).IsPartOf(this)) + { + count++; + } + } + } + + return count; + } + + public override string ToString() => Name ?? GetType().Name; + + public virtual void OnRegister() + { + } + + public virtual void OnUnregister() + { + } + + public virtual void OnChildAdded(Region child) + { + } + + public virtual void OnChildRemoved(Region child) + { + } + + public virtual bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation) => + m.WalkRegion == null || AcceptsSpawnsFrom(m.WalkRegion); + + public virtual void OnEnter(Mobile m) + { + } + + public virtual void OnExit(Mobile m) + { + } + + public virtual void MakeGuard(Mobile focus) + { + Parent?.MakeGuard(focus); + } + + public virtual Type GetResource(Type type) => Parent?.GetResource(type) ?? type; + + public virtual bool CanUseStuckMenu(Mobile m) => Parent?.CanUseStuckMenu(m) != false; + + public virtual void OnAggressed(Mobile aggressor, Mobile aggressed, bool criminal) + { + Parent?.OnAggressed(aggressor, aggressed, criminal); + } + + public virtual void OnDidHarmful(Mobile harmer, Mobile harmed) + { + Parent?.OnDidHarmful(harmer, harmed); + } + + public virtual void OnGotHarmful(Mobile harmer, Mobile harmed) + { + Parent?.OnGotHarmful(harmer, harmed); + } + + public virtual void OnLocationChanged(Mobile m, Point3D oldLocation) + { + Parent?.OnLocationChanged(m, oldLocation); + } + + public virtual bool OnTarget(Mobile m, Target t, object o) => Parent?.OnTarget(m, t, o) != false; + + public virtual bool OnCombatantChange(Mobile m, Mobile old, Mobile newMobile) => + Parent?.OnCombatantChange(m, old, newMobile) != false; + + public virtual bool AllowHousing(Mobile from, Point3D p) => Parent?.AllowHousing(from, p) != false; + + public virtual bool SendInaccessibleMessage(Item item, Mobile from) => + Parent?.SendInaccessibleMessage(item, from) == true; + + public virtual bool CheckAccessibility(Item item, Mobile from) => Parent?.CheckAccessibility(item, from) != false; + + public virtual bool OnDecay(Item item) => Parent?.OnDecay(item) != false; + + public virtual bool AllowHarmful(Mobile from, Mobile target) => + Parent?.AllowHarmful(from, target) ?? Mobile.AllowHarmfulHandler?.Invoke(from, target) ?? true; + + public virtual void OnCriminalAction(Mobile m, bool message) + { + if (Parent != null) + { + Parent.OnCriminalAction(m, message); + } + else if (message) + { + m.SendLocalizedMessage(1005040); // You've committed a criminal act!! + } + } + + public virtual bool AllowBeneficial(Mobile from, Mobile target) => + Parent?.AllowBeneficial(from, target) ?? + Mobile.AllowBeneficialHandler?.Invoke(from, target) ?? true; + + public virtual void OnBeneficialAction(Mobile helper, Mobile target) + { + Parent?.OnBeneficialAction(helper, target); + } + + public virtual void OnGotBeneficialAction(Mobile helper, Mobile target) + { + Parent?.OnGotBeneficialAction(helper, target); + } + + public virtual void SpellDamageScalar(Mobile caster, Mobile target, ref double damage) + { + Parent?.SpellDamageScalar(caster, target, ref damage); + } + + public virtual void OnSpeech(SpeechEventArgs args) + { + Parent?.OnSpeech(args); + } + + public virtual bool OnSkillUse(Mobile m, int skill) => Parent?.OnSkillUse(m, skill) != false; + + public virtual bool OnBeginSpellCast(Mobile m, ISpell s) => Parent?.OnBeginSpellCast(m, s) != false; + + public virtual void OnSpellCast(Mobile m, ISpell s) + { + Parent?.OnSpellCast(m, s); + } + + public virtual bool OnResurrect(Mobile m) => Parent?.OnResurrect(m) != false; + + public virtual bool OnBeforeDeath(Mobile m) => Parent?.OnBeforeDeath(m) != false; + + public virtual void OnDeath(Mobile m) + { + Parent?.OnDeath(m); + } + + public virtual bool OnDamage(Mobile m, ref int damage) => Parent?.OnDamage(m, ref damage) != false; + + public virtual bool OnHeal(Mobile m, ref int heal) => Parent?.OnHeal(m, ref heal) != false; + + public virtual bool OnDoubleClick(Mobile m, object o) => Parent?.OnDoubleClick(m, o) != false; + + public virtual bool OnSingleClick(Mobile m, object o) => Parent?.OnSingleClick(m, o) != false; + + public virtual bool AllowSpawn() => Parent?.AllowSpawn() != false; + + public virtual void AlterLightLevel(Mobile m, ref int global, ref int personal) + { + Parent?.AlterLightLevel(m, ref global, ref personal); + } + + public virtual TimeSpan GetLogoutDelay(Mobile m) + { + if (Parent != null) + { + return Parent.GetLogoutDelay(m); + } + + return m.AccessLevel > AccessLevel.Player ? StaffLogoutDelay : DefaultLogoutDelay; + } + + internal static bool CanMove(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation, Map map) + { + var oldRegion = m.Region; + var newRegion = Find(newLocation, map); + + while (oldRegion != newRegion) + { + if (!newRegion.OnMoveInto(m, d, newLocation, oldLocation)) { return false; } - var p = Parent; - - while (p != null) + if (newRegion.Parent == null) { - if (p == region) - { - return true; - } - - p = p.Parent; + return true; } - return false; + newRegion = newRegion.Parent; } - // TODO: Memoize this - public T GetRegion() where T : Region - { - var r = this; + return true; + } - do + internal static void OnRegionChange(Mobile m, Region oldRegion, Region newRegion) + { + if (newRegion != null && m.NetState != null) + { + m.CheckLightLevels(false); + + if (oldRegion == null || oldRegion.Music != newRegion.Music) { - if (r is T tr) - { - return tr; - } - - r = r.Parent; - } while (r != null); - - return null; - } - - public Region GetRegion(Type regionType) - { - if (regionType == null) - { - return null; - } - - var r = this; - - do - { - if (regionType.IsInstanceOfType(r)) - { - return r; - } - - r = r.Parent; - } while (r != null); - - return null; - } - - public Region GetRegion(string regionName) - { - if (regionName == null) - { - return null; - } - - var r = this; - - do - { - if (r.Name == regionName) - { - return r; - } - - r = r.Parent; - } while (r != null); - - return null; - } - - public bool IsPartOf() where T : Region => GetRegion() != null; - - public bool IsPartOf(Region region) => this == region || IsChildOf(region); - - public bool IsPartOf(string regionName) => GetRegion(regionName) != null; - - public virtual bool AcceptsSpawnsFrom(Region region) => - AllowSpawn() && (region == this || Parent?.AcceptsSpawnsFrom(region) == true); - - public List GetPlayers() - { - var list = new List(); - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var ns in sector.Clients) - { - var player = ns.Mobile; - if (player?.Deleted == false && player.Region.IsPartOf(this)) - { - list.Add(ns.Mobile); - } - } - } - - return list; - } - - public int GetPlayerCount() - { - var count = 0; - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var ns in sector.Clients) - { - var player = ns.Mobile; - if (player?.Deleted == false && player.Region.IsPartOf(this)) - { - count++; - } - } - } - - return count; - } - - public List GetMobiles() - { - var list = new List(); - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var mobile in sector.Mobiles) - { - if (mobile.Region.IsPartOf(this)) - { - list.Add(mobile); - } - } - } - - return list; - } - - public int GetMobileCount() - { - var count = 0; - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var mobile in sector.Mobiles) - { - if (mobile.Region.IsPartOf(this)) - { - count++; - } - } - } - - return count; - } - - public List GetItems() - { - var list = new List(); - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var item in sector.Items) - { - if (Find(item.Location, item.Map).IsPartOf(this)) - { - list.Add(item); - } - } - } - - return list; - } - - public int GetItemCount() - { - var count = 0; - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var item in sector.Items) - { - if (Find(item.Location, item.Map).IsPartOf(this)) - { - count++; - } - } - } - - return count; - } - - public override string ToString() => Name ?? GetType().Name; - - public virtual void OnRegister() - { - } - - public virtual void OnUnregister() - { - } - - public virtual void OnChildAdded(Region child) - { - } - - public virtual void OnChildRemoved(Region child) - { - } - - public virtual bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation) => - m.WalkRegion == null || AcceptsSpawnsFrom(m.WalkRegion); - - public virtual void OnEnter(Mobile m) - { - } - - public virtual void OnExit(Mobile m) - { - } - - public virtual void MakeGuard(Mobile focus) - { - Parent?.MakeGuard(focus); - } - - public virtual Type GetResource(Type type) => Parent?.GetResource(type) ?? type; - - public virtual bool CanUseStuckMenu(Mobile m) => Parent?.CanUseStuckMenu(m) != false; - - public virtual void OnAggressed(Mobile aggressor, Mobile aggressed, bool criminal) - { - Parent?.OnAggressed(aggressor, aggressed, criminal); - } - - public virtual void OnDidHarmful(Mobile harmer, Mobile harmed) - { - Parent?.OnDidHarmful(harmer, harmed); - } - - public virtual void OnGotHarmful(Mobile harmer, Mobile harmed) - { - Parent?.OnGotHarmful(harmer, harmed); - } - - public virtual void OnLocationChanged(Mobile m, Point3D oldLocation) - { - Parent?.OnLocationChanged(m, oldLocation); - } - - public virtual bool OnTarget(Mobile m, Target t, object o) => Parent?.OnTarget(m, t, o) != false; - - public virtual bool OnCombatantChange(Mobile m, Mobile old, Mobile newMobile) => - Parent?.OnCombatantChange(m, old, newMobile) != false; - - public virtual bool AllowHousing(Mobile from, Point3D p) => Parent?.AllowHousing(from, p) != false; - - public virtual bool SendInaccessibleMessage(Item item, Mobile from) => - Parent?.SendInaccessibleMessage(item, from) == true; - - public virtual bool CheckAccessibility(Item item, Mobile from) => Parent?.CheckAccessibility(item, from) != false; - - public virtual bool OnDecay(Item item) => Parent?.OnDecay(item) != false; - - public virtual bool AllowHarmful(Mobile from, Mobile target) => - Parent?.AllowHarmful(from, target) ?? Mobile.AllowHarmfulHandler?.Invoke(from, target) ?? true; - - public virtual void OnCriminalAction(Mobile m, bool message) - { - if (Parent != null) - { - Parent.OnCriminalAction(m, message); - } - else if (message) - { - m.SendLocalizedMessage(1005040); // You've committed a criminal act!! + m.NetState.SendPlayMusic(newRegion.Music); } } - public virtual bool AllowBeneficial(Mobile from, Mobile target) => - Parent?.AllowBeneficial(from, target) ?? - Mobile.AllowBeneficialHandler?.Invoke(from, target) ?? true; + var oldR = oldRegion; + var newR = newRegion; - public virtual void OnBeneficialAction(Mobile helper, Mobile target) + while (oldR != newR) { - Parent?.OnBeneficialAction(helper, target); - } + var oldRChild = oldR?.ChildLevel ?? -1; + var newRChild = newR?.ChildLevel ?? -1; - public virtual void OnGotBeneficialAction(Mobile helper, Mobile target) - { - Parent?.OnGotBeneficialAction(helper, target); - } - - public virtual void SpellDamageScalar(Mobile caster, Mobile target, ref double damage) - { - Parent?.SpellDamageScalar(caster, target, ref damage); - } - - public virtual void OnSpeech(SpeechEventArgs args) - { - Parent?.OnSpeech(args); - } - - public virtual bool OnSkillUse(Mobile m, int skill) => Parent?.OnSkillUse(m, skill) != false; - - public virtual bool OnBeginSpellCast(Mobile m, ISpell s) => Parent?.OnBeginSpellCast(m, s) != false; - - public virtual void OnSpellCast(Mobile m, ISpell s) - { - Parent?.OnSpellCast(m, s); - } - - public virtual bool OnResurrect(Mobile m) => Parent?.OnResurrect(m) != false; - - public virtual bool OnBeforeDeath(Mobile m) => Parent?.OnBeforeDeath(m) != false; - - public virtual void OnDeath(Mobile m) - { - Parent?.OnDeath(m); - } - - public virtual bool OnDamage(Mobile m, ref int damage) => Parent?.OnDamage(m, ref damage) != false; - - public virtual bool OnHeal(Mobile m, ref int heal) => Parent?.OnHeal(m, ref heal) != false; - - public virtual bool OnDoubleClick(Mobile m, object o) => Parent?.OnDoubleClick(m, o) != false; - - public virtual bool OnSingleClick(Mobile m, object o) => Parent?.OnSingleClick(m, o) != false; - - public virtual bool AllowSpawn() => Parent?.AllowSpawn() != false; - - public virtual void AlterLightLevel(Mobile m, ref int global, ref int personal) - { - Parent?.AlterLightLevel(m, ref global, ref personal); - } - - public virtual TimeSpan GetLogoutDelay(Mobile m) - { - if (Parent != null) + if (oldRChild >= newRChild) { - return Parent.GetLogoutDelay(m); + oldR?.OnExit(m); + oldR = oldR?.Parent; } - return m.AccessLevel > AccessLevel.Player ? StaffLogoutDelay : DefaultLogoutDelay; - } - - internal static bool CanMove(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation, Map map) - { - var oldRegion = m.Region; - var newRegion = Find(newLocation, map); - - while (oldRegion != newRegion) + if (newRChild >= oldRChild) { - if (!newRegion.OnMoveInto(m, d, newLocation, oldLocation)) - { - return false; - } - - if (newRegion.Parent == null) - { - return true; - } - - newRegion = newRegion.Parent; - } - - return true; - } - - internal static void OnRegionChange(Mobile m, Region oldRegion, Region newRegion) - { - if (newRegion != null && m.NetState != null) - { - m.CheckLightLevels(false); - - if (oldRegion == null || oldRegion.Music != newRegion.Music) - { - m.NetState.SendPlayMusic(newRegion.Music); - } - } - - var oldR = oldRegion; - var newR = newRegion; - - while (oldR != newR) - { - var oldRChild = oldR?.ChildLevel ?? -1; - var newRChild = newR?.ChildLevel ?? -1; - - if (oldRChild >= newRChild) - { - oldR?.OnExit(m); - oldR = oldR?.Parent; - } - - if (newRChild >= oldRChild) - { - newR?.OnEnter(m); - newR = newR?.Parent; - } + newR?.OnEnter(m); + newR = newR?.Parent; } } } diff --git a/Projects/Server/Regions/RegionLoader.cs b/Projects/Server/Regions/RegionLoader.cs index 15fe2a9b1..eda07b2c6 100644 --- a/Projects/Server/Regions/RegionLoader.cs +++ b/Projects/Server/Regions/RegionLoader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: RegionLoader.cs * * * @@ -21,65 +21,64 @@ using Server.Json; using Server.Logging; using Server.Utilities; -namespace Server +namespace Server; + +internal static class RegionLoader { - internal static class RegionLoader + private static readonly ILogger logger = LogFactory.GetLogger(typeof(RegionLoader)); + + internal static void LoadRegions() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(RegionLoader)); + var path = Path.Join(Core.BaseDirectory, "Data/regions.json"); - internal static void LoadRegions() + var failures = new List(); + var count = 0; + + logger.Information("Loading regions"); + + var stopwatch = Stopwatch.StartNew(); + var regions = JsonConfig.Deserialize>(path); + if (regions == null) { - var path = Path.Join(Core.BaseDirectory, "Data/regions.json"); + throw new JsonException($"Failed to deserialize {path}."); + } - var failures = new List(); - var count = 0; + foreach (var json in regions) + { + var type = AssemblyHandler.FindTypeByName(json.Type); - logger.Information("Loading regions"); - - var stopwatch = Stopwatch.StartNew(); - var regions = JsonConfig.Deserialize>(path); - if (regions == null) + if (type == null || !typeof(Region).IsAssignableFrom(type)) { - throw new JsonException($"Failed to deserialize {path}."); + failures.Add($"\tInvalid region type {json.Type}"); + continue; } - foreach (var json in regions) - { - var type = AssemblyHandler.FindTypeByName(json.Type); + var region = type.CreateInstance(json, JsonConfig.DefaultOptions); + region?.Register(); + count++; + } - if (type == null || !typeof(Region).IsAssignableFrom(type)) - { - failures.Add($"\tInvalid region type {json.Type}"); - continue; - } + stopwatch.Stop(); - var region = type.CreateInstance(json, JsonConfig.DefaultOptions); - region?.Register(); - count++; - } + if (failures.Count == 0) + { + logger.Information( + "Regions loaded ({Count} regions, {FailureCount} failures) ({Duration:F2} seconds)", + count, + failures.Count, + stopwatch.Elapsed.TotalSeconds + ); + } + else + { + logger.Warning( + "Failed loading regions ({Count} regions, {FailureCount} failures) ({Duration:F2} seconds)", + count, + failures.Count, + stopwatch.Elapsed.TotalSeconds + ); - stopwatch.Stop(); - - if (failures.Count == 0) - { - logger.Information( - "Regions loaded ({Count} regions, {FailureCount} failures) ({Duration:F2} seconds)", - count, - failures.Count, - stopwatch.Elapsed.TotalSeconds - ); - } - else - { - logger.Warning( - "Failed loading regions ({Count} regions, {FailureCount} failures) ({Duration:F2} seconds)", - count, - failures.Count, - stopwatch.Elapsed.TotalSeconds - ); - - logger.Warning("{Failures}", failures); - } + logger.Warning("{Failures}", failures); } } } diff --git a/Projects/Server/Sector.cs b/Projects/Server/Sector.cs index 898461856..be6b7de1f 100644 --- a/Projects/Server/Sector.cs +++ b/Projects/Server/Sector.cs @@ -4,212 +4,211 @@ using Server.Collections; using Server.Items; using Server.Network; -namespace Server +namespace Server; + +public class RegionRect : IComparable { - public class RegionRect : IComparable + private Rectangle3D m_Rect; + + public RegionRect(Region region, Rectangle3D rect) { - private Rectangle3D m_Rect; - - public RegionRect(Region region, Rectangle3D rect) - { - Region = region; - m_Rect = rect; - } - - public Region Region { get; } - - public Rectangle3D Rect => m_Rect; - - public int CompareTo(RegionRect regRect) => regRect == null ? 1 : Region.CompareTo(regRect.Region); - - public bool Contains(Point3D loc) => m_Rect.Contains(loc); + Region = region; + m_Rect = rect; } - public class Sector + public Region Region { get; } + + public Rectangle3D Rect => m_Rect; + + public int CompareTo(RegionRect regRect) => regRect == null ? 1 : Region.CompareTo(regRect.Region); + + public bool Contains(Point3D loc) => m_Rect.Contains(loc); +} + +public class Sector +{ + // TODO: Can we avoid this? + private static readonly List m_DefaultMobileList = new(); + private static readonly List m_DefaultItemList = new(); + private static readonly List m_DefaultClientList = new(); + private static readonly List m_DefaultMultiList = new(); + private static readonly List m_DefaultRectList = new(); + private bool m_Active; + private List m_Clients; + private List m_Items; + private List m_Mobiles; + private List m_Multis; + private List m_RegionRects; + + public Sector(int x, int y, Map owner) { - // TODO: Can we avoid this? - private static readonly List m_DefaultMobileList = new(); - private static readonly List m_DefaultItemList = new(); - private static readonly List m_DefaultClientList = new(); - private static readonly List m_DefaultMultiList = new(); - private static readonly List m_DefaultRectList = new(); - private bool m_Active; - private List m_Clients; - private List m_Items; - private List m_Mobiles; - private List m_Multis; - private List m_RegionRects; + X = x; + Y = y; + Owner = owner; + m_Active = false; + } - public Sector(int x, int y, Map owner) + public List RegionRects => m_RegionRects ?? m_DefaultRectList; + + public List Multis => m_Multis ?? m_DefaultMultiList; + + public List Mobiles => m_Mobiles ?? m_DefaultMobileList; + + public List Items => m_Items ?? m_DefaultItemList; + + public List Clients => m_Clients ?? m_DefaultClientList; + + public bool Active => m_Active && Owner != Map.Internal; + + public Map Owner { get; } + + public int X { get; } + + public int Y { get; } + + public void OnClientChange(NetState oldState, NetState newState) + { + Utility.Replace(ref m_Clients, oldState, newState); + } + + public void OnEnter(Item item) + { + Utility.Add(ref m_Items, item); + } + + public void OnLeave(Item item) + { + Utility.Remove(ref m_Items, item); + } + + public void OnEnter(Mobile mob) + { + Utility.Add(ref m_Mobiles, mob); + + if (mob.NetState != null) { - X = x; - Y = y; - Owner = owner; - m_Active = false; + Utility.Add(ref m_Clients, mob.NetState); + + Owner.ActivateSectors(X, Y); } + } - public List RegionRects => m_RegionRects ?? m_DefaultRectList; + public void OnLeave(Mobile mob) + { + Utility.Remove(ref m_Mobiles, mob); - public List Multis => m_Multis ?? m_DefaultMultiList; - - public List Mobiles => m_Mobiles ?? m_DefaultMobileList; - - public List Items => m_Items ?? m_DefaultItemList; - - public List Clients => m_Clients ?? m_DefaultClientList; - - public bool Active => m_Active && Owner != Map.Internal; - - public Map Owner { get; } - - public int X { get; } - - public int Y { get; } - - public void OnClientChange(NetState oldState, NetState newState) + if (mob.NetState != null) { - Utility.Replace(ref m_Clients, oldState, newState); + Utility.Remove(ref m_Clients, mob.NetState); + + Owner.DeactivateSectors(X, Y); } + } - public void OnEnter(Item item) + public void OnEnter(Region region, Rectangle3D rect) + { + Utility.Add(ref m_RegionRects, new RegionRect(region, rect)); + + m_RegionRects.Sort(); + + UpdateMobileRegions(); + } + + public void OnLeave(Region region) + { + if (m_RegionRects != null) { - Utility.Add(ref m_Items, item); - } - - public void OnLeave(Item item) - { - Utility.Remove(ref m_Items, item); - } - - public void OnEnter(Mobile mob) - { - Utility.Add(ref m_Mobiles, mob); - - if (mob.NetState != null) + for (var i = m_RegionRects.Count - 1; i >= 0; i--) { - Utility.Add(ref m_Clients, mob.NetState); + var regRect = m_RegionRects[i]; - Owner.ActivateSectors(X, Y); - } - } - - public void OnLeave(Mobile mob) - { - Utility.Remove(ref m_Mobiles, mob); - - if (mob.NetState != null) - { - Utility.Remove(ref m_Clients, mob.NetState); - - Owner.DeactivateSectors(X, Y); - } - } - - public void OnEnter(Region region, Rectangle3D rect) - { - Utility.Add(ref m_RegionRects, new RegionRect(region, rect)); - - m_RegionRects.Sort(); - - UpdateMobileRegions(); - } - - public void OnLeave(Region region) - { - if (m_RegionRects != null) - { - for (var i = m_RegionRects.Count - 1; i >= 0; i--) + if (regRect.Region == region) { - var regRect = m_RegionRects[i]; - - if (regRect.Region == region) - { - m_RegionRects.RemoveAt(i); - } - } - - if (m_RegionRects.Count == 0) - { - m_RegionRects = null; + m_RegionRects.RemoveAt(i); } } - UpdateMobileRegions(); + if (m_RegionRects.Count == 0) + { + m_RegionRects = null; + } } - private void UpdateMobileRegions() + UpdateMobileRegions(); + } + + private void UpdateMobileRegions() + { + if (m_Mobiles != null) { + using var queue = PooledRefQueue.Create(m_Mobiles.Count); + foreach (var mob in m_Mobiles) + { + queue.Enqueue(mob); + } + + while (queue.Count > 0) + { + queue.Dequeue().UpdateRegion(); + } + } + } + + public void OnMultiEnter(BaseMulti multi) + { + Utility.Add(ref m_Multis, multi); + } + + public void OnMultiLeave(BaseMulti multi) + { + Utility.Remove(ref m_Multis, multi); + } + + public void Activate() + { + if (!Active) + { + if (m_Items != null) + { + foreach (var item in m_Items) + { + item.OnSectorActivate(); + } + } + if (m_Mobiles != null) { - using var queue = PooledRefQueue.Create(m_Mobiles.Count); foreach (var mob in m_Mobiles) { - queue.Enqueue(mob); - } - - while (queue.Count > 0) - { - queue.Dequeue().UpdateRegion(); + mob.OnSectorActivate(); } } - } - public void OnMultiEnter(BaseMulti multi) - { - Utility.Add(ref m_Multis, multi); + m_Active = true; } + } - public void OnMultiLeave(BaseMulti multi) + public void Deactivate() + { + if (Active) { - Utility.Remove(ref m_Multis, multi); - } - - public void Activate() - { - if (!Active) + if (m_Items != null) { - if (m_Items != null) + foreach (var item in m_Items) { - foreach (var item in m_Items) - { - item.OnSectorActivate(); - } + item.OnSectorDeactivate(); } - - if (m_Mobiles != null) - { - foreach (var mob in m_Mobiles) - { - mob.OnSectorActivate(); - } - } - - m_Active = true; } - } - public void Deactivate() - { - if (Active) + if (m_Mobiles != null) { - if (m_Items != null) + foreach (var mob in m_Mobiles) { - foreach (var item in m_Items) - { - item.OnSectorDeactivate(); - } + mob.OnSectorDeactivate(); } - - if (m_Mobiles != null) - { - foreach (var mob in m_Mobiles) - { - mob.OnSectorDeactivate(); - } - } - - m_Active = false; } + + m_Active = false; } } } diff --git a/Projects/Server/SecureTrade.cs b/Projects/Server/SecureTrade.cs index 032931cea..4a0ed4b29 100644 --- a/Projects/Server/SecureTrade.cs +++ b/Projects/Server/SecureTrade.cs @@ -3,75 +3,266 @@ using Server.Accounting; using Server.Items; using Server.Network; -namespace Server +namespace Server; + +public class SecureTrade { - public class SecureTrade + public SecureTrade(Mobile from, Mobile to) { - public SecureTrade(Mobile from, Mobile to) + Valid = true; + + From = new SecureTradeInfo(this, from, new SecureTradeContainer(this)); + To = new SecureTradeInfo(this, to, new SecureTradeContainer(this)); + + var from704565 = from.NetState?.NewSecureTrading == true; + var to704565 = to.NetState?.NewSecureTrading == true; + + from.NetState.SendMobileStatus(from, to); + from.NetState.SendUpdateSecureTrade(From.Container, false, false); + from.NetState.SendSecureTradeEquip(To.Container, to); + + from.NetState.SendUpdateSecureTrade(From.Container, false, false); + from.NetState.SendSecureTradeEquip(From.Container, from); + + from.NetState.SendDisplaySecureTrade(to, From.Container, To.Container, to.Name); + from.NetState.SendUpdateSecureTrade(From.Container, false, false); + + if (from.Account != null && from704565) { - Valid = true; + from.NetState.SendUpdateSecureTrade( + From.Container, + TradeFlag.UpdateLedger, + from.Account.TotalGold, + from.Account.TotalPlat + ); + } - From = new SecureTradeInfo(this, from, new SecureTradeContainer(this)); - To = new SecureTradeInfo(this, to, new SecureTradeContainer(this)); + to.NetState.SendMobileStatus(to, from); + to.NetState.SendUpdateSecureTrade(To.Container, false, false); + to.NetState.SendSecureTradeEquip(From.Container, from); - var from704565 = from.NetState?.NewSecureTrading == true; - var to704565 = to.NetState?.NewSecureTrading == true; + to.NetState.SendUpdateSecureTrade(To.Container, false, false); + to.NetState.SendSecureTradeEquip(To.Container, to); - from.NetState.SendMobileStatus(from, to); - from.NetState.SendUpdateSecureTrade(From.Container, false, false); - from.NetState.SendSecureTradeEquip(To.Container, to); + to.NetState.SendDisplaySecureTrade(from, To.Container, From.Container, from.Name); + to.NetState.SendUpdateSecureTrade(To.Container, false, false); - from.NetState.SendUpdateSecureTrade(From.Container, false, false); - from.NetState.SendSecureTradeEquip(From.Container, from); + if (to.Account != null && to704565) + { + to.NetState.SendUpdateSecureTrade( + To.Container, + TradeFlag.UpdateLedger, + to.Account.TotalGold, + to.Account.TotalPlat + ); + } + } - from.NetState.SendDisplaySecureTrade(to, From.Container, To.Container, to.Name); - from.NetState.SendUpdateSecureTrade(From.Container, false, false); + public SecureTradeInfo From { get; } - if (from.Account != null && from704565) + public SecureTradeInfo To { get; } + + public bool Valid { get; private set; } + + public void Cancel() + { + if (!Valid) + { + return; + } + + var list = From.Container.Items; + + for (var i = list.Count - 1; i >= 0; --i) + { + if (i < list.Count) { - from.NetState.SendUpdateSecureTrade( - From.Container, - TradeFlag.UpdateLedger, - from.Account.TotalGold, - from.Account.TotalPlat - ); - } + var item = list[i]; - to.NetState.SendMobileStatus(to, from); - to.NetState.SendUpdateSecureTrade(To.Container, false, false); - to.NetState.SendSecureTradeEquip(From.Container, from); + if (item == From.VirtualCheck) + { + continue; + } - to.NetState.SendUpdateSecureTrade(To.Container, false, false); - to.NetState.SendSecureTradeEquip(To.Container, to); + item.OnSecureTrade(From.Mobile, To.Mobile, From.Mobile, false); - to.NetState.SendDisplaySecureTrade(from, To.Container, From.Container, from.Name); - to.NetState.SendUpdateSecureTrade(To.Container, false, false); - - if (to.Account != null && to704565) - { - to.NetState.SendUpdateSecureTrade( - To.Container, - TradeFlag.UpdateLedger, - to.Account.TotalGold, - to.Account.TotalPlat - ); + if (!item.Deleted) + { + From.Mobile.AddToBackpack(item); + } } } - public SecureTradeInfo From { get; } + list = To.Container.Items; - public SecureTradeInfo To { get; } - - public bool Valid { get; private set; } - - public void Cancel() + for (var i = list.Count - 1; i >= 0; --i) { - if (!Valid) + if (i < list.Count) { + var item = list[i]; + + if (item == To.VirtualCheck) + { + continue; + } + + item.OnSecureTrade(To.Mobile, From.Mobile, To.Mobile, false); + + if (!item.Deleted) + { + To.Mobile.AddToBackpack(item); + } + } + } + + Close(); + } + + public void Close() + { + if (!Valid) + { + return; + } + + From.Mobile.NetState.SendCloseSecureTrade(From.Container); + To.Mobile.NetState.SendCloseSecureTrade(To.Container); + + Valid = false; + + var ns = From.Mobile.NetState; + + ns?.RemoveTrade(this); + + ns = To.Mobile.NetState; + + ns?.RemoveTrade(this); + + Timer.StartTimer(From.Dispose); + Timer.StartTimer(To.Dispose); + } + + public void UpdateFromCurrency() + { + UpdateCurrency(From, To); + } + + public void UpdateToCurrency() + { + UpdateCurrency(To, From); + } + + private static void UpdateCurrency(SecureTradeInfo left, SecureTradeInfo right) + { + if (left.Mobile.NetState?.NewSecureTrading == true) + { + var plat = left.Mobile.Account.TotalPlat; + var gold = left.Mobile.Account.TotalGold; + + left.Mobile.NetState.SendUpdateSecureTrade(left.Container, TradeFlag.UpdateLedger, gold, plat); + } + + if (right.Mobile.NetState?.NewSecureTrading == true) + { + right.Mobile.NetState.SendUpdateSecureTrade(right.Container, TradeFlag.UpdateGold, left.Gold, left.Plat); + } + } + + public void Update() + { + if (!Valid) + { + return; + } + + if (!From.IsDisposed && From.Accepted && !To.IsDisposed && To.Accepted) + { + var list = From.Container.Items; + + var allowed = true; + + for (var i = list.Count - 1; allowed && i >= 0; --i) + { + if (i < list.Count) + { + var item = list[i]; + + if (item == From.VirtualCheck) + { + continue; + } + + if (!item.AllowSecureTrade(From.Mobile, To.Mobile, To.Mobile, true)) + { + allowed = false; + } + } + } + + list = To.Container.Items; + + for (var i = list.Count - 1; allowed && i >= 0; --i) + { + if (i < list.Count) + { + var item = list[i]; + + if (item == To.VirtualCheck) + { + continue; + } + + if (!item.AllowSecureTrade(To.Mobile, From.Mobile, From.Mobile, true)) + { + allowed = false; + } + } + } + + if (AccountGold.Enabled) + { + if (From.Mobile.Account != null) + { + var totalPlat = From.Mobile.Account.TotalPlat; + var totalGold = From.Mobile.Account.TotalGold; + + if (totalPlat < From.Plat || totalGold < From.Gold) + { + allowed = false; + From.Mobile.SendMessage("You do not have enough currency to complete this trade."); + } + } + + if (To.Mobile.Account != null) + { + var totalPlat = To.Mobile.Account.TotalPlat; + var totalGold = To.Mobile.Account.TotalGold; + + if (totalPlat < To.Plat || totalGold < To.Gold) + { + allowed = false; + To.Mobile.SendMessage("You do not have enough currency to complete this trade."); + } + } + } + + if (!allowed) + { + From.Accepted = false; + To.Accepted = false; + + From.Mobile.NetState.SendUpdateSecureTrade(From.Container, From.Accepted, To.Accepted); + To.Mobile.NetState.SendUpdateSecureTrade(To.Container, To.Accepted, From.Accepted); + return; } - var list = From.Container.Items; + if (AccountGold.Enabled && From.Mobile.Account != null && To.Mobile.Account != null) + { + HandleAccountGoldTrade(); + } + + list = From.Container.Items; for (var i = list.Count - 1; i >= 0; --i) { @@ -84,11 +275,11 @@ namespace Server continue; } - item.OnSecureTrade(From.Mobile, To.Mobile, From.Mobile, false); + item.OnSecureTrade(From.Mobile, To.Mobile, To.Mobile, true); if (!item.Deleted) { - From.Mobile.AddToBackpack(item); + To.Mobile.AddToBackpack(item); } } } @@ -106,364 +297,172 @@ namespace Server continue; } - item.OnSecureTrade(To.Mobile, From.Mobile, To.Mobile, false); + item.OnSecureTrade(To.Mobile, From.Mobile, From.Mobile, true); if (!item.Deleted) { - To.Mobile.AddToBackpack(item); + From.Mobile.AddToBackpack(item); } } } Close(); } - - public void Close() + else if (!From.IsDisposed && !To.IsDisposed) { - if (!Valid) - { - return; - } - - From.Mobile.NetState.SendCloseSecureTrade(From.Container); - To.Mobile.NetState.SendCloseSecureTrade(To.Container); - - Valid = false; - - var ns = From.Mobile.NetState; - - ns?.RemoveTrade(this); - - ns = To.Mobile.NetState; - - ns?.RemoveTrade(this); - - Timer.StartTimer(From.Dispose); - Timer.StartTimer(To.Dispose); - } - - public void UpdateFromCurrency() - { - UpdateCurrency(From, To); - } - - public void UpdateToCurrency() - { - UpdateCurrency(To, From); - } - - private static void UpdateCurrency(SecureTradeInfo left, SecureTradeInfo right) - { - if (left.Mobile.NetState?.NewSecureTrading == true) - { - var plat = left.Mobile.Account.TotalPlat; - var gold = left.Mobile.Account.TotalGold; - - left.Mobile.NetState.SendUpdateSecureTrade(left.Container, TradeFlag.UpdateLedger, gold, plat); - } - - if (right.Mobile.NetState?.NewSecureTrading == true) - { - right.Mobile.NetState.SendUpdateSecureTrade(right.Container, TradeFlag.UpdateGold, left.Gold, left.Plat); - } - } - - public void Update() - { - if (!Valid) - { - return; - } - - if (!From.IsDisposed && From.Accepted && !To.IsDisposed && To.Accepted) - { - var list = From.Container.Items; - - var allowed = true; - - for (var i = list.Count - 1; allowed && i >= 0; --i) - { - if (i < list.Count) - { - var item = list[i]; - - if (item == From.VirtualCheck) - { - continue; - } - - if (!item.AllowSecureTrade(From.Mobile, To.Mobile, To.Mobile, true)) - { - allowed = false; - } - } - } - - list = To.Container.Items; - - for (var i = list.Count - 1; allowed && i >= 0; --i) - { - if (i < list.Count) - { - var item = list[i]; - - if (item == To.VirtualCheck) - { - continue; - } - - if (!item.AllowSecureTrade(To.Mobile, From.Mobile, From.Mobile, true)) - { - allowed = false; - } - } - } - - if (AccountGold.Enabled) - { - if (From.Mobile.Account != null) - { - var totalPlat = From.Mobile.Account.TotalPlat; - var totalGold = From.Mobile.Account.TotalGold; - - if (totalPlat < From.Plat || totalGold < From.Gold) - { - allowed = false; - From.Mobile.SendMessage("You do not have enough currency to complete this trade."); - } - } - - if (To.Mobile.Account != null) - { - var totalPlat = To.Mobile.Account.TotalPlat; - var totalGold = To.Mobile.Account.TotalGold; - - if (totalPlat < To.Plat || totalGold < To.Gold) - { - allowed = false; - To.Mobile.SendMessage("You do not have enough currency to complete this trade."); - } - } - } - - if (!allowed) - { - From.Accepted = false; - To.Accepted = false; - - From.Mobile.NetState.SendUpdateSecureTrade(From.Container, From.Accepted, To.Accepted); - To.Mobile.NetState.SendUpdateSecureTrade(To.Container, To.Accepted, From.Accepted); - - return; - } - - if (AccountGold.Enabled && From.Mobile.Account != null && To.Mobile.Account != null) - { - HandleAccountGoldTrade(); - } - - list = From.Container.Items; - - for (var i = list.Count - 1; i >= 0; --i) - { - if (i < list.Count) - { - var item = list[i]; - - if (item == From.VirtualCheck) - { - continue; - } - - item.OnSecureTrade(From.Mobile, To.Mobile, To.Mobile, true); - - if (!item.Deleted) - { - To.Mobile.AddToBackpack(item); - } - } - } - - list = To.Container.Items; - - for (var i = list.Count - 1; i >= 0; --i) - { - if (i < list.Count) - { - var item = list[i]; - - if (item == To.VirtualCheck) - { - continue; - } - - item.OnSecureTrade(To.Mobile, From.Mobile, From.Mobile, true); - - if (!item.Deleted) - { - From.Mobile.AddToBackpack(item); - } - } - } - - Close(); - } - else if (!From.IsDisposed && !To.IsDisposed) - { - From.Mobile.NetState.SendUpdateSecureTrade(From.Container, From.Accepted, To.Accepted); - To.Mobile.NetState.SendUpdateSecureTrade(To.Container, To.Accepted, From.Accepted); - } - } - - private void HandleAccountGoldTrade() - { - int fromPlatSend = 0, fromGoldSend = 0, fromPlatRecv = 0, fromGoldRecv = 0; - int toPlatSend = 0, toGoldSend = 0, toPlatRecv = 0, toGoldRecv = 0; - - if ((From.Plat > 0) & From.Mobile.Account.WithdrawPlat(From.Plat)) - { - fromPlatSend = From.Plat; - - if (To.Mobile.Account.DepositPlat(From.Plat)) - { - toPlatRecv = fromPlatSend; - } - } - - if ((From.Gold > 0) & From.Mobile.Account.WithdrawGold(From.Gold)) - { - fromGoldSend = From.Gold; - - if (To.Mobile.Account.DepositGold(From.Gold)) - { - toGoldRecv = fromGoldSend; - } - } - - if ((To.Plat > 0) & To.Mobile.Account.WithdrawPlat(To.Plat)) - { - toPlatSend = To.Plat; - - if (From.Mobile.Account.DepositPlat(To.Plat)) - { - fromPlatRecv = toPlatSend; - } - } - - if ((To.Gold > 0) & To.Mobile.Account.WithdrawGold(To.Gold)) - { - toGoldSend = To.Gold; - - if (From.Mobile.Account.DepositGold(To.Gold)) - { - fromGoldRecv = toGoldSend; - } - } - - HandleAccountGoldTrade(From.Mobile, To.Mobile, fromPlatSend, fromGoldSend, fromPlatRecv, fromGoldRecv); - HandleAccountGoldTrade(To.Mobile, From.Mobile, toPlatSend, toGoldSend, toPlatRecv, toGoldRecv); - } - - private static void HandleAccountGoldTrade( - Mobile left, - Mobile right, - int platSend, - int goldSend, - int platRecv, - int goldRecv - ) - { - if (platSend > 0 || goldSend > 0) - { - if (platSend > 0 && goldSend > 0) - { - left.SendMessage( - "You traded {0:#,0} platinum and {1:#,0} gold to {2}.", - platSend, - goldSend, - right.RawName - ); - } - else if (platSend > 0) - { - left.SendMessage("You traded {0:#,0} platinum to {1}.", platSend, right.RawName); - } - else if (goldSend > 0) - { - left.SendMessage("You traded {0:#,0} gold to {1}.", goldSend, right.RawName); - } - } - - if (platRecv > 0 || goldRecv > 0) - { - if (platRecv > 0 && goldRecv > 0) - { - left.SendMessage( - "You received {0:#,0} platinum and {1:#,0} gold from {2}.", - platRecv, - goldRecv, - right.RawName - ); - } - else if (platRecv > 0) - { - left.SendMessage("You received {0:#,0} platinum from {1}.", platRecv, right.RawName); - } - else if (goldRecv > 0) - { - left.SendMessage("You received {0:#,0} gold from {1}.", goldRecv, right.RawName); - } - } + From.Mobile.NetState.SendUpdateSecureTrade(From.Container, From.Accepted, To.Accepted); + To.Mobile.NetState.SendUpdateSecureTrade(To.Container, To.Accepted, From.Accepted); } } - public class SecureTradeInfo : IDisposable + private void HandleAccountGoldTrade() { - public SecureTradeInfo(SecureTrade owner, Mobile m, SecureTradeContainer c) + int fromPlatSend = 0, fromGoldSend = 0, fromPlatRecv = 0, fromGoldRecv = 0; + int toPlatSend = 0, toGoldSend = 0, toPlatRecv = 0, toGoldRecv = 0; + + if ((From.Plat > 0) & From.Mobile.Account.WithdrawPlat(From.Plat)) { - Owner = owner; - Mobile = m; - Container = c; + fromPlatSend = From.Plat; - Mobile.AddItem(Container); - - VirtualCheck = new VirtualCheck(); - Container.DropItem(VirtualCheck); + if (To.Mobile.Account.DepositPlat(From.Plat)) + { + toPlatRecv = fromPlatSend; + } } - public SecureTrade Owner { get; private set; } - public Mobile Mobile { get; private set; } - public SecureTradeContainer Container { get; private set; } - public VirtualCheck VirtualCheck { get; private set; } - - public int Gold + if ((From.Gold > 0) & From.Mobile.Account.WithdrawGold(From.Gold)) { - get => VirtualCheck.Gold; - set => VirtualCheck.Gold = value; + fromGoldSend = From.Gold; + + if (To.Mobile.Account.DepositGold(From.Gold)) + { + toGoldRecv = fromGoldSend; + } } - public int Plat + if ((To.Plat > 0) & To.Mobile.Account.WithdrawPlat(To.Plat)) { - get => VirtualCheck.Plat; - set => VirtualCheck.Plat = value; + toPlatSend = To.Plat; + + if (From.Mobile.Account.DepositPlat(To.Plat)) + { + fromPlatRecv = toPlatSend; + } } - public bool Accepted { get; set; } - - public bool IsDisposed { get; private set; } - - public void Dispose() + if ((To.Gold > 0) & To.Mobile.Account.WithdrawGold(To.Gold)) { - VirtualCheck.Delete(); - VirtualCheck = null; + toGoldSend = To.Gold; - Container.Delete(); - Container = null; + if (From.Mobile.Account.DepositGold(To.Gold)) + { + fromGoldRecv = toGoldSend; + } + } - Mobile = null; - Owner = null; + HandleAccountGoldTrade(From.Mobile, To.Mobile, fromPlatSend, fromGoldSend, fromPlatRecv, fromGoldRecv); + HandleAccountGoldTrade(To.Mobile, From.Mobile, toPlatSend, toGoldSend, toPlatRecv, toGoldRecv); + } - IsDisposed = true; + private static void HandleAccountGoldTrade( + Mobile left, + Mobile right, + int platSend, + int goldSend, + int platRecv, + int goldRecv + ) + { + if (platSend > 0 || goldSend > 0) + { + if (platSend > 0 && goldSend > 0) + { + left.SendMessage( + "You traded {0:#,0} platinum and {1:#,0} gold to {2}.", + platSend, + goldSend, + right.RawName + ); + } + else if (platSend > 0) + { + left.SendMessage("You traded {0:#,0} platinum to {1}.", platSend, right.RawName); + } + else if (goldSend > 0) + { + left.SendMessage("You traded {0:#,0} gold to {1}.", goldSend, right.RawName); + } + } + + if (platRecv > 0 || goldRecv > 0) + { + if (platRecv > 0 && goldRecv > 0) + { + left.SendMessage( + "You received {0:#,0} platinum and {1:#,0} gold from {2}.", + platRecv, + goldRecv, + right.RawName + ); + } + else if (platRecv > 0) + { + left.SendMessage("You received {0:#,0} platinum from {1}.", platRecv, right.RawName); + } + else if (goldRecv > 0) + { + left.SendMessage("You received {0:#,0} gold from {1}.", goldRecv, right.RawName); + } } } } + +public class SecureTradeInfo : IDisposable +{ + public SecureTradeInfo(SecureTrade owner, Mobile m, SecureTradeContainer c) + { + Owner = owner; + Mobile = m; + Container = c; + + Mobile.AddItem(Container); + + VirtualCheck = new VirtualCheck(); + Container.DropItem(VirtualCheck); + } + + public SecureTrade Owner { get; private set; } + public Mobile Mobile { get; private set; } + public SecureTradeContainer Container { get; private set; } + public VirtualCheck VirtualCheck { get; private set; } + + public int Gold + { + get => VirtualCheck.Gold; + set => VirtualCheck.Gold = value; + } + + public int Plat + { + get => VirtualCheck.Plat; + set => VirtualCheck.Plat = value; + } + + public bool Accepted { get; set; } + + public bool IsDisposed { get; private set; } + + public void Dispose() + { + VirtualCheck.Delete(); + VirtualCheck = null; + + Container.Delete(); + Container = null; + + Mobile = null; + Owner = null; + + IsDisposed = true; + } +} diff --git a/Projects/Server/Serial.cs b/Projects/Server/Serial.cs index 7a83163fa..fcb43a07b 100644 --- a/Projects/Server/Serial.cs +++ b/Projects/Server/Serial.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Serial.cs * * * diff --git a/Projects/Server/Serialization/AdhocPersistence.cs b/Projects/Server/Serialization/AdhocPersistence.cs index 83fdc590d..086179409 100644 --- a/Projects/Server/Serialization/AdhocPersistence.cs +++ b/Projects/Server/Serialization/AdhocPersistence.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: AdhocPersistence.cs * * * @@ -16,7 +16,6 @@ using System; using System.IO; using System.IO.MemoryMappedFiles; -using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Server; diff --git a/Projects/Server/Serialization/Attributes/ManualDirtyCheckingAttribute.cs b/Projects/Server/Serialization/Attributes/ManualDirtyCheckingAttribute.cs index 4a15de156..7574a47d6 100644 --- a/Projects/Server/Serialization/Attributes/ManualDirtyCheckingAttribute.cs +++ b/Projects/Server/Serialization/Attributes/ManualDirtyCheckingAttribute.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ManualDirtyCheckingAttribute.cs * * * @@ -15,14 +15,13 @@ using System; -namespace Server -{ - /// - /// Indicates that the applied class has dirty checking. This is necessary for classes that are not code genned. - /// - [AttributeUsage(AttributeTargets.Class)] - public class ManualDirtyCheckingAttribute : Attribute - { +namespace Server; + +/// +/// Indicates that the applied class has dirty checking. This is necessary for classes that are not code genned. +/// +[AttributeUsage(AttributeTargets.Class)] +public class ManualDirtyCheckingAttribute : Attribute +{ - } } diff --git a/Projects/Server/Serialization/BinaryFileReader.cs b/Projects/Server/Serialization/BinaryFileReader.cs index 927acad9f..eab5c7ffa 100644 --- a/Projects/Server/Serialization/BinaryFileReader.cs +++ b/Projects/Server/Serialization/BinaryFileReader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BinaryFileReader.cs * * * diff --git a/Projects/Server/Serialization/BinaryFileWriter.cs b/Projects/Server/Serialization/BinaryFileWriter.cs index 9980eb000..ceb7447c1 100644 --- a/Projects/Server/Serialization/BinaryFileWriter.cs +++ b/Projects/Server/Serialization/BinaryFileWriter.cs @@ -1,8 +1,8 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: BufferedFileWriter.cs * + * File: BinaryFileWriter.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 * @@ -16,55 +16,54 @@ using System; using System.IO; -namespace Server +namespace Server; + +public class BinaryFileWriter : BufferWriter, IDisposable { - public class BinaryFileWriter : BufferWriter, IDisposable + private readonly Stream _file; + private long _position; + + public BinaryFileWriter(string filename, bool prefixStr) : + this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), prefixStr) + {} + + public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr) { - private readonly Stream _file; - private long _position; + _file = stream; + _position = _file.Position; + } - public BinaryFileWriter(string filename, bool prefixStr) : - this(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), prefixStr) - {} + public override long Position => _position + Index; - public BinaryFileWriter(Stream stream, bool prefixStr) : base(prefixStr) + protected override int BufferSize => 81920; + + public override void Flush() + { + if (Index > 0) { - _file = stream; - _position = _file.Position; + _position += Index; + + _file.Write(Buffer, 0, (int)Index); + Index = 0; } + } - public override long Position => _position + Index; - - protected override int BufferSize => 81920; - - public override void Flush() - { - if (Index > 0) - { - _position += Index; - - _file.Write(Buffer, 0, (int)Index); - Index = 0; - } - } - - public override void Close() - { - if (Index > 0) - { - Flush(); - } - - _file.Close(); - } - - public override long Seek(long offset, SeekOrigin origin) + public override void Close() + { + if (Index > 0) { Flush(); - - return _position = _file.Seek(offset, origin); } - public void Dispose() => Close(); + _file.Close(); } + + public override long Seek(long offset, SeekOrigin origin) + { + Flush(); + + return _position = _file.Seek(offset, origin); + } + + public void Dispose() => Close(); } diff --git a/Projects/Server/Serialization/BufferReader.cs b/Projects/Server/Serialization/BufferReader.cs index 7af7bce55..1a1f151af 100644 --- a/Projects/Server/Serialization/BufferReader.cs +++ b/Projects/Server/Serialization/BufferReader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BufferReader.cs * * * diff --git a/Projects/Server/Serialization/BufferWriter.cs b/Projects/Server/Serialization/BufferWriter.cs index 044bb8302..325b33717 100644 --- a/Projects/Server/Serialization/BufferWriter.cs +++ b/Projects/Server/Serialization/BufferWriter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BufferedFileWriter.cs * * * @@ -21,312 +21,311 @@ using System.Text; using Server.Collections; using Server.Text; -namespace Server +namespace Server; + +public class BufferWriter : IGenericWriter { - public class BufferWriter : IGenericWriter + private readonly Encoding m_Encoding; + private readonly bool m_PrefixStrings; + private long _bytesWritten; + private long _index; + + protected long Index { - private readonly Encoding m_Encoding; - private readonly bool m_PrefixStrings; - private long _bytesWritten; - private long _index; - - protected long Index + get => _index; + set { - get => _index; - set + if (value < 0 || value > _buffer.Length) { - if (value < 0 || value > _buffer.Length) - { - // If you are receiving this exception and your value is too large, you may need to use `Resize` - // If you are receiving this exception and your value is negative, you probably used Seek incorrectly. - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _index = value; - - if (value > _bytesWritten) - { - _bytesWritten = value; - } - } - } - - private byte[] _buffer; - - public BufferWriter(byte[] buffer, bool prefixStr) - { - m_PrefixStrings = prefixStr; - m_Encoding = TextEncoding.UTF8; - _buffer = buffer; - } - - public BufferWriter(bool prefixStr) : this(0, prefixStr) - { - } - - public BufferWriter(int count, bool prefixStr) - { - m_PrefixStrings = prefixStr; - m_Encoding = TextEncoding.UTF8; - _buffer = GC.AllocateUninitializedArray(count < 1 ? BufferSize : count); - } - - public virtual long Position => Index; - - protected virtual int BufferSize => 256; - - public byte[] Buffer => _buffer; - - public virtual void Close() - { - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Resize(int size) - { - // We shouldn't ever resize to a 0 length buffer. That is dangerous - if (size <= 0) - { - size = BufferSize; + // If you are receiving this exception and your value is too large, you may need to use `Resize` + // If you are receiving this exception and your value is negative, you probably used Seek incorrectly. + throw new ArgumentOutOfRangeException(nameof(value)); } - if (size < _buffer.Length) + _index = value; + + if (value > _bytesWritten) { - _bytesWritten = size; - } - - var newBuffer = GC.AllocateUninitializedArray(size); - _buffer.AsSpan(0, Math.Min(size, _buffer.Length)).CopyTo(newBuffer); - _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)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void FlushIfNeeded(int amount) - { - if (Index + amount > _buffer.Length) - { - Flush(); - } - } - - public void Write(ReadOnlySpan bytes) - { - var remaining = bytes.Length; - var idx = 0; - - while (remaining > 0) - { - FlushIfNeeded(remaining); - - var count = Math.Min((int)(_buffer.Length - Index), remaining); - bytes.Slice(idx, count).CopyTo(_buffer.AsSpan((int)Index, count)); - - idx += count; - Index += count; - remaining -= count; - } - } - - public void Write(BitArray bitArray) - { - var byteLength = BitArray.GetByteArrayLengthFromBitLength(bitArray.Length); - - ((IGenericWriter)this).WriteEncodedInt(bitArray.Length); - FlushIfNeeded(byteLength); - bitArray.CopyTo(_buffer.AsSpan((int)Index, byteLength)); - Index += byteLength; - } - - public virtual long Seek(long offset, SeekOrigin origin) - { - Debug.Assert( - origin != SeekOrigin.End || offset <= 0 && offset > -_buffer.Length, - "Attempting to seek to an invalid position using SeekOrigin.End" - ); - Debug.Assert( - origin != SeekOrigin.Begin || offset >= 0 && offset < _buffer.Length, - "Attempting to seek to an invalid position using SeekOrigin.Begin" - ); - Debug.Assert( - origin != SeekOrigin.Current || Index + offset >= 0 && Index + offset < _buffer.Length, - "Attempting to seek to an invalid position using SeekOrigin.Current" - ); - - return Index = Math.Max(0, origin switch - { - SeekOrigin.Current => Index + offset, - SeekOrigin.End => _bytesWritten + offset, - _ => offset // Begin - }); - } - - public void Write(string value) - { - if (m_PrefixStrings) - { - if (value == null) - { - Write(false); - } - else - { - Write(true); - InternalWriteString(value); - } - } - else - { - InternalWriteString(value); - } - } - - public void Write(long value) - { - FlushIfNeeded(8); - - _buffer[Index++] = (byte)value; - _buffer[Index++] = (byte)(value >> 8); - _buffer[Index++] = (byte)(value >> 16); - _buffer[Index++] = (byte)(value >> 24); - _buffer[Index++] = (byte)(value >> 32); - _buffer[Index++] = (byte)(value >> 40); - _buffer[Index++] = (byte)(value >> 48); - _buffer[Index++] = (byte)(value >> 56); - } - - public void Write(ulong value) - { - FlushIfNeeded(8); - - _buffer[Index++] = (byte)value; - _buffer[Index++] = (byte)(value >> 8); - _buffer[Index++] = (byte)(value >> 16); - _buffer[Index++] = (byte)(value >> 24); - _buffer[Index++] = (byte)(value >> 32); - _buffer[Index++] = (byte)(value >> 40); - _buffer[Index++] = (byte)(value >> 48); - _buffer[Index++] = (byte)(value >> 56); - } - - public void Write(int value) - { - FlushIfNeeded(4); - - _buffer[Index++] = (byte)value; - _buffer[Index++] = (byte)(value >> 8); - _buffer[Index++] = (byte)(value >> 16); - _buffer[Index++] = (byte)(value >> 24); - } - - public void Write(uint value) - { - FlushIfNeeded(4); - - _buffer[Index++] = (byte)value; - _buffer[Index++] = (byte)(value >> 8); - _buffer[Index++] = (byte)(value >> 16); - _buffer[Index++] = (byte)(value >> 24); - } - - public void Write(short value) - { - FlushIfNeeded(2); - - _buffer[Index++] = (byte)value; - _buffer[Index++] = (byte)(value >> 8); - } - - public void Write(ushort value) - { - FlushIfNeeded(2); - - _buffer[Index++] = (byte)value; - _buffer[Index++] = (byte)(value >> 8); - } - - public unsafe void Write(double value) - { - FlushIfNeeded(8); - - fixed (byte* pBuffer = _buffer) - { - *(double*)(pBuffer + Index) = value; - } - - Index += 8; - } - - public unsafe void Write(float value) - { - FlushIfNeeded(4); - - fixed (byte* pBuffer = _buffer) - { - *(float*)(pBuffer + Index) = value; - } - - Index += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(byte value) - { - FlushIfNeeded(1); - _buffer[Index++] = value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(sbyte value) - { - FlushIfNeeded(1); - _buffer[Index++] = (byte)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void Write(bool value) - { - FlushIfNeeded(1); - _buffer[Index++] = *(byte*)&value; // up to 30% faster to dereference the raw value on the stack - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(Serial serial) => Write(serial.Value); - - internal void InternalWriteString(string value) - { - var remaining = m_Encoding.GetByteCount(value); - - ((IGenericWriter)this).WriteEncodedInt(remaining); - - if (remaining == 0) - { - return; - } - - // It is much faster to encode to stack buffer, then copy to the real buffer - Span span = stackalloc byte[Math.Min(BufferSize, 256)]; - var maxChars = span.Length / m_Encoding.GetMaxByteCount(1); - var charsLeft = value.Length; - var current = 0; - - while (charsLeft > 0) - { - var charCount = Math.Min(charsLeft, maxChars); - var bytesWritten = m_Encoding.GetBytes(value.AsSpan(current, charCount), span); - remaining -= bytesWritten; - charsLeft -= charCount; - current += charCount; - - Write(span[..bytesWritten]); + _bytesWritten = value; } } } + + private byte[] _buffer; + + public BufferWriter(byte[] buffer, bool prefixStr) + { + m_PrefixStrings = prefixStr; + m_Encoding = TextEncoding.UTF8; + _buffer = buffer; + } + + public BufferWriter(bool prefixStr) : this(0, prefixStr) + { + } + + public BufferWriter(int count, bool prefixStr) + { + m_PrefixStrings = prefixStr; + m_Encoding = TextEncoding.UTF8; + _buffer = GC.AllocateUninitializedArray(count < 1 ? BufferSize : count); + } + + public virtual long Position => Index; + + protected virtual int BufferSize => 256; + + public byte[] Buffer => _buffer; + + public virtual void Close() + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Resize(int size) + { + // We shouldn't ever resize to a 0 length buffer. That is dangerous + if (size <= 0) + { + size = BufferSize; + } + + if (size < _buffer.Length) + { + _bytesWritten = size; + } + + var newBuffer = GC.AllocateUninitializedArray(size); + _buffer.AsSpan(0, Math.Min(size, _buffer.Length)).CopyTo(newBuffer); + _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)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FlushIfNeeded(int amount) + { + if (Index + amount > _buffer.Length) + { + Flush(); + } + } + + public void Write(ReadOnlySpan bytes) + { + var remaining = bytes.Length; + var idx = 0; + + while (remaining > 0) + { + FlushIfNeeded(remaining); + + var count = Math.Min((int)(_buffer.Length - Index), remaining); + bytes.Slice(idx, count).CopyTo(_buffer.AsSpan((int)Index, count)); + + idx += count; + Index += count; + remaining -= count; + } + } + + public void Write(BitArray bitArray) + { + var byteLength = BitArray.GetByteArrayLengthFromBitLength(bitArray.Length); + + ((IGenericWriter)this).WriteEncodedInt(bitArray.Length); + FlushIfNeeded(byteLength); + bitArray.CopyTo(_buffer.AsSpan((int)Index, byteLength)); + Index += byteLength; + } + + public virtual long Seek(long offset, SeekOrigin origin) + { + Debug.Assert( + origin != SeekOrigin.End || offset <= 0 && offset > -_buffer.Length, + "Attempting to seek to an invalid position using SeekOrigin.End" + ); + Debug.Assert( + origin != SeekOrigin.Begin || offset >= 0 && offset < _buffer.Length, + "Attempting to seek to an invalid position using SeekOrigin.Begin" + ); + Debug.Assert( + origin != SeekOrigin.Current || Index + offset >= 0 && Index + offset < _buffer.Length, + "Attempting to seek to an invalid position using SeekOrigin.Current" + ); + + return Index = Math.Max(0, origin switch + { + SeekOrigin.Current => Index + offset, + SeekOrigin.End => _bytesWritten + offset, + _ => offset // Begin + }); + } + + public void Write(string value) + { + if (m_PrefixStrings) + { + if (value == null) + { + Write(false); + } + else + { + Write(true); + InternalWriteString(value); + } + } + else + { + InternalWriteString(value); + } + } + + public void Write(long value) + { + FlushIfNeeded(8); + + _buffer[Index++] = (byte)value; + _buffer[Index++] = (byte)(value >> 8); + _buffer[Index++] = (byte)(value >> 16); + _buffer[Index++] = (byte)(value >> 24); + _buffer[Index++] = (byte)(value >> 32); + _buffer[Index++] = (byte)(value >> 40); + _buffer[Index++] = (byte)(value >> 48); + _buffer[Index++] = (byte)(value >> 56); + } + + public void Write(ulong value) + { + FlushIfNeeded(8); + + _buffer[Index++] = (byte)value; + _buffer[Index++] = (byte)(value >> 8); + _buffer[Index++] = (byte)(value >> 16); + _buffer[Index++] = (byte)(value >> 24); + _buffer[Index++] = (byte)(value >> 32); + _buffer[Index++] = (byte)(value >> 40); + _buffer[Index++] = (byte)(value >> 48); + _buffer[Index++] = (byte)(value >> 56); + } + + public void Write(int value) + { + FlushIfNeeded(4); + + _buffer[Index++] = (byte)value; + _buffer[Index++] = (byte)(value >> 8); + _buffer[Index++] = (byte)(value >> 16); + _buffer[Index++] = (byte)(value >> 24); + } + + public void Write(uint value) + { + FlushIfNeeded(4); + + _buffer[Index++] = (byte)value; + _buffer[Index++] = (byte)(value >> 8); + _buffer[Index++] = (byte)(value >> 16); + _buffer[Index++] = (byte)(value >> 24); + } + + public void Write(short value) + { + FlushIfNeeded(2); + + _buffer[Index++] = (byte)value; + _buffer[Index++] = (byte)(value >> 8); + } + + public void Write(ushort value) + { + FlushIfNeeded(2); + + _buffer[Index++] = (byte)value; + _buffer[Index++] = (byte)(value >> 8); + } + + public unsafe void Write(double value) + { + FlushIfNeeded(8); + + fixed (byte* pBuffer = _buffer) + { + *(double*)(pBuffer + Index) = value; + } + + Index += 8; + } + + public unsafe void Write(float value) + { + FlushIfNeeded(4); + + fixed (byte* pBuffer = _buffer) + { + *(float*)(pBuffer + Index) = value; + } + + Index += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(byte value) + { + FlushIfNeeded(1); + _buffer[Index++] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(sbyte value) + { + FlushIfNeeded(1); + _buffer[Index++] = (byte)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Write(bool value) + { + FlushIfNeeded(1); + _buffer[Index++] = *(byte*)&value; // up to 30% faster to dereference the raw value on the stack + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(Serial serial) => Write(serial.Value); + + internal void InternalWriteString(string value) + { + var remaining = m_Encoding.GetByteCount(value); + + ((IGenericWriter)this).WriteEncodedInt(remaining); + + if (remaining == 0) + { + return; + } + + // It is much faster to encode to stack buffer, then copy to the real buffer + Span span = stackalloc byte[Math.Min(BufferSize, 256)]; + var maxChars = span.Length / m_Encoding.GetMaxByteCount(1); + var charsLeft = value.Length; + var current = 0; + + while (charsLeft > 0) + { + var charCount = Math.Min(charsLeft, maxChars); + var bytesWritten = m_Encoding.GetBytes(value.AsSpan(current, charCount), span); + remaining -= bytesWritten; + charsLeft -= charCount; + current += charCount; + + Write(span[..bytesWritten]); + } + } } diff --git a/Projects/Server/Serialization/GenericPersistence.cs b/Projects/Server/Serialization/GenericPersistence.cs index 36909b38c..355f21433 100644 --- a/Projects/Server/Serialization/GenericPersistence.cs +++ b/Projects/Server/Serialization/GenericPersistence.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GenericPersistence.cs * * * diff --git a/Projects/Server/Serialization/IGenericReader.cs b/Projects/Server/Serialization/IGenericReader.cs index 5abf069aa..837c6e212 100644 --- a/Projects/Server/Serialization/IGenericReader.cs +++ b/Projects/Server/Serialization/IGenericReader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IGenericReader.cs * * * @@ -18,107 +18,106 @@ using System.IO; using System.Net; using Server.Collections; -namespace Server +namespace Server; + +public interface IGenericReader { - public interface IGenericReader + // Used to determine valid Entity deserialization + DateTime LastSerialized { get; init; } + + string ReadString(bool intern = false); + long ReadLong(); + ulong ReadULong(); + int ReadInt(); + uint ReadUInt(); + short ReadShort(); + ushort ReadUShort(); + double ReadDouble(); + float ReadFloat(); + byte ReadByte(); + sbyte ReadSByte(); + bool ReadBool(); + Serial ReadSerial(); + + DateTime ReadDateTime() => new(ReadLong(), DateTimeKind.Utc); + TimeSpan ReadTimeSpan() => new(ReadLong()); + + DateTime ReadDeltaTime() { - // Used to determine valid Entity deserialization - DateTime LastSerialized { get; init; } - - string ReadString(bool intern = false); - long ReadLong(); - ulong ReadULong(); - int ReadInt(); - uint ReadUInt(); - short ReadShort(); - ushort ReadUShort(); - double ReadDouble(); - float ReadFloat(); - byte ReadByte(); - sbyte ReadSByte(); - bool ReadBool(); - Serial ReadSerial(); - - DateTime ReadDateTime() => new(ReadLong(), DateTimeKind.Utc); - TimeSpan ReadTimeSpan() => new(ReadLong()); - - DateTime ReadDeltaTime() + return ReadLong() switch { - return ReadLong() switch - { - long.MinValue => DateTime.MinValue, - long.MaxValue => DateTime.MaxValue, - var delta => new DateTime(delta + DateTime.UtcNow.Ticks, DateTimeKind.Utc) - }; - } - decimal ReadDecimal() => new(stackalloc int[4] { ReadInt(), ReadInt(), ReadInt(), ReadInt() }); - int ReadEncodedInt() - { - int v = 0, shift = 0; - byte b; - - do - { - b = ReadByte(); - v |= (b & 0x7F) << shift; - shift += 7; - } - while (b >= 0x80); - - return v; - } - IPAddress ReadIPAddress() - { - byte length = ReadByte(); - // Either 2 ushorts, or 8 ushorts - Span integer = stackalloc byte[length]; - Read(integer); - return Utility.Intern(new IPAddress(integer)); - } - Point3D ReadPoint3D() => new(ReadInt(), ReadInt(), ReadInt()); - Point2D ReadPoint2D() => new(ReadInt(), ReadInt()); - Rectangle2D ReadRect2D() => new(ReadPoint2D(), ReadPoint2D()); - Rectangle3D ReadRect3D() => new(ReadPoint3D(), ReadPoint3D()); - Map ReadMap() => Map.Maps[ReadByte()]; - Race ReadRace() => Race.Races[ReadByte()]; - int Read(Span buffer); - unsafe T ReadEnum() where T : unmanaged, Enum - { - switch (sizeof(T)) - { - case 1: - { - var num = ReadByte(); - return *(T*)# - } - case 2: - { - var num = ReadShort(); - return *(T*)# - } - case 4: - { - var num = ReadEncodedInt(); - return *(T*)# - } - case 8: - { - var num = ReadLong(); - return *(T*)# - } - } - - return default; - } - Guid ReadGuid() - { - Span bytes = stackalloc byte[16]; - Read(bytes); - return new Guid(bytes); - } - - BitArray ReadBitArray(); - - long Seek(long offset, SeekOrigin origin); + long.MinValue => DateTime.MinValue, + long.MaxValue => DateTime.MaxValue, + var delta => new DateTime(delta + DateTime.UtcNow.Ticks, DateTimeKind.Utc) + }; } + decimal ReadDecimal() => new(stackalloc int[4] { ReadInt(), ReadInt(), ReadInt(), ReadInt() }); + int ReadEncodedInt() + { + int v = 0, shift = 0; + byte b; + + do + { + b = ReadByte(); + v |= (b & 0x7F) << shift; + shift += 7; + } + while (b >= 0x80); + + return v; + } + IPAddress ReadIPAddress() + { + byte length = ReadByte(); + // Either 2 ushorts, or 8 ushorts + Span integer = stackalloc byte[length]; + Read(integer); + return Utility.Intern(new IPAddress(integer)); + } + Point3D ReadPoint3D() => new(ReadInt(), ReadInt(), ReadInt()); + Point2D ReadPoint2D() => new(ReadInt(), ReadInt()); + Rectangle2D ReadRect2D() => new(ReadPoint2D(), ReadPoint2D()); + Rectangle3D ReadRect3D() => new(ReadPoint3D(), ReadPoint3D()); + Map ReadMap() => Map.Maps[ReadByte()]; + Race ReadRace() => Race.Races[ReadByte()]; + int Read(Span buffer); + unsafe T ReadEnum() where T : unmanaged, Enum + { + switch (sizeof(T)) + { + case 1: + { + var num = ReadByte(); + return *(T*)# + } + case 2: + { + var num = ReadShort(); + return *(T*)# + } + case 4: + { + var num = ReadEncodedInt(); + return *(T*)# + } + case 8: + { + var num = ReadLong(); + return *(T*)# + } + } + + return default; + } + Guid ReadGuid() + { + Span bytes = stackalloc byte[16]; + Read(bytes); + return new Guid(bytes); + } + + BitArray ReadBitArray(); + + long Seek(long offset, SeekOrigin origin); } diff --git a/Projects/Server/Serialization/IGenericWriter.cs b/Projects/Server/Serialization/IGenericWriter.cs index 8eeec249f..97aa76ca5 100644 --- a/Projects/Server/Serialization/IGenericWriter.cs +++ b/Projects/Server/Serialization/IGenericWriter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: IGenericWriter.cs * * * @@ -18,151 +18,150 @@ using System.IO; using System.Net; using Server.Collections; -namespace Server +namespace Server; + +public interface IGenericWriter { - public interface IGenericWriter + long Position { get; } + void Close(); + void Write(string value); + void Write(long value); + void Write(ulong value); + void Write(int value); + void Write(uint value); + void Write(short value); + void Write(ushort value); + void Write(double value); + void Write(float value); + void Write(byte value); + void Write(sbyte value); + void Write(bool value); + void Write(Serial serial); + + void Write(DateTime value) { - long Position { get; } - void Close(); - void Write(string value); - void Write(long value); - void Write(ulong value); - void Write(int value); - void Write(uint value); - void Write(short value); - void Write(ushort value); - void Write(double value); - void Write(float value); - void Write(byte value); - void Write(sbyte value); - void Write(bool value); - void Write(Serial serial); - - void Write(DateTime value) + // If DateTimeKind is Unspecified, we can't assume it needs to be converted. + if (value.Kind == DateTimeKind.Local) { - // If DateTimeKind is Unspecified, we can't assume it needs to be converted. - if (value.Kind == DateTimeKind.Local) - { - value = value.ToUniversalTime(); - } - - Write(value.Ticks); - } - void WriteDeltaTime(DateTime value) - { - if (value == DateTime.MinValue) - { - Write(long.MinValue); - return; - } - - if (value == DateTime.MaxValue) - { - Write(long.MaxValue); - return; - } - - if (value.Kind == DateTimeKind.Local) - { - value = value.ToUniversalTime(); - } - - // Technically supports negative deltas for times in the past - Write(value.Ticks - DateTime.UtcNow.Ticks); - } - void Write(IPAddress value) - { - Span stack = stackalloc byte[16]; - value.TryWriteBytes(stack, out var bytesWritten); - Write((byte)bytesWritten); - Write(stack[..bytesWritten]); - } - void Write(TimeSpan value) - { - Write(value.Ticks); + value = value.ToUniversalTime(); } - public void Write(decimal value) - { - var bits = decimal.GetBits(value); - - for (var i = 0; i < 4; ++i) - { - Write(bits[i]); - } - } - void WriteEncodedInt(int value) - { - var v = (uint)value; - - while (v >= 0x80) - { - Write((byte)(v | 0x80)); - v >>= 7; - } - - Write((byte)v); - } - void Write(Point3D value) - { - Write(value.m_X); - Write(value.m_Y); - Write(value.m_Z); - } - void Write(Point2D value) - { - Write(value.m_X); - Write(value.m_Y); - } - void Write(Rectangle2D value) - { - Write(value.Start); - Write(value.End); - } - void Write(Rectangle3D value) - { - Write(value.Start); - Write(value.End); - } - void Write(Map value) => Write((byte)(value?.MapIndex ?? 0xFF)); - void Write(Race value) => Write((byte)(value?.RaceIndex ?? 0xFF)); - void Write(ReadOnlySpan bytes); - unsafe void WriteEnum(T value) where T : unmanaged, Enum - { - switch (sizeof(T)) - { - default: throw new ArgumentException($"Argument of type {typeof(T)} is not a normal enum"); - case 1: - { - Write(*(byte*)&value); - break; - } - case 2: - { - Write(*(ushort*)&value); - break; - } - case 4: - { - WriteEncodedInt(*(int*)&value); - break; - } - case 8: - { - Write(*(ulong*)&value); - break; - } - } - } - void Write(Guid guid) - { - Span stack = stackalloc byte[16]; - guid.TryWriteBytes(stack); - Write(stack); - } - - void Write(BitArray bitArray); - - long Seek(long offset, SeekOrigin origin); + Write(value.Ticks); } + void WriteDeltaTime(DateTime value) + { + if (value == DateTime.MinValue) + { + Write(long.MinValue); + return; + } + + if (value == DateTime.MaxValue) + { + Write(long.MaxValue); + return; + } + + if (value.Kind == DateTimeKind.Local) + { + value = value.ToUniversalTime(); + } + + // Technically supports negative deltas for times in the past + Write(value.Ticks - DateTime.UtcNow.Ticks); + } + void Write(IPAddress value) + { + Span stack = stackalloc byte[16]; + value.TryWriteBytes(stack, out var bytesWritten); + Write((byte)bytesWritten); + Write(stack[..bytesWritten]); + } + void Write(TimeSpan value) + { + Write(value.Ticks); + } + + public void Write(decimal value) + { + var bits = decimal.GetBits(value); + + for (var i = 0; i < 4; ++i) + { + Write(bits[i]); + } + } + void WriteEncodedInt(int value) + { + var v = (uint)value; + + while (v >= 0x80) + { + Write((byte)(v | 0x80)); + v >>= 7; + } + + Write((byte)v); + } + void Write(Point3D value) + { + Write(value.m_X); + Write(value.m_Y); + Write(value.m_Z); + } + void Write(Point2D value) + { + Write(value.m_X); + Write(value.m_Y); + } + void Write(Rectangle2D value) + { + Write(value.Start); + Write(value.End); + } + void Write(Rectangle3D value) + { + Write(value.Start); + Write(value.End); + } + void Write(Map value) => Write((byte)(value?.MapIndex ?? 0xFF)); + void Write(Race value) => Write((byte)(value?.RaceIndex ?? 0xFF)); + void Write(ReadOnlySpan bytes); + unsafe void WriteEnum(T value) where T : unmanaged, Enum + { + switch (sizeof(T)) + { + default: throw new ArgumentException($"Argument of type {typeof(T)} is not a normal enum"); + case 1: + { + Write(*(byte*)&value); + break; + } + case 2: + { + Write(*(ushort*)&value); + break; + } + case 4: + { + WriteEncodedInt(*(int*)&value); + break; + } + case 8: + { + Write(*(ulong*)&value); + break; + } + } + } + void Write(Guid guid) + { + Span stack = stackalloc byte[16]; + guid.TryWriteBytes(stack); + Write(stack); + } + + void Write(BitArray bitArray); + + long Seek(long offset, SeekOrigin origin); } diff --git a/Projects/Server/Serialization/ISerializable.cs b/Projects/Server/Serialization/ISerializable.cs index 3488c2c38..f49bd50f5 100644 --- a/Projects/Server/Serialization/ISerializable.cs +++ b/Projects/Server/Serialization/ISerializable.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ISerializable.cs * * * @@ -16,69 +16,68 @@ using System; using System.IO; -namespace Server +namespace Server; + +public interface ISerializable { - public interface ISerializable + // Should be serialized/deserialized with the index so it can be referenced by IGenericReader + DateTime Created { get; set; } + + // Should be serialized/deserialized with the index so it can be referenced by IGenericReader + DateTime LastSerialized { get; protected internal set; } + long SavePosition { get; protected internal set; } + BufferWriter SaveBuffer { get; protected internal set; } + + int TypeRef { get; } + Serial Serial { get; } + + // Executed on every entity, before it's serialized. + // For example, this is used to clean up weak references and mark them dirty. + void BeforeSerialize(); + void Deserialize(IGenericReader reader); + void Serialize(IGenericWriter writer); + void Delete(); + bool Deleted { get; } + + void SetTypeRef(Type type); + + public void InitializeSaveBuffer(byte[] buffer) { - // Should be serialized/deserialized with the index so it can be referenced by IGenericReader - DateTime Created { get; set; } - - // Should be serialized/deserialized with the index so it can be referenced by IGenericReader - DateTime LastSerialized { get; protected internal set; } - long SavePosition { get; protected internal set; } - BufferWriter SaveBuffer { get; protected internal set; } - - int TypeRef { get; } - Serial Serial { get; } - - // Executed on every entity, before it's serialized. - // For example, this is used to clean up weak references and mark them dirty. - void BeforeSerialize(); - void Deserialize(IGenericReader reader); - void Serialize(IGenericWriter writer); - void Delete(); - bool Deleted { get; } - - void SetTypeRef(Type type); - - public void InitializeSaveBuffer(byte[] buffer) + SaveBuffer = new BufferWriter(buffer, true); + if (World.DirtyTrackingEnabled) { - SaveBuffer = new BufferWriter(buffer, true); - if (World.DirtyTrackingEnabled) - { - SavePosition = SaveBuffer.Position; - } - else - { - SavePosition = -1; - } + SavePosition = SaveBuffer.Position; + } + else + { + SavePosition = -1; + } + } + + public void Serialize() + { + SaveBuffer ??= new BufferWriter(true); + + BeforeSerialize(); + + // Clean, don't bother serializing + if (SavePosition > -1) + { + SaveBuffer.Seek(SavePosition, SeekOrigin.Begin); + return; } - public void Serialize() + LastSerialized = Core.Now; + SaveBuffer.Seek(0, SeekOrigin.Begin); + Serialize(SaveBuffer); + + if (World.DirtyTrackingEnabled) { - SaveBuffer ??= new BufferWriter(true); - - BeforeSerialize(); - - // Clean, don't bother serializing - if (SavePosition > -1) - { - SaveBuffer.Seek(SavePosition, SeekOrigin.Begin); - return; - } - - LastSerialized = Core.Now; - SaveBuffer.Seek(0, SeekOrigin.Begin); - Serialize(SaveBuffer); - - if (World.DirtyTrackingEnabled) - { - SavePosition = SaveBuffer.Position; - } - else - { - this.MarkDirty(); - } + SavePosition = SaveBuffer.Position; + } + else + { + this.MarkDirty(); } } } diff --git a/Projects/Server/Serialization/ISerializableExtensions.cs b/Projects/Server/Serialization/ISerializableExtensions.cs index 15ecb4f1c..687d73876 100644 --- a/Projects/Server/Serialization/ISerializableExtensions.cs +++ b/Projects/Server/Serialization/ISerializableExtensions.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ISerializableExtensions.cs * * * @@ -17,165 +17,164 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public static class ISerializableExtensions { - public static class ISerializableExtensions + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MarkDirty(this ISerializable entity) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void MarkDirty(this ISerializable entity) + if (entity != null) { - if (entity != null) - { - entity.SavePosition = -1; - } + entity.SavePosition = -1; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Delete(this ISerializable entity, IEntity toDelete) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Delete(this ISerializable entity, IEntity toDelete) + { + toDelete?.Delete(); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this ISerializable entity, ICollection list, T value) + { + list.Add(value); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this ISerializable entity, IDictionary dict, K key, V value) + { + dict[key] = value; + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Insert(this ISerializable entity, IList list, T value, int index) + { + list.Insert(index, value); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Remove(this ISerializable entity, ICollection list, T value) + { + if (list.Remove(value)) { - toDelete?.Delete(); entity.MarkDirty(); + return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add(this ISerializable entity, ICollection list, T value) + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Remove(this ISerializable entity, IDictionary dict, K key, out V value) + { + if (dict.Remove(key, out value)) { - list.Add(value); entity.MarkDirty(); + return true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add(this ISerializable entity, IDictionary dict, K key, V value) + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RemoveAt(this ISerializable entity, IList list, int index) + { + list.RemoveAt(index); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this ISerializable entity, ICollection list) + { + list.Clear(); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Stop(this ISerializable entity, Timer timer) + { + timer?.Stop(); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Start(this ISerializable entity, Timer timer) + { + timer?.Start(); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Restart(this ISerializable entity, Timer timer, TimeSpan delay, TimeSpan interval) + { + if (timer != null) { - dict[key] = value; - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Insert(this ISerializable entity, IList list, T value, int index) - { - list.Insert(index, value); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Remove(this ISerializable entity, ICollection list, T value) - { - if (list.Remove(value)) - { - entity.MarkDirty(); - return true; - } - - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Remove(this ISerializable entity, IDictionary dict, K key, out V value) - { - if (dict.Remove(key, out value)) - { - entity.MarkDirty(); - return true; - } - - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void RemoveAt(this ISerializable entity, IList list, int index) - { - list.RemoveAt(index); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clear(this ISerializable entity, ICollection list) - { - list.Clear(); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Stop(this ISerializable entity, Timer timer) - { - timer?.Stop(); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Start(this ISerializable entity, Timer timer) - { - timer?.Start(); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Restart(this ISerializable entity, Timer timer, TimeSpan delay, TimeSpan interval) - { - if (timer != null) - { - timer.Stop(); - timer.Delay = delay; - timer.Interval = interval; - timer.Start(); - entity.MarkDirty(); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add(this ISerializable entity, ref List list, T value) - { - Utility.Add(ref list, value); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add(this ISerializable entity, ref Dictionary dict, K key, V value) - { - Utility.Add(ref dict, key, value); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Remove(this ISerializable entity, ref List list, T value) - { - if (Utility.Remove(ref list, value)) - { - entity.MarkDirty(); - return true; - } - - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clear(this ISerializable entity, ref List list) - { - Utility.Clear(ref list); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clear(this ISerializable entity, ref HashSet set) - { - Utility.Clear(ref set); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Clear(this ISerializable entity, ref Dictionary dict) - { - Utility.Clear(ref dict); - entity.MarkDirty(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Stop(this ISerializable entity, ref Timer timer) - { - timer?.Stop(); - timer = null; + timer.Stop(); + timer.Delay = delay; + timer.Interval = interval; + timer.Start(); entity.MarkDirty(); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this ISerializable entity, ref List list, T value) + { + Utility.Add(ref list, value); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this ISerializable entity, ref Dictionary dict, K key, V value) + { + Utility.Add(ref dict, key, value); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Remove(this ISerializable entity, ref List list, T value) + { + if (Utility.Remove(ref list, value)) + { + entity.MarkDirty(); + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this ISerializable entity, ref List list) + { + Utility.Clear(ref list); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this ISerializable entity, ref HashSet set) + { + Utility.Clear(ref set); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this ISerializable entity, ref Dictionary dict) + { + Utility.Clear(ref dict); + entity.MarkDirty(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Stop(this ISerializable entity, ref Timer timer) + { + timer?.Stop(); + timer = null; + entity.MarkDirty(); + } } diff --git a/Projects/Server/Serialization/Persistence.cs b/Projects/Server/Serialization/Persistence.cs index 8c76165c7..048b087ba 100644 --- a/Projects/Server/Serialization/Persistence.cs +++ b/Projects/Server/Serialization/Persistence.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Persistence.cs * * * @@ -18,112 +18,111 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -namespace Server +namespace Server; + +public class Persistence { - public class Persistence + public const int DefaultPriority = 100; + + private static readonly SortedSet _registry = new(new RegistryEntryComparer()); + + public static void Register( + string name, + Action serializer, + Action snapshotWriter, + Action deserializer, + int priority = DefaultPriority + ) { - public const int DefaultPriority = 100; - - private static readonly SortedSet _registry = new(new RegistryEntryComparer()); - - public static void Register( - string name, - Action serializer, - Action snapshotWriter, - Action deserializer, - int priority = DefaultPriority - ) - { - _registry.Add( - new RegistryEntry - { - Name = name, - Priority = priority, - Serialize = serializer, - WriteSnapshot = snapshotWriter, - Deserialize = deserializer - } - ); - } - - public static void Unregister(string name) => _registry.RemoveWhere(entry => entry.Name == name); - - public static void Load(string path) - { - // This should probably not be parallel since Mobiles must be loaded before Items - foreach (var entry in _registry) + _registry.Add( + new RegistryEntry { - entry.Deserialize(path); + Name = name, + Priority = priority, + Serialize = serializer, + WriteSnapshot = snapshotWriter, + Deserialize = deserializer } - } + ); + } - public static void Serialize() + public static void Unregister(string name) => _registry.RemoveWhere(entry => entry.Name == name); + + public static void Load(string path) + { + // This should probably not be parallel since Mobiles must be loaded before Items + foreach (var entry in _registry) { - Parallel.ForEach(_registry, entry => entry.Serialize()); - } - - public static void WriteSnapshot(string path) - { - foreach (var entry in _registry) - { - entry.WriteSnapshot(path); - } - } - - public record RegistryEntry - { - public string Name { get; init; } - public int Priority { get; init; } - public Action Serialize { get; init; } // Serializing to memory buffers - public Action WriteSnapshot { get; init; } - public Action Deserialize { get; init; } - } - - internal class RegistryEntryComparer : IComparer - { - public int Compare(RegistryEntry x, RegistryEntry y) - { - if (x == y) - { - return 0; - } - - if (x == null) - { - return 1; - } - - if (y == null) - { - return -1; - } - - // First sort by priority - var cmp = x.Priority.CompareTo(y.Priority); - - // Then alphabetically. We won't allow the same entry (by name) twice in the SortedSet - return cmp != 0 ? cmp : x.Name?.CompareOrdinal(y.Name) ?? -1; - } - } - - public static void TraceException(Exception ex) - { - try - { - using var op = new StreamWriter("save-errors.log", true); - op.WriteLine("# {0}", Core.Now); - - op.WriteLine(ex); - - op.WriteLine(); - op.WriteLine(); - } - catch - { - // ignored - } - - Console.WriteLine(ex); + entry.Deserialize(path); } } + + public static void Serialize() + { + Parallel.ForEach(_registry, entry => entry.Serialize()); + } + + public static void WriteSnapshot(string path) + { + foreach (var entry in _registry) + { + entry.WriteSnapshot(path); + } + } + + public record RegistryEntry + { + public string Name { get; init; } + public int Priority { get; init; } + public Action Serialize { get; init; } // Serializing to memory buffers + public Action WriteSnapshot { get; init; } + public Action Deserialize { get; init; } + } + + internal class RegistryEntryComparer : IComparer + { + public int Compare(RegistryEntry x, RegistryEntry y) + { + if (x == y) + { + return 0; + } + + if (x == null) + { + return 1; + } + + if (y == null) + { + return -1; + } + + // First sort by priority + var cmp = x.Priority.CompareTo(y.Priority); + + // Then alphabetically. We won't allow the same entry (by name) twice in the SortedSet + return cmp != 0 ? cmp : x.Name?.CompareOrdinal(y.Name) ?? -1; + } + } + + public static void TraceException(Exception ex) + { + try + { + using var op = new StreamWriter("save-errors.log", true); + op.WriteLine("# {0}", Core.Now); + + op.WriteLine(ex); + + op.WriteLine(); + op.WriteLine(); + } + catch + { + // ignored + } + + Console.WriteLine(ex); + } } diff --git a/Projects/Server/Serialization/SerializationExtensions.cs b/Projects/Server/Serialization/SerializationExtensions.cs index a9d6d94a2..4ee793941 100644 --- a/Projects/Server/Serialization/SerializationExtensions.cs +++ b/Projects/Server/Serialization/SerializationExtensions.cs @@ -1,3 +1,18 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2022 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SerializationExtensions.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 Server.Guilds; diff --git a/Projects/Server/Skills.cs b/Projects/Server/Skills.cs index 6ad91ebbf..c0a931e86 100644 --- a/Projects/Server/Skills.cs +++ b/Projects/Server/Skills.cs @@ -2,936 +2,935 @@ using System; using System.Runtime.CompilerServices; using Server.Network; -namespace Server +namespace Server; + +public delegate TimeSpan SkillUseCallback(Mobile user); + +public enum SkillLock : byte { - public delegate TimeSpan SkillUseCallback(Mobile user); + Up = 0, + Down = 1, + Locked = 2 +} - public enum SkillLock : byte +public enum SkillName +{ + Alchemy = 0, + Anatomy = 1, + AnimalLore = 2, + ItemID = 3, + ArmsLore = 4, + Parry = 5, + Begging = 6, + Blacksmith = 7, + Fletching = 8, + Peacemaking = 9, + Camping = 10, + Carpentry = 11, + Cartography = 12, + Cooking = 13, + DetectHidden = 14, + Discordance = 15, + EvalInt = 16, + Healing = 17, + Fishing = 18, + Forensics = 19, + Herding = 20, + Hiding = 21, + Provocation = 22, + Inscribe = 23, + Lockpicking = 24, + Magery = 25, + MagicResist = 26, + Tactics = 27, + Snooping = 28, + Musicianship = 29, + Poisoning = 30, + Archery = 31, + SpiritSpeak = 32, + Stealing = 33, + Tailoring = 34, + AnimalTaming = 35, + TasteID = 36, + Tinkering = 37, + Tracking = 38, + Veterinary = 39, + Swords = 40, + Macing = 41, + Fencing = 42, + Wrestling = 43, + Lumberjacking = 44, + Mining = 45, + Meditation = 46, + Stealth = 47, + RemoveTrap = 48, + Necromancy = 49, + Focus = 50, + Chivalry = 51, + Bushido = 52, + Ninjitsu = 53, + Spellweaving = 54, + Mysticism = 55, + Imbuing = 56, + Throwing = 57 +} + +[PropertyObject] +public class Skill +{ + private ushort m_Base; + private ushort m_Cap; + + public Skill(Skills owner, SkillInfo info, IGenericReader reader) { - Up = 0, - Down = 1, - Locked = 2 - } + Owner = owner; + Info = info; - public enum SkillName - { - Alchemy = 0, - Anatomy = 1, - AnimalLore = 2, - ItemID = 3, - ArmsLore = 4, - Parry = 5, - Begging = 6, - Blacksmith = 7, - Fletching = 8, - Peacemaking = 9, - Camping = 10, - Carpentry = 11, - Cartography = 12, - Cooking = 13, - DetectHidden = 14, - Discordance = 15, - EvalInt = 16, - Healing = 17, - Fishing = 18, - Forensics = 19, - Herding = 20, - Hiding = 21, - Provocation = 22, - Inscribe = 23, - Lockpicking = 24, - Magery = 25, - MagicResist = 26, - Tactics = 27, - Snooping = 28, - Musicianship = 29, - Poisoning = 30, - Archery = 31, - SpiritSpeak = 32, - Stealing = 33, - Tailoring = 34, - AnimalTaming = 35, - TasteID = 36, - Tinkering = 37, - Tracking = 38, - Veterinary = 39, - Swords = 40, - Macing = 41, - Fencing = 42, - Wrestling = 43, - Lumberjacking = 44, - Mining = 45, - Meditation = 46, - Stealth = 47, - RemoveTrap = 48, - Necromancy = 49, - Focus = 50, - Chivalry = 51, - Bushido = 52, - Ninjitsu = 53, - Spellweaving = 54, - Mysticism = 55, - Imbuing = 56, - Throwing = 57 - } + int version = reader.ReadByte(); - [PropertyObject] - public class Skill - { - private ushort m_Base; - private ushort m_Cap; - - public Skill(Skills owner, SkillInfo info, IGenericReader reader) + switch (version) { - Owner = owner; - Info = info; + case 0: + { + m_Base = reader.ReadUShort(); + m_Cap = reader.ReadUShort(); + Lock = (SkillLock)reader.ReadByte(); - int version = reader.ReadByte(); + break; + } + case 0xFF: + { + m_Base = 0; + m_Cap = 1000; + Lock = SkillLock.Up; - switch (version) - { - case 0: + break; + } + default: + { + if ((version & 0xC0) == 0x00) { - m_Base = reader.ReadUShort(); - m_Cap = reader.ReadUShort(); - Lock = (SkillLock)reader.ReadByte(); - - break; - } - case 0xFF: - { - m_Base = 0; - m_Cap = 1000; - Lock = SkillLock.Up; - - break; - } - default: - { - if ((version & 0xC0) == 0x00) + if ((version & 0x1) != 0) { - if ((version & 0x1) != 0) - { - m_Base = reader.ReadUShort(); - } - - if ((version & 0x2) != 0) - { - m_Cap = reader.ReadUShort(); - } - else - { - m_Cap = 1000; - } - - if ((version & 0x4) != 0) - { - Lock = (SkillLock)reader.ReadByte(); - } + m_Base = reader.ReadUShort(); } - break; - } - } - - if (Lock is < SkillLock.Up or > SkillLock.Locked) - { - Lock = SkillLock.Up; - } - } - - public Skill(Skills owner, SkillInfo info, int baseValue, int cap, SkillLock skillLock) - { - Owner = owner; - Info = info; - m_Base = (ushort)baseValue; - m_Cap = (ushort)cap; - Lock = skillLock; - } - - public Skills Owner { get; } - - public SkillName SkillName => (SkillName)Info.SkillID; - - public int SkillID => Info.SkillID; - - [CommandProperty(AccessLevel.Counselor)] - public string Name => Info.Name; - - public SkillInfo Info { get; } - - [CommandProperty(AccessLevel.Counselor)] - public SkillLock Lock { get; private set; } - - public int BaseFixedPoint - { - get => m_Base; - set - { - var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); - - int oldBase = m_Base; - - if (m_Base != sv) - { - Owner.Total = Owner.Total - m_Base + sv; - - m_Base = sv; - - Owner.OnSkillChange(this); - - var m = Owner.Owner; - - m?.OnSkillChange(SkillName, (double)oldBase / 10); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public double Base - { - get => m_Base / 10.0; - set => BaseFixedPoint = (int)(value * 10.0); - } - - public int CapFixedPoint - { - get => m_Cap; - set - { - var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); - - if (m_Cap != sv) - { - m_Cap = sv; - - Owner.OnSkillChange(this); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public double Cap - { - get => m_Cap / 10.0; - set => CapFixedPoint = (int)(value * 10.0); - } - - public static bool UseStatMods { get; set; } - - public int Fixed => (int)(Value * 10); - - [CommandProperty(AccessLevel.Counselor)] - public double Value - { - get - { - // There has to be this distinction between the racial values and not to account for gaining skills and these skills aren't displayed nor Totaled up. - var value = NonRacialValue; - - var raceBonus = Owner.Owner.RacialSkillBonus; - - if (raceBonus > value) - { - value = raceBonus; - } - - return value; - } - } - - [CommandProperty(AccessLevel.Counselor)] - public double NonRacialValue - { - get - { - var baseValue = Base; - var inv = 100.0 - baseValue; - - if (inv < 0.0) - { - inv = 0.0; - } - - inv /= 100.0; - - var statsOffset = (UseStatMods ? Owner.Owner.Str : Owner.Owner.RawStr) * Info.StrScale + - (UseStatMods ? Owner.Owner.Dex : Owner.Owner.RawDex) * Info.DexScale + - (UseStatMods ? Owner.Owner.Int : Owner.Owner.RawInt) * Info.IntScale; - var statTotal = Info.StatTotal * inv; - - statsOffset *= inv; - - if (statsOffset > statTotal) - { - statsOffset = statTotal; - } - - var value = baseValue + statsOffset; - - Owner.Owner.ValidateSkillMods(); - - var mods = Owner.Owner.SkillMods; - - double bonusObey = 0.0, bonusNotObey = 0.0; - - foreach (var mod in mods) - { - if (mod.Skill != (SkillName)Info.SkillID) - { - continue; - } - - if (mod.Relative) - { - if (mod.ObeyCap) + if ((version & 0x2) != 0) { - bonusObey += mod.Value; + m_Cap = reader.ReadUShort(); } else { - bonusNotObey += mod.Value; + m_Cap = 1000; } + + if ((version & 0x4) != 0) + { + Lock = (SkillLock)reader.ReadByte(); + } + } + + break; + } + } + + if (Lock is < SkillLock.Up or > SkillLock.Locked) + { + Lock = SkillLock.Up; + } + } + + public Skill(Skills owner, SkillInfo info, int baseValue, int cap, SkillLock skillLock) + { + Owner = owner; + Info = info; + m_Base = (ushort)baseValue; + m_Cap = (ushort)cap; + Lock = skillLock; + } + + public Skills Owner { get; } + + public SkillName SkillName => (SkillName)Info.SkillID; + + public int SkillID => Info.SkillID; + + [CommandProperty(AccessLevel.Counselor)] + public string Name => Info.Name; + + public SkillInfo Info { get; } + + [CommandProperty(AccessLevel.Counselor)] + public SkillLock Lock { get; private set; } + + public int BaseFixedPoint + { + get => m_Base; + set + { + var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); + + int oldBase = m_Base; + + if (m_Base != sv) + { + Owner.Total = Owner.Total - m_Base + sv; + + m_Base = sv; + + Owner.OnSkillChange(this); + + var m = Owner.Owner; + + m?.OnSkillChange(SkillName, (double)oldBase / 10); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public double Base + { + get => m_Base / 10.0; + set => BaseFixedPoint = (int)(value * 10.0); + } + + public int CapFixedPoint + { + get => m_Cap; + set + { + var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); + + if (m_Cap != sv) + { + m_Cap = sv; + + Owner.OnSkillChange(this); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public double Cap + { + get => m_Cap / 10.0; + set => CapFixedPoint = (int)(value * 10.0); + } + + public static bool UseStatMods { get; set; } + + public int Fixed => (int)(Value * 10); + + [CommandProperty(AccessLevel.Counselor)] + public double Value + { + get + { + // There has to be this distinction between the racial values and not to account for gaining skills and these skills aren't displayed nor Totaled up. + var value = NonRacialValue; + + var raceBonus = Owner.Owner.RacialSkillBonus; + + if (raceBonus > value) + { + value = raceBonus; + } + + return value; + } + } + + [CommandProperty(AccessLevel.Counselor)] + public double NonRacialValue + { + get + { + var baseValue = Base; + var inv = 100.0 - baseValue; + + if (inv < 0.0) + { + inv = 0.0; + } + + inv /= 100.0; + + var statsOffset = (UseStatMods ? Owner.Owner.Str : Owner.Owner.RawStr) * Info.StrScale + + (UseStatMods ? Owner.Owner.Dex : Owner.Owner.RawDex) * Info.DexScale + + (UseStatMods ? Owner.Owner.Int : Owner.Owner.RawInt) * Info.IntScale; + var statTotal = Info.StatTotal * inv; + + statsOffset *= inv; + + if (statsOffset > statTotal) + { + statsOffset = statTotal; + } + + var value = baseValue + statsOffset; + + Owner.Owner.ValidateSkillMods(); + + var mods = Owner.Owner.SkillMods; + + double bonusObey = 0.0, bonusNotObey = 0.0; + + foreach (var mod in mods) + { + if (mod.Skill != (SkillName)Info.SkillID) + { + continue; + } + + if (mod.Relative) + { + if (mod.ObeyCap) + { + bonusObey += mod.Value; } else { - bonusObey = 0.0; - bonusNotObey = 0.0; - value = mod.Value; + bonusNotObey += mod.Value; } } - - value += bonusNotObey; - - if (value < Cap) - { - value += bonusObey; - - if (value > Cap) - { - value = Cap; - } - } - - return value; - } - } - - public override string ToString() => $"[{Name}: {Base}]"; - - public void SetLockNoRelay(SkillLock skillLock) - { - if (skillLock is < SkillLock.Up or > SkillLock.Locked) - { - return; - } - - Lock = skillLock; - } - - public void Serialize(IGenericWriter writer) - { - if (m_Base == 0 && m_Cap == 1000 && Lock == SkillLock.Up) - { - writer.Write((byte)0xFF); // default - } - else - { - var flags = 0x0; - - if (m_Base != 0) - { - flags |= 0x1; - } - - if (m_Cap != 1000) - { - flags |= 0x2; - } - - if (Lock != SkillLock.Up) - { - flags |= 0x4; - } - - writer.Write((byte)flags); // version - - if (m_Base != 0) - { - writer.Write((short)m_Base); - } - - if (m_Cap != 1000) - { - writer.Write((short)m_Cap); - } - - if (Lock != SkillLock.Up) - { - writer.Write((byte)Lock); - } - } - } - - public void Update() - { - Owner.OnSkillChange(this); - } - } - - public class SkillInfo - { - public SkillInfo( - int skillID, string name, double strScale, double dexScale, double intScale, string title, - SkillUseCallback callback, double strGain, double dexGain, double intGain, double gainFactor, - string professionSkillName = null - ) - { - Name = name; - Title = title; - SkillID = skillID; - StrScale = strScale / 100.0; - DexScale = dexScale / 100.0; - IntScale = intScale / 100.0; - Callback = callback; - StrGain = strGain; - DexGain = dexGain; - IntGain = intGain; - GainFactor = gainFactor; - ProfessionSkillName = professionSkillName ?? Name.Replace(" ", ""); - StatTotal = strScale + dexScale + intScale; - } - - public SkillUseCallback Callback { get; set; } - - public int SkillID { get; } - - public string Name { get; set; } - - public string Title { get; set; } - - public double StrScale { get; set; } - - public double DexScale { get; set; } - - public double IntScale { get; set; } - - public double StatTotal { get; set; } - - public double StrGain { get; set; } - - public double DexGain { get; set; } - - public double IntGain { get; set; } - - public double GainFactor { get; set; } - - public string ProfessionSkillName { get; set; } - - public static SkillInfo[] Table { get; set; } = - { - new(0, "Alchemy", 0.0, 5.0, 5.0, "Alchemist", null, 0.0, 0.5, 0.5, 1.0), - new(1, "Anatomy", 0.0, 0.0, 0.0, "Biologist", null, 0.15, 0.15, 0.7, 1.0), - new(2, "Animal Lore", 0.0, 0.0, 0.0, "Naturalist", null, 0.0, 0.0, 1.0, 1.0), - new(3, "Item Identification", 0.0, 0.0, 0.0, "Merchant", null, 0.0, 0.0, 1.0, 1.0, "ItemID"), - new(4, "Arms Lore", 0.0, 0.0, 0.0, "Weapon Master", null, 0.75, 0.15, 0.1, 1.0), - new(5, "Parrying", 7.5, 2.5, 0.0, "Duelist", null, 0.75, 0.25, 0.0, 1.0), - new(6, "Begging", 0.0, 0.0, 0.0, "Beggar", null, 0.0, 0.0, 0.0, 1.0), - new(7, "Blacksmithy", 10.0, 0.0, 0.0, "Blacksmith", null, 1.0, 0.0, 0.0, 1.0, "Blacksmith"), - new(8, "Bowcraft/Fletching", 6.0, 16.0, 0.0, "Bowyer", null, 0.6, 1.6, 0.0, 1.0, "Bowcraft"), - new(9, "Peacemaking", 0.0, 0.0, 0.0, "Pacifier", null, 0.0, 0.0, 0.0, 1.0), - new(10, "Camping", 20.0, 15.0, 15.0, "Explorer", null, 2.0, 1.5, 1.5, 1.0), - new(11, "Carpentry", 20.0, 5.0, 0.0, "Carpenter", null, 2.0, 0.5, 0.0, 1.0), - new(12, "Cartography", 0.0, 7.5, 7.5, "Cartographer", null, 0.0, 0.75, 0.75, 1.0), - new(13, "Cooking", 0.0, 20.0, 30.0, "Chef", null, 0.0, 2.0, 3.0, 1.0), - new(14, "Detecting Hidden", 0.0, 0.0, 0.0, "Scout", null, 0.0, 0.4, 0.6, 1.0), - new(15, "Discordance", 0.0, 2.5, 2.5, "Demoralizer", null, 0.0, 0.25, 0.25, 1.0, "Enticement"), - new(16, "Evaluating Intelligence", 0.0, 0.0, 0.0, "Scholar", null, 0.0, 0.0, 1.0, 1.0, "EvaluateIntelligence"), - new(17, "Healing", 6.0, 6.0, 8.0, "Healer", null, 0.6, 0.6, 0.8, 1.0), - new(18, "Fishing", 0.0, 0.0, 0.0, "Fisherman", null, 0.5, 0.5, 0.0, 1.0), - new(19, "Forensic Evaluation", 0.0, 0.0, 0.0, "Detective", null, 0.0, 0.2, 0.8, 1.0), - new(20, "Herding", 16.25, 6.25, 2.5, "Shepherd", null, 1.625, 0.625, 0.25, 1.0), - new(21, "Hiding", 0.0, 0.0, 0.0, "Shade", null, 0.0, 0.8, 0.2, 1.0), - new(22, "Provocation", 0.0, 4.5, 0.5, "Rouser", null, 0.0, 0.45, 0.05, 1.0), - new(23, "Inscription", 0.0, 2.0, 8.0, "Scribe", null, 0.0, 0.2, 0.8, 1.0), - new(24, "Lockpicking", 0.0, 25.0, 0.0, "Infiltrator", null, 0.0, 2.0, 0.0, 1.0), - new(25, "Magery", 0.0, 0.0, 15.0, "Mage", null, 0.0, 0.0, 1.5, 1.0), - new(26, "Resisting Spells", 0.0, 0.0, 0.0, "Warder", null, 0.25, 0.25, 0.5, 1.0), - new(27, "Tactics", 0.0, 0.0, 0.0, "Tactician", null, 0.0, 0.0, 0.0, 1.0), - new(28, "Snooping", 0.0, 25.0, 0.0, "Spy", null, 0.0, 2.5, 0.0, 1.0), - new(29, "Musicianship", 0.0, 0.0, 0.0, "Bard", null, 0.0, 0.8, 0.2, 1.0), - new(30, "Poisoning", 0.0, 4.0, 16.0, "Assassin", null, 0.0, 0.4, 1.6, 1.0), - new(31, "Archery", 2.5, 7.5, 0.0, "Archer", null, 0.25, 0.75, 0.0, 1.0), - new(32, "Spirit Speak", 0.0, 0.0, 0.0, "Medium", null, 0.0, 0.0, 1.0, 1.0), - new(33, "Stealing", 0.0, 10.0, 0.0, "Pickpocket", null, 0.0, 1.0, 0.0, 1.0), - new(34, "Tailoring", 3.75, 16.25, 5.0, "Tailor", null, 0.38, 1.63, 0.5, 1.0), - new(35, "Animal Taming", 14.0, 2.0, 4.0, "Tamer", null, 1.4, 0.2, 0.4, 1.0), - new(36, "Taste Identification", 0.0, 0.0, 0.0, "Praegustator", null, 0.2, 0.0, 0.8, 1.0), - new(37, "Tinkering", 5.0, 2.0, 3.0, "Tinker", null, 0.5, 0.2, 0.3, 1.0), - new(38, "Tracking", 0.0, 12.5, 12.5, "Ranger", null, 0.0, 1.25, 1.25, 1.0), - new(39, "Veterinary", 8.0, 4.0, 8.0, "Veterinarian", null, 0.8, 0.4, 0.8, 1.0), - new(40, "Swordsmanship", 7.5, 2.5, 0.0, "Swordsman", null, 0.75, 0.25, 0.0, 1.0), - new(41, "Mace Fighting", 9.0, 1.0, 0.0, "Armsman", null, 0.9, 0.1, 0.0, 1.0), - new(42, "Fencing", 4.5, 5.5, 0.0, "Fencer", null, 0.45, 0.55, 0.0, 1.0), - new(43, "Wrestling", 9.0, 1.0, 0.0, "Wrestler", null, 0.9, 0.1, 0.0, 1.0), - new(44, "Lumberjacking", 20.0, 0.0, 0.0, "Lumberjack", null, 2.0, 0.0, 0.0, 1.0), - new(45, "Mining", 20.0, 0.0, 0.0, "Miner", null, 2.0, 0.0, 0.0, 1.0), - new(46, "Meditation", 0.0, 0.0, 0.0, "Stoic", null, 0.0, 0.0, 0.0, 1.0), - new(47, "Stealth", 0.0, 0.0, 0.0, "Rogue", null, 0.0, 0.0, 0.0, 1.0), - new(48, "Remove Trap", 0.0, 0.0, 0.0, "Trap Specialist", null, 0.0, 0.0, 0.0, 1.0, "Disarm"), - new(49, "Necromancy", 0.0, 0.0, 0.0, "Necromancer", null, 0.0, 0.0, 0.0, 1.0), - new(50, "Focus", 0.0, 0.0, 0.0, "Driven", null, 0.0, 0.0, 0.0, 1.0), - new(51, "Chivalry", 0.0, 0.0, 0.0, "Paladin", null, 0.0, 0.0, 0.0, 1.0), - new(52, "Bushido", 0.0, 0.0, 0.0, "Samurai", null, 0.0, 0.0, 0.0, 1.0), - new(53, "Ninjitsu", 0.0, 0.0, 0.0, "Ninja", null, 0.0, 0.0, 0.0, 1.0), - new(54, "Spellweaving", 0.0, 0.0, 0.0, "Arcanist", null, 0.0, 0.0, 0.0, 1.0), - new(55, "Mysticism", 0.0, 0.0, 0.0, "Mystic", null, 0.0, 0.0, 0.0, 1.0), - new(56, "Imbuing", 0.0, 0.0, 0.0, "Artificer", null, 0.0, 0.0, 0.0, 1.0), - new(57, "Throwing", 0.0, 0.0, 0.0, "Bladeweaver", null, 0.0, 0.0, 0.0, 1.0) - }; - } - - [PropertyObject] - public class Skills - { - private readonly Skill[] m_Skills; - private Skill m_Highest; - - public Skills(Mobile owner) - { - Owner = owner; - Cap = 7000; - - var info = SkillInfo.Table; - - m_Skills = new Skill[info.Length]; - } - - public Skills(Mobile owner, IGenericReader reader) - { - Owner = owner; - - var version = reader.ReadInt(); - - switch (version) - { - case 3: - case 2: - { - Cap = reader.ReadInt(); - - goto case 1; - } - case 1: - { - if (version < 2) - { - Cap = 7000; - } - - if (version < 3) - /*m_Total =*/ - { - reader.ReadInt(); - } - - var info = SkillInfo.Table; - - m_Skills = new Skill[info.Length]; - - var count = reader.ReadInt(); - - for (var i = 0; i < count; ++i) - { - if (i < info.Length) - { - var sk = new Skill(this, info[i], reader); - - if (sk.BaseFixedPoint != 0 || sk.CapFixedPoint != 1000 || sk.Lock != SkillLock.Up) - { - m_Skills[i] = sk; - Total += sk.BaseFixedPoint; - } - } - else - { - // Will be discarded - _ = new Skill(this, null, reader); - } - } - - // for ( int i = count; i < info.Length; ++i ) - // m_Skills[i] = new Skill( this, info[i], 0, 1000, SkillLock.Up ); - - break; - } - case 0: - { - reader.ReadInt(); - - goto case 1; - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Cap { get; set; } - - public int Total { get; set; } - - public Mobile Owner { get; } - - public int Length => m_Skills.Length; - - public Skill this[SkillName name] => this[(int)name]; - - public Skill this[int skillID] - { - get - { - if (skillID < 0 || skillID >= m_Skills.Length) - { - return null; - } - - var sk = m_Skills[skillID]; - - if (sk == null) - { - m_Skills[skillID] = sk = new Skill(this, SkillInfo.Table[skillID], 0, 1000, SkillLock.Up); - } - - return sk; - } - } - - public Skill Highest - { - get - { - if (m_Highest == null) - { - Skill highest = null; - var value = int.MinValue; - - for (var i = 0; i < m_Skills.Length; ++i) - { - var sk = m_Skills[i]; - - if (sk?.BaseFixedPoint > value) - { - value = sk.BaseFixedPoint; - highest = sk; - } - } - - m_Highest = highest == null && m_Skills.Length > 0 ? this[0] : highest; - } - - return m_Highest; - } - } - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Alchemy => this[SkillName.Alchemy]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Anatomy => this[SkillName.Anatomy]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill AnimalLore => this[SkillName.AnimalLore]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill ItemID => this[SkillName.ItemID]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill ArmsLore => this[SkillName.ArmsLore]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Parry => this[SkillName.Parry]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Begging => this[SkillName.Begging]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Blacksmith => this[SkillName.Blacksmith]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Fletching => this[SkillName.Fletching]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Peacemaking => this[SkillName.Peacemaking]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Camping => this[SkillName.Camping]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Carpentry => this[SkillName.Carpentry]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Cartography => this[SkillName.Cartography]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Cooking => this[SkillName.Cooking]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill DetectHidden => this[SkillName.DetectHidden]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Discordance => this[SkillName.Discordance]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill EvalInt => this[SkillName.EvalInt]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Healing => this[SkillName.Healing]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Fishing => this[SkillName.Fishing]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Forensics => this[SkillName.Forensics]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Herding => this[SkillName.Herding]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Hiding => this[SkillName.Hiding]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Provocation => this[SkillName.Provocation]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Inscribe => this[SkillName.Inscribe]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Lockpicking => this[SkillName.Lockpicking]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Magery => this[SkillName.Magery]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill MagicResist => this[SkillName.MagicResist]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Tactics => this[SkillName.Tactics]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Snooping => this[SkillName.Snooping]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Musicianship => this[SkillName.Musicianship]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Poisoning => this[SkillName.Poisoning]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Archery => this[SkillName.Archery]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill SpiritSpeak => this[SkillName.SpiritSpeak]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Stealing => this[SkillName.Stealing]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Tailoring => this[SkillName.Tailoring]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill AnimalTaming => this[SkillName.AnimalTaming]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill TasteID => this[SkillName.TasteID]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Tinkering => this[SkillName.Tinkering]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Tracking => this[SkillName.Tracking]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Veterinary => this[SkillName.Veterinary]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Swords => this[SkillName.Swords]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Macing => this[SkillName.Macing]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Fencing => this[SkillName.Fencing]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Wrestling => this[SkillName.Wrestling]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Lumberjacking => this[SkillName.Lumberjacking]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Mining => this[SkillName.Mining]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Meditation => this[SkillName.Meditation]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Stealth => this[SkillName.Stealth]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill RemoveTrap => this[SkillName.RemoveTrap]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Necromancy => this[SkillName.Necromancy]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Focus => this[SkillName.Focus]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Chivalry => this[SkillName.Chivalry]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Bushido => this[SkillName.Bushido]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Ninjitsu => this[SkillName.Ninjitsu]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Spellweaving => this[SkillName.Spellweaving]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Mysticism => this[SkillName.Mysticism]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Imbuing => this[SkillName.Imbuing]; - - [CommandProperty(AccessLevel.Counselor, canModify: true)] - public Skill Throwing => this[SkillName.Throwing]; - - public override string ToString() => "..."; - - public static bool UseSkill(Mobile from, SkillName name) => UseSkill(from, (int)name); - - public static bool UseSkill(Mobile from, int skillID) - { - if (!from.CheckAlive()) - { - return false; - } - - if (!from.Region.OnSkillUse(from, skillID)) - { - return false; - } - - if (!from.AllowSkillUse((SkillName)skillID)) - { - return false; - } - - if (skillID >= 0 && skillID < SkillInfo.Table.Length) - { - var info = SkillInfo.Table[skillID]; - - if (info.Callback != null) - { - if (Core.TickCount - from.NextSkillTime >= 0 && from.Spell == null) - { - from.DisruptiveAction(); - - from.NextSkillTime = Core.TickCount + (int)info.Callback(from).TotalMilliseconds; - - return true; - } - - from.SendSkillMessage(); - } else { - from.SendLocalizedMessage(500014); // That skill cannot be used directly. + bonusObey = 0.0; + bonusNotObey = 0.0; + value = mod.Value; + } + } + + value += bonusNotObey; + + if (value < Cap) + { + value += bonusObey; + + if (value > Cap) + { + value = Cap; + } + } + + return value; + } + } + + public override string ToString() => $"[{Name}: {Base}]"; + + public void SetLockNoRelay(SkillLock skillLock) + { + if (skillLock is < SkillLock.Up or > SkillLock.Locked) + { + return; + } + + Lock = skillLock; + } + + public void Serialize(IGenericWriter writer) + { + if (m_Base == 0 && m_Cap == 1000 && Lock == SkillLock.Up) + { + writer.Write((byte)0xFF); // default + } + else + { + var flags = 0x0; + + if (m_Base != 0) + { + flags |= 0x1; + } + + if (m_Cap != 1000) + { + flags |= 0x2; + } + + if (Lock != SkillLock.Up) + { + flags |= 0x4; + } + + writer.Write((byte)flags); // version + + if (m_Base != 0) + { + writer.Write((short)m_Base); + } + + if (m_Cap != 1000) + { + writer.Write((short)m_Cap); + } + + if (Lock != SkillLock.Up) + { + writer.Write((byte)Lock); + } + } + } + + public void Update() + { + Owner.OnSkillChange(this); + } +} + +public class SkillInfo +{ + public SkillInfo( + int skillID, string name, double strScale, double dexScale, double intScale, string title, + SkillUseCallback callback, double strGain, double dexGain, double intGain, double gainFactor, + string professionSkillName = null + ) + { + Name = name; + Title = title; + SkillID = skillID; + StrScale = strScale / 100.0; + DexScale = dexScale / 100.0; + IntScale = intScale / 100.0; + Callback = callback; + StrGain = strGain; + DexGain = dexGain; + IntGain = intGain; + GainFactor = gainFactor; + ProfessionSkillName = professionSkillName ?? Name.Replace(" ", ""); + StatTotal = strScale + dexScale + intScale; + } + + public SkillUseCallback Callback { get; set; } + + public int SkillID { get; } + + public string Name { get; set; } + + public string Title { get; set; } + + public double StrScale { get; set; } + + public double DexScale { get; set; } + + public double IntScale { get; set; } + + public double StatTotal { get; set; } + + public double StrGain { get; set; } + + public double DexGain { get; set; } + + public double IntGain { get; set; } + + public double GainFactor { get; set; } + + public string ProfessionSkillName { get; set; } + + public static SkillInfo[] Table { get; set; } = + { + new(0, "Alchemy", 0.0, 5.0, 5.0, "Alchemist", null, 0.0, 0.5, 0.5, 1.0), + new(1, "Anatomy", 0.0, 0.0, 0.0, "Biologist", null, 0.15, 0.15, 0.7, 1.0), + new(2, "Animal Lore", 0.0, 0.0, 0.0, "Naturalist", null, 0.0, 0.0, 1.0, 1.0), + new(3, "Item Identification", 0.0, 0.0, 0.0, "Merchant", null, 0.0, 0.0, 1.0, 1.0, "ItemID"), + new(4, "Arms Lore", 0.0, 0.0, 0.0, "Weapon Master", null, 0.75, 0.15, 0.1, 1.0), + new(5, "Parrying", 7.5, 2.5, 0.0, "Duelist", null, 0.75, 0.25, 0.0, 1.0), + new(6, "Begging", 0.0, 0.0, 0.0, "Beggar", null, 0.0, 0.0, 0.0, 1.0), + new(7, "Blacksmithy", 10.0, 0.0, 0.0, "Blacksmith", null, 1.0, 0.0, 0.0, 1.0, "Blacksmith"), + new(8, "Bowcraft/Fletching", 6.0, 16.0, 0.0, "Bowyer", null, 0.6, 1.6, 0.0, 1.0, "Bowcraft"), + new(9, "Peacemaking", 0.0, 0.0, 0.0, "Pacifier", null, 0.0, 0.0, 0.0, 1.0), + new(10, "Camping", 20.0, 15.0, 15.0, "Explorer", null, 2.0, 1.5, 1.5, 1.0), + new(11, "Carpentry", 20.0, 5.0, 0.0, "Carpenter", null, 2.0, 0.5, 0.0, 1.0), + new(12, "Cartography", 0.0, 7.5, 7.5, "Cartographer", null, 0.0, 0.75, 0.75, 1.0), + new(13, "Cooking", 0.0, 20.0, 30.0, "Chef", null, 0.0, 2.0, 3.0, 1.0), + new(14, "Detecting Hidden", 0.0, 0.0, 0.0, "Scout", null, 0.0, 0.4, 0.6, 1.0), + new(15, "Discordance", 0.0, 2.5, 2.5, "Demoralizer", null, 0.0, 0.25, 0.25, 1.0, "Enticement"), + new(16, "Evaluating Intelligence", 0.0, 0.0, 0.0, "Scholar", null, 0.0, 0.0, 1.0, 1.0, "EvaluateIntelligence"), + new(17, "Healing", 6.0, 6.0, 8.0, "Healer", null, 0.6, 0.6, 0.8, 1.0), + new(18, "Fishing", 0.0, 0.0, 0.0, "Fisherman", null, 0.5, 0.5, 0.0, 1.0), + new(19, "Forensic Evaluation", 0.0, 0.0, 0.0, "Detective", null, 0.0, 0.2, 0.8, 1.0), + new(20, "Herding", 16.25, 6.25, 2.5, "Shepherd", null, 1.625, 0.625, 0.25, 1.0), + new(21, "Hiding", 0.0, 0.0, 0.0, "Shade", null, 0.0, 0.8, 0.2, 1.0), + new(22, "Provocation", 0.0, 4.5, 0.5, "Rouser", null, 0.0, 0.45, 0.05, 1.0), + new(23, "Inscription", 0.0, 2.0, 8.0, "Scribe", null, 0.0, 0.2, 0.8, 1.0), + new(24, "Lockpicking", 0.0, 25.0, 0.0, "Infiltrator", null, 0.0, 2.0, 0.0, 1.0), + new(25, "Magery", 0.0, 0.0, 15.0, "Mage", null, 0.0, 0.0, 1.5, 1.0), + new(26, "Resisting Spells", 0.0, 0.0, 0.0, "Warder", null, 0.25, 0.25, 0.5, 1.0), + new(27, "Tactics", 0.0, 0.0, 0.0, "Tactician", null, 0.0, 0.0, 0.0, 1.0), + new(28, "Snooping", 0.0, 25.0, 0.0, "Spy", null, 0.0, 2.5, 0.0, 1.0), + new(29, "Musicianship", 0.0, 0.0, 0.0, "Bard", null, 0.0, 0.8, 0.2, 1.0), + new(30, "Poisoning", 0.0, 4.0, 16.0, "Assassin", null, 0.0, 0.4, 1.6, 1.0), + new(31, "Archery", 2.5, 7.5, 0.0, "Archer", null, 0.25, 0.75, 0.0, 1.0), + new(32, "Spirit Speak", 0.0, 0.0, 0.0, "Medium", null, 0.0, 0.0, 1.0, 1.0), + new(33, "Stealing", 0.0, 10.0, 0.0, "Pickpocket", null, 0.0, 1.0, 0.0, 1.0), + new(34, "Tailoring", 3.75, 16.25, 5.0, "Tailor", null, 0.38, 1.63, 0.5, 1.0), + new(35, "Animal Taming", 14.0, 2.0, 4.0, "Tamer", null, 1.4, 0.2, 0.4, 1.0), + new(36, "Taste Identification", 0.0, 0.0, 0.0, "Praegustator", null, 0.2, 0.0, 0.8, 1.0), + new(37, "Tinkering", 5.0, 2.0, 3.0, "Tinker", null, 0.5, 0.2, 0.3, 1.0), + new(38, "Tracking", 0.0, 12.5, 12.5, "Ranger", null, 0.0, 1.25, 1.25, 1.0), + new(39, "Veterinary", 8.0, 4.0, 8.0, "Veterinarian", null, 0.8, 0.4, 0.8, 1.0), + new(40, "Swordsmanship", 7.5, 2.5, 0.0, "Swordsman", null, 0.75, 0.25, 0.0, 1.0), + new(41, "Mace Fighting", 9.0, 1.0, 0.0, "Armsman", null, 0.9, 0.1, 0.0, 1.0), + new(42, "Fencing", 4.5, 5.5, 0.0, "Fencer", null, 0.45, 0.55, 0.0, 1.0), + new(43, "Wrestling", 9.0, 1.0, 0.0, "Wrestler", null, 0.9, 0.1, 0.0, 1.0), + new(44, "Lumberjacking", 20.0, 0.0, 0.0, "Lumberjack", null, 2.0, 0.0, 0.0, 1.0), + new(45, "Mining", 20.0, 0.0, 0.0, "Miner", null, 2.0, 0.0, 0.0, 1.0), + new(46, "Meditation", 0.0, 0.0, 0.0, "Stoic", null, 0.0, 0.0, 0.0, 1.0), + new(47, "Stealth", 0.0, 0.0, 0.0, "Rogue", null, 0.0, 0.0, 0.0, 1.0), + new(48, "Remove Trap", 0.0, 0.0, 0.0, "Trap Specialist", null, 0.0, 0.0, 0.0, 1.0, "Disarm"), + new(49, "Necromancy", 0.0, 0.0, 0.0, "Necromancer", null, 0.0, 0.0, 0.0, 1.0), + new(50, "Focus", 0.0, 0.0, 0.0, "Driven", null, 0.0, 0.0, 0.0, 1.0), + new(51, "Chivalry", 0.0, 0.0, 0.0, "Paladin", null, 0.0, 0.0, 0.0, 1.0), + new(52, "Bushido", 0.0, 0.0, 0.0, "Samurai", null, 0.0, 0.0, 0.0, 1.0), + new(53, "Ninjitsu", 0.0, 0.0, 0.0, "Ninja", null, 0.0, 0.0, 0.0, 1.0), + new(54, "Spellweaving", 0.0, 0.0, 0.0, "Arcanist", null, 0.0, 0.0, 0.0, 1.0), + new(55, "Mysticism", 0.0, 0.0, 0.0, "Mystic", null, 0.0, 0.0, 0.0, 1.0), + new(56, "Imbuing", 0.0, 0.0, 0.0, "Artificer", null, 0.0, 0.0, 0.0, 1.0), + new(57, "Throwing", 0.0, 0.0, 0.0, "Bladeweaver", null, 0.0, 0.0, 0.0, 1.0) + }; +} + +[PropertyObject] +public class Skills +{ + private readonly Skill[] m_Skills; + private Skill m_Highest; + + public Skills(Mobile owner) + { + Owner = owner; + Cap = 7000; + + var info = SkillInfo.Table; + + m_Skills = new Skill[info.Length]; + } + + public Skills(Mobile owner, IGenericReader reader) + { + Owner = owner; + + var version = reader.ReadInt(); + + switch (version) + { + case 3: + case 2: + { + Cap = reader.ReadInt(); + + goto case 1; + } + case 1: + { + if (version < 2) + { + Cap = 7000; + } + + if (version < 3) + /*m_Total =*/ + { + reader.ReadInt(); + } + + var info = SkillInfo.Table; + + m_Skills = new Skill[info.Length]; + + var count = reader.ReadInt(); + + for (var i = 0; i < count; ++i) + { + if (i < info.Length) + { + var sk = new Skill(this, info[i], reader); + + if (sk.BaseFixedPoint != 0 || sk.CapFixedPoint != 1000 || sk.Lock != SkillLock.Up) + { + m_Skills[i] = sk; + Total += sk.BaseFixedPoint; + } + } + else + { + // Will be discarded + _ = new Skill(this, null, reader); + } + } + + // for ( int i = count; i < info.Length; ++i ) + // m_Skills[i] = new Skill( this, info[i], 0, 1000, SkillLock.Up ); + + break; + } + case 0: + { + reader.ReadInt(); + + goto case 1; + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Cap { get; set; } + + public int Total { get; set; } + + public Mobile Owner { get; } + + public int Length => m_Skills.Length; + + public Skill this[SkillName name] => this[(int)name]; + + public Skill this[int skillID] + { + get + { + if (skillID < 0 || skillID >= m_Skills.Length) + { + return null; + } + + var sk = m_Skills[skillID]; + + if (sk == null) + { + m_Skills[skillID] = sk = new Skill(this, SkillInfo.Table[skillID], 0, 1000, SkillLock.Up); + } + + return sk; + } + } + + public Skill Highest + { + get + { + if (m_Highest == null) + { + Skill highest = null; + var value = int.MinValue; + + for (var i = 0; i < m_Skills.Length; ++i) + { + var sk = m_Skills[i]; + + if (sk?.BaseFixedPoint > value) + { + value = sk.BaseFixedPoint; + highest = sk; + } + } + + m_Highest = highest == null && m_Skills.Length > 0 ? this[0] : highest; + } + + return m_Highest; + } + } + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Alchemy => this[SkillName.Alchemy]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Anatomy => this[SkillName.Anatomy]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill AnimalLore => this[SkillName.AnimalLore]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill ItemID => this[SkillName.ItemID]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill ArmsLore => this[SkillName.ArmsLore]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Parry => this[SkillName.Parry]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Begging => this[SkillName.Begging]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Blacksmith => this[SkillName.Blacksmith]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Fletching => this[SkillName.Fletching]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Peacemaking => this[SkillName.Peacemaking]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Camping => this[SkillName.Camping]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Carpentry => this[SkillName.Carpentry]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Cartography => this[SkillName.Cartography]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Cooking => this[SkillName.Cooking]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill DetectHidden => this[SkillName.DetectHidden]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Discordance => this[SkillName.Discordance]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill EvalInt => this[SkillName.EvalInt]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Healing => this[SkillName.Healing]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Fishing => this[SkillName.Fishing]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Forensics => this[SkillName.Forensics]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Herding => this[SkillName.Herding]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Hiding => this[SkillName.Hiding]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Provocation => this[SkillName.Provocation]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Inscribe => this[SkillName.Inscribe]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Lockpicking => this[SkillName.Lockpicking]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Magery => this[SkillName.Magery]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill MagicResist => this[SkillName.MagicResist]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Tactics => this[SkillName.Tactics]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Snooping => this[SkillName.Snooping]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Musicianship => this[SkillName.Musicianship]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Poisoning => this[SkillName.Poisoning]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Archery => this[SkillName.Archery]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill SpiritSpeak => this[SkillName.SpiritSpeak]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Stealing => this[SkillName.Stealing]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Tailoring => this[SkillName.Tailoring]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill AnimalTaming => this[SkillName.AnimalTaming]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill TasteID => this[SkillName.TasteID]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Tinkering => this[SkillName.Tinkering]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Tracking => this[SkillName.Tracking]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Veterinary => this[SkillName.Veterinary]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Swords => this[SkillName.Swords]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Macing => this[SkillName.Macing]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Fencing => this[SkillName.Fencing]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Wrestling => this[SkillName.Wrestling]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Lumberjacking => this[SkillName.Lumberjacking]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Mining => this[SkillName.Mining]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Meditation => this[SkillName.Meditation]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Stealth => this[SkillName.Stealth]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill RemoveTrap => this[SkillName.RemoveTrap]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Necromancy => this[SkillName.Necromancy]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Focus => this[SkillName.Focus]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Chivalry => this[SkillName.Chivalry]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Bushido => this[SkillName.Bushido]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Ninjitsu => this[SkillName.Ninjitsu]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Spellweaving => this[SkillName.Spellweaving]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Mysticism => this[SkillName.Mysticism]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Imbuing => this[SkillName.Imbuing]; + + [CommandProperty(AccessLevel.Counselor, canModify: true)] + public Skill Throwing => this[SkillName.Throwing]; + + public override string ToString() => "..."; + + public static bool UseSkill(Mobile from, SkillName name) => UseSkill(from, (int)name); + + public static bool UseSkill(Mobile from, int skillID) + { + if (!from.CheckAlive()) + { + return false; + } + + if (!from.Region.OnSkillUse(from, skillID)) + { + return false; + } + + if (!from.AllowSkillUse((SkillName)skillID)) + { + return false; + } + + if (skillID >= 0 && skillID < SkillInfo.Table.Length) + { + var info = SkillInfo.Table[skillID]; + + if (info.Callback != null) + { + if (Core.TickCount - from.NextSkillTime >= 0 && from.Spell == null) + { + from.DisruptiveAction(); + + from.NextSkillTime = Core.TickCount + (int)info.Callback(from).TotalMilliseconds; + + return true; + } + + from.SendSkillMessage(); + } + else + { + from.SendLocalizedMessage(500014); // That skill cannot be used directly. + } + } + + return false; + } + + public void Serialize(IGenericWriter writer) + { + Total = 0; + + writer.Write(3); // version + + writer.Write(Cap); + writer.Write(m_Skills.Length); + + for (var i = 0; i < m_Skills.Length; ++i) + { + var sk = m_Skills[i]; + + if (sk == null) + { + writer.Write((byte)0xFF); + } + else + { + sk.Serialize(writer); + Total += sk.BaseFixedPoint; + } + } + } + + public void OnSkillChange(Skill skill) + { + if (skill == m_Highest) // could be downgrading the skill, force a recalc + { + m_Highest = null; + } + else if (skill.BaseFixedPoint > m_Highest?.BaseFixedPoint) + { + m_Highest = skill; + } + + Owner.OnSkillInvalidated(skill); + Owner.NetState.SendSkillChange(skill); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new(m_Skills); + + public ref struct Enumerator + { + private readonly Skill[] _skills; + private int _index; + private Skill _current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(Skill[] skills) + { + _skills = skills; + _index = 0; + _current = default; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + Skill[] localList = _skills; + + while ((uint)_index < (uint)localList.Length) + { + _current = _skills[_index++]; + if (_current != null) + { + return true; } } return false; } - public void Serialize(IGenericWriter writer) + public Skill Current { - Total = 0; - - writer.Write(3); // version - - writer.Write(Cap); - writer.Write(m_Skills.Length); - - for (var i = 0; i < m_Skills.Length; ++i) - { - var sk = m_Skills[i]; - - if (sk == null) - { - writer.Write((byte)0xFF); - } - else - { - sk.Serialize(writer); - Total += sk.BaseFixedPoint; - } - } - } - - public void OnSkillChange(Skill skill) - { - if (skill == m_Highest) // could be downgrading the skill, force a recalc - { - m_Highest = null; - } - else if (skill.BaseFixedPoint > m_Highest?.BaseFixedPoint) - { - m_Highest = skill; - } - - Owner.OnSkillInvalidated(skill); - Owner.NetState.SendSkillChange(skill); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new(m_Skills); - - public ref struct Enumerator - { - private readonly Skill[] _skills; - private int _index; - private Skill _current; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(Skill[] skills) - { - _skills = skills; - _index = 0; - _current = default; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - Skill[] localList = _skills; - - while ((uint)_index < (uint)localList.Length) - { - _current = _skills[_index++]; - if (_current != null) - { - return true; - } - } - - return false; - } - - public Skill Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _current; - } + get => _current; } } } diff --git a/Projects/Server/Targeting/LandTarget.cs b/Projects/Server/Targeting/LandTarget.cs index 32511045e..48039a78c 100644 --- a/Projects/Server/Targeting/LandTarget.cs +++ b/Projects/Server/Targeting/LandTarget.cs @@ -1,39 +1,38 @@ -namespace Server.Targeting +namespace Server.Targeting; + +public class LandTarget : IPoint3D { - public class LandTarget : IPoint3D + private Point3D m_Location; + + public LandTarget(Point3D location, Map map) { - private Point3D m_Location; + m_Location = location; - public LandTarget(Point3D location, Map map) + if (map != null) { - m_Location = location; - - if (map != null) - { - m_Location.Z = map.GetAverageZ(m_Location.X, m_Location.Y); - TileID = map.Tiles.GetLandTile(m_Location.X, m_Location.Y).ID & TileData.MaxLandValue; - } + m_Location.Z = map.GetAverageZ(m_Location.X, m_Location.Y); + TileID = map.Tiles.GetLandTile(m_Location.X, m_Location.Y).ID & TileData.MaxLandValue; } - - [CommandProperty(AccessLevel.Counselor)] - public string Name => TileData.LandTable[TileID].Name; - - [CommandProperty(AccessLevel.Counselor)] - public TileFlag Flags => TileData.LandTable[TileID].Flags; - - [CommandProperty(AccessLevel.Counselor)] - public int TileID { get; } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D Location => m_Location; - - [CommandProperty(AccessLevel.Counselor)] - public int X => m_Location.X; - - [CommandProperty(AccessLevel.Counselor)] - public int Y => m_Location.Y; - - [CommandProperty(AccessLevel.Counselor)] - public int Z => m_Location.Z; } + + [CommandProperty(AccessLevel.Counselor)] + public string Name => TileData.LandTable[TileID].Name; + + [CommandProperty(AccessLevel.Counselor)] + public TileFlag Flags => TileData.LandTable[TileID].Flags; + + [CommandProperty(AccessLevel.Counselor)] + public int TileID { get; } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Location => m_Location; + + [CommandProperty(AccessLevel.Counselor)] + public int X => m_Location.X; + + [CommandProperty(AccessLevel.Counselor)] + public int Y => m_Location.Y; + + [CommandProperty(AccessLevel.Counselor)] + public int Z => m_Location.Z; } diff --git a/Projects/Server/Targeting/MultiTarget.cs b/Projects/Server/Targeting/MultiTarget.cs index 24ffba3a8..524b401ae 100644 --- a/Projects/Server/Targeting/MultiTarget.cs +++ b/Projects/Server/Targeting/MultiTarget.cs @@ -1,23 +1,22 @@ using Server.Network; -namespace Server.Targeting +namespace Server.Targeting; + +public abstract class MultiTarget : Target { - public abstract class MultiTarget : Target + protected MultiTarget( + int multiID, Point3D offset, int range = 10, bool allowGround = true, + TargetFlags flags = TargetFlags.None + ) + : base(range, allowGround, flags) { - protected MultiTarget( - int multiID, Point3D offset, int range = 10, bool allowGround = true, - TargetFlags flags = TargetFlags.None - ) - : base(range, allowGround, flags) - { - MultiID = multiID; - Offset = offset; - } - - public int MultiID { get; set; } - - public Point3D Offset { get; set; } - - public override void SendTargetTo(NetState ns) => ns.SendMultiTargetReq(this); + MultiID = multiID; + Offset = offset; } + + public int MultiID { get; set; } + + public Point3D Offset { get; set; } + + public override void SendTargetTo(NetState ns) => ns.SendMultiTargetReq(this); } diff --git a/Projects/Server/Targeting/StaticTarget.cs b/Projects/Server/Targeting/StaticTarget.cs index 33cb9107c..289155048 100644 --- a/Projects/Server/Targeting/StaticTarget.cs +++ b/Projects/Server/Targeting/StaticTarget.cs @@ -1,35 +1,34 @@ -namespace Server.Targeting +namespace Server.Targeting; + +public class StaticTarget : IPoint3D { - public class StaticTarget : IPoint3D + private Point3D m_Location; + + public StaticTarget(Point3D location, int itemID) { - private Point3D m_Location; - - public StaticTarget(Point3D location, int itemID) - { - m_Location = location; - ItemID = itemID & TileData.MaxItemValue; - m_Location.Z += TileData.ItemTable[ItemID].CalcHeight; - } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D Location => m_Location; - - [CommandProperty(AccessLevel.Counselor)] - public string Name => TileData.ItemTable[ItemID].Name; - - [CommandProperty(AccessLevel.Counselor)] - public TileFlag Flags => TileData.ItemTable[ItemID].Flags; - - [CommandProperty(AccessLevel.Counselor)] - public int ItemID { get; } - - [CommandProperty(AccessLevel.Counselor)] - public int X => m_Location.X; - - [CommandProperty(AccessLevel.Counselor)] - public int Y => m_Location.Y; - - [CommandProperty(AccessLevel.Counselor)] - public int Z => m_Location.Z; + m_Location = location; + ItemID = itemID & TileData.MaxItemValue; + m_Location.Z += TileData.ItemTable[ItemID].CalcHeight; } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Location => m_Location; + + [CommandProperty(AccessLevel.Counselor)] + public string Name => TileData.ItemTable[ItemID].Name; + + [CommandProperty(AccessLevel.Counselor)] + public TileFlag Flags => TileData.ItemTable[ItemID].Flags; + + [CommandProperty(AccessLevel.Counselor)] + public int ItemID { get; } + + [CommandProperty(AccessLevel.Counselor)] + public int X => m_Location.X; + + [CommandProperty(AccessLevel.Counselor)] + public int Y => m_Location.Y; + + [CommandProperty(AccessLevel.Counselor)] + public int Z => m_Location.Z; } diff --git a/Projects/Server/Targeting/Target.cs b/Projects/Server/Targeting/Target.cs index 69cec2c75..728300827 100644 --- a/Projects/Server/Targeting/Target.cs +++ b/Projects/Server/Targeting/Target.cs @@ -1,291 +1,290 @@ using System; using Server.Network; -namespace Server.Targeting +namespace Server.Targeting; + +public abstract class Target { - public abstract class Target + private static int m_NextTargetID; + + private Timer m_TimeoutTimer; + + protected Target(int range, bool allowGround, TargetFlags flags) { - private static int m_NextTargetID; + TargetID = ++m_NextTargetID; + Range = range; + AllowGround = allowGround; + Flags = flags; - private Timer m_TimeoutTimer; + CheckLOS = true; + } - protected Target(int range, bool allowGround, TargetFlags flags) + public long TimeoutTime { get; private set; } + + public bool CheckLOS { get; set; } + + public bool DisallowMultis { get; set; } + + public bool AllowNonlocal { get; set; } + + public int TargetID { get; } + + public int Range { get; set; } + + public bool AllowGround { get; set; } + + public TargetFlags Flags { get; set; } + + public static void Cancel(Mobile m) + { + m.NetState.SendCancelTarget(); + m.Target?.OnTargetCancel(m, TargetCancelType.Canceled); + } + + public void BeginTimeout(Mobile from, long delay) + { + TimeoutTime = Core.TickCount + delay; + + m_TimeoutTimer?.Stop(); + + m_TimeoutTimer = new TimeoutTimer(this, from, TimeSpan.FromMilliseconds(delay)); + m_TimeoutTimer.Start(); + } + + public void CancelTimeout() + { + m_TimeoutTimer?.Stop(); + m_TimeoutTimer = null; + } + + public void Timeout(Mobile from) + { + CancelTimeout(); + from.ClearTarget(); + + Cancel(from); + + OnTargetCancel(from, TargetCancelType.Timeout); + OnTargetFinish(from); + } + + public virtual void SendTargetTo(NetState ns) => ns.SendTargetReq(this); + + public void Cancel(Mobile from, TargetCancelType type) + { + CancelTimeout(); + from.ClearTarget(); + + OnTargetCancel(from, type); + OnTargetFinish(from); + } + + protected virtual bool CanTarget(Mobile from, LandTarget landTarget, ref Point3D loc, ref Map map) + { + if (!AllowGround) { - TargetID = ++m_NextTargetID; - Range = range; - AllowGround = allowGround; - Flags = flags; - - CheckLOS = true; + // We should actually never get here. If we do, it's probably a misbehaving client/macro. + OnTargetCancel(from, TargetCancelType.Canceled); + return false; } - public long TimeoutTime { get; private set; } + loc = landTarget.Location; + map = from.Map; + return true; + } - public bool CheckLOS { get; set; } + protected virtual bool CanTarget(Mobile from, StaticTarget staticTarget, ref Point3D loc, ref Map map) + { + loc = staticTarget.Location; + map = from.Map; + return true; + } - public bool DisallowMultis { get; set; } - - public bool AllowNonlocal { get; set; } - - public int TargetID { get; } - - public int Range { get; set; } - - public bool AllowGround { get; set; } - - public TargetFlags Flags { get; set; } - - public static void Cancel(Mobile m) + protected virtual bool CanTarget(Mobile from, Item item, ref Point3D loc, ref Map map) + { + if (item.Deleted) { - m.NetState.SendCancelTarget(); - m.Target?.OnTargetCancel(m, TargetCancelType.Canceled); + OnTargetDeleted(from, item); + return false; } - public void BeginTimeout(Mobile from, long delay) + if (!item.CanTarget) { - TimeoutTime = Core.TickCount + delay; - - m_TimeoutTimer?.Stop(); - - m_TimeoutTimer = new TimeoutTimer(this, from, TimeSpan.FromMilliseconds(delay)); - m_TimeoutTimer.Start(); + OnTargetUntargetable(from, item); + return false; } - public void CancelTimeout() + if (!AllowNonlocal && item.RootParent is Mobile && item.RootParent != from && + from.AccessLevel == AccessLevel.Player) { - m_TimeoutTimer?.Stop(); - m_TimeoutTimer = null; + OnNonlocalTarget(from, item); + return false; } - public void Timeout(Mobile from) + loc = item.GetWorldLocation(); + map = item.Map; + return true; + } + + protected virtual bool CanTarget(Mobile from, Mobile mobile, ref Point3D loc, ref Map map) + { + if (mobile.Deleted) { - CancelTimeout(); - from.ClearTarget(); + OnTargetDeleted(from, mobile); + return false; + } - Cancel(from); + if (!mobile.CanTarget) + { + OnTargetUntargetable(from, mobile); + return false; + } - OnTargetCancel(from, TargetCancelType.Timeout); + loc = mobile.Location; + map = mobile.Map; + return true; + } + + public void Invoke(Mobile from, object targeted) + { + CancelTimeout(); + from.ClearTarget(); + + if (from.Deleted) + { + OnTargetCancel(from, TargetCancelType.Canceled); OnTargetFinish(from); + return; } - public virtual void SendTargetTo(NetState ns) => ns.SendTargetReq(this); + Point3D loc = default; + Map map = null; + Item item = null; + Mobile mobile = null; + bool isValidTargetType = true; - public void Cancel(Mobile from, TargetCancelType type) + bool valid = targeted switch { - CancelTimeout(); - from.ClearTarget(); + LandTarget landTarget => CanTarget(from, landTarget, ref loc, ref map), + StaticTarget staticTarget => CanTarget(from, staticTarget, ref loc, ref map), + Item i => CanTarget(from, item = i, ref loc, ref map), + Mobile m => CanTarget(from, mobile = m, ref loc, ref map), + _ => isValidTargetType = false + }; - OnTargetCancel(from, type); - OnTargetFinish(from); - } - - protected virtual bool CanTarget(Mobile from, LandTarget landTarget, ref Point3D loc, ref Map map) + if (!valid) { - if (!AllowGround) - { - // We should actually never get here. If we do, it's probably a misbehaving client/macro. - OnTargetCancel(from, TargetCancelType.Canceled); - return false; - } - - loc = landTarget.Location; - map = from.Map; - return true; - } - - protected virtual bool CanTarget(Mobile from, StaticTarget staticTarget, ref Point3D loc, ref Map map) - { - loc = staticTarget.Location; - map = from.Map; - return true; - } - - protected virtual bool CanTarget(Mobile from, Item item, ref Point3D loc, ref Map map) - { - if (item.Deleted) - { - OnTargetDeleted(from, item); - return false; - } - - if (!item.CanTarget) - { - OnTargetUntargetable(from, item); - return false; - } - - if (!AllowNonlocal && item.RootParent is Mobile && item.RootParent != from && - from.AccessLevel == AccessLevel.Player) - { - OnNonlocalTarget(from, item); - return false; - } - - loc = item.GetWorldLocation(); - map = item.Map; - return true; - } - - protected virtual bool CanTarget(Mobile from, Mobile mobile, ref Point3D loc, ref Map map) - { - if (mobile.Deleted) - { - OnTargetDeleted(from, mobile); - return false; - } - - if (!mobile.CanTarget) - { - OnTargetUntargetable(from, mobile); - return false; - } - - loc = mobile.Location; - map = mobile.Map; - return true; - } - - public void Invoke(Mobile from, object targeted) - { - CancelTimeout(); - from.ClearTarget(); - - if (from.Deleted) + if (!isValidTargetType) { OnTargetCancel(from, TargetCancelType.Canceled); - OnTargetFinish(from); - return; - } - - Point3D loc = default; - Map map = null; - Item item = null; - Mobile mobile = null; - bool isValidTargetType = true; - - bool valid = targeted switch - { - LandTarget landTarget => CanTarget(from, landTarget, ref loc, ref map), - StaticTarget staticTarget => CanTarget(from, staticTarget, ref loc, ref map), - Item i => CanTarget(from, item = i, ref loc, ref map), - Mobile m => CanTarget(from, mobile = m, ref loc, ref map), - _ => isValidTargetType = false - }; - - if (!valid) - { - if (!isValidTargetType) - { - OnTargetCancel(from, TargetCancelType.Canceled); - } - - OnTargetFinish(from); - } - - if (map == null || map != from.Map || Range >= 0 && !from.InRange(loc, Range)) - { - OnTargetOutOfRange(from, targeted); - } - else if (!from.CanSee(targeted)) - { - OnCantSeeTarget(from, targeted); - } - else if (CheckLOS && !from.InLOS(targeted)) - { - OnTargetOutOfLOS(from, targeted); - } - else if (item?.InSecureTrade == true) - { - OnTargetInSecureTrade(from, targeted); - } - else if (item?.IsAccessibleTo(from) == false) - { - OnTargetNotAccessible(from, targeted); - } - else if (item?.CheckTarget(from, this, targeted) == false) - { - OnTargetUntargetable(from, targeted); - } - else if (mobile?.CheckTarget(from, this, mobile) == false) - { - OnTargetUntargetable(from, mobile); - } - else if (from.Region.OnTarget(from, this, targeted)) - { - OnTarget(from, targeted); } OnTargetFinish(from); } - protected virtual void OnTarget(Mobile from, object targeted) + if (map == null || map != from.Map || Range >= 0 && !from.InRange(loc, Range)) { + OnTargetOutOfRange(from, targeted); + } + else if (!from.CanSee(targeted)) + { + OnCantSeeTarget(from, targeted); + } + else if (CheckLOS && !from.InLOS(targeted)) + { + OnTargetOutOfLOS(from, targeted); + } + else if (item?.InSecureTrade == true) + { + OnTargetInSecureTrade(from, targeted); + } + else if (item?.IsAccessibleTo(from) == false) + { + OnTargetNotAccessible(from, targeted); + } + else if (item?.CheckTarget(from, this, targeted) == false) + { + OnTargetUntargetable(from, targeted); + } + else if (mobile?.CheckTarget(from, this, mobile) == false) + { + OnTargetUntargetable(from, mobile); + } + else if (from.Region.OnTarget(from, this, targeted)) + { + OnTarget(from, targeted); } - protected virtual void OnTargetNotAccessible(Mobile from, object targeted) + OnTargetFinish(from); + } + + protected virtual void OnTarget(Mobile from, object targeted) + { + } + + protected virtual void OnTargetNotAccessible(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnTargetInSecureTrade(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnNonlocalTarget(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnCantSeeTarget(Mobile from, object targeted) + { + from.SendLocalizedMessage(500237); // Target can not be seen. + } + + protected virtual void OnTargetOutOfLOS(Mobile from, object targeted) + { + from.SendLocalizedMessage(500237); // Target can not be seen. + } + + protected virtual void OnTargetOutOfRange(Mobile from, object targeted) + { + from.SendLocalizedMessage(500446); // That is too far away. + } + + protected virtual void OnTargetDeleted(Mobile from, object targeted) + { + } + + protected virtual void OnTargetUntargetable(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnTargetCancel(Mobile from, TargetCancelType cancelType) + { + } + + protected virtual void OnTargetFinish(Mobile from) + { + } + + private class TimeoutTimer : Timer + { + private readonly Mobile m_Mobile; + private readonly Target m_Target; + + public TimeoutTimer(Target target, Mobile m, TimeSpan delay) : base(delay) { - from.SendLocalizedMessage(500447); // That is not accessible. + m_Target = target; + m_Mobile = m; } - protected virtual void OnTargetInSecureTrade(Mobile from, object targeted) + protected override void OnTick() { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - protected virtual void OnNonlocalTarget(Mobile from, object targeted) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - protected virtual void OnCantSeeTarget(Mobile from, object targeted) - { - from.SendLocalizedMessage(500237); // Target can not be seen. - } - - protected virtual void OnTargetOutOfLOS(Mobile from, object targeted) - { - from.SendLocalizedMessage(500237); // Target can not be seen. - } - - protected virtual void OnTargetOutOfRange(Mobile from, object targeted) - { - from.SendLocalizedMessage(500446); // That is too far away. - } - - protected virtual void OnTargetDeleted(Mobile from, object targeted) - { - } - - protected virtual void OnTargetUntargetable(Mobile from, object targeted) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - protected virtual void OnTargetCancel(Mobile from, TargetCancelType cancelType) - { - } - - protected virtual void OnTargetFinish(Mobile from) - { - } - - private class TimeoutTimer : Timer - { - private readonly Mobile m_Mobile; - private readonly Target m_Target; - - public TimeoutTimer(Target target, Mobile m, TimeSpan delay) : base(delay) + if (m_Mobile.Target == m_Target) { - m_Target = target; - m_Mobile = m; - } - - protected override void OnTick() - { - if (m_Mobile.Target == m_Target) - { - m_Target.Timeout(m_Mobile); - } + m_Target.Timeout(m_Mobile); } } } diff --git a/Projects/Server/Targeting/TargetCancelType.cs b/Projects/Server/Targeting/TargetCancelType.cs index 1d6740d97..d5afb6b15 100644 --- a/Projects/Server/Targeting/TargetCancelType.cs +++ b/Projects/Server/Targeting/TargetCancelType.cs @@ -1,10 +1,9 @@ -namespace Server.Targeting +namespace Server.Targeting; + +public enum TargetCancelType { - public enum TargetCancelType - { - Overridden, - Canceled, - Disconnected, - Timeout - } + Overridden, + Canceled, + Disconnected, + Timeout } diff --git a/Projects/Server/Targeting/TargetFlags.cs b/Projects/Server/Targeting/TargetFlags.cs index 0d10bcad0..b338d35a7 100644 --- a/Projects/Server/Targeting/TargetFlags.cs +++ b/Projects/Server/Targeting/TargetFlags.cs @@ -1,12 +1,11 @@ using System; -namespace Server.Targeting +namespace Server.Targeting; + +[Flags] +public enum TargetFlags : byte { - [Flags] - public enum TargetFlags : byte - { - None = 0x00, - Harmful = 0x01, - Beneficial = 0x02 - } + None = 0x00, + Harmful = 0x01, + Beneficial = 0x02 } diff --git a/Projects/Server/Text/HexStringConverter.cs b/Projects/Server/Text/HexStringConverter.cs index 257c73642..dc2528081 100644 --- a/Projects/Server/Text/HexStringConverter.cs +++ b/Projects/Server/Text/HexStringConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: HexStringConverter.cs * * * @@ -15,119 +15,118 @@ using System; -namespace Server.Text -{ - public static class HexStringConverter - { - public static readonly uint[] m_Lookup32Chars = CreateLookup32Chars(); +namespace Server.Text; - private static uint[] CreateLookup32Chars() +public static class HexStringConverter +{ + public static readonly uint[] m_Lookup32Chars = CreateLookup32Chars(); + + private static uint[] CreateLookup32Chars() + { + var result = new uint[256]; + for (var i = 0; i < 256; i++) { - var result = new uint[256]; - for (var i = 0; i < 256; i++) + var s = i.ToString("X2"); + if (BitConverter.IsLittleEndian) { - var s = i.ToString("X2"); + result[i] = s[0] + ((uint)s[1] << 16); + } + else + { + result[i] = s[1] + ((uint)s[0] << 16); + } + } + + return result; + } + + public static string ToHexString(this byte[] bytes) => new ReadOnlySpan(bytes).ToHexString(); + + public static string ToHexString(this Span bytes) => ((ReadOnlySpan)bytes).ToHexString(); + + public static unsafe string ToHexString(this ReadOnlySpan bytes) + { + var result = new string((char)0, bytes.Length * 2); + fixed (char* resultP = result) + { + var resultP2 = (uint*)resultP; + for (var i = 0; i < bytes.Length; i++) + { + resultP2[i] = m_Lookup32Chars[bytes[i]]; + } + } + + return result; + } + + public static unsafe int ToSpacedHexString(this ReadOnlySpan bytes, Span result) + { + var charsWritten = 0; + fixed (char* resultP = result) + { + for (int i = 0; i < bytes.Length; i++) + { + var resultP2 = (uint*)(resultP + charsWritten); + *resultP2 = m_Lookup32Chars[bytes[i]]; + charsWritten += 2; + + if (i < bytes.Length - 1) + { + *(resultP + charsWritten++) = ' '; + } + } + } + + return charsWritten; + } + + public static string ToDelimitedHexString(this byte[] bytes) => ((ReadOnlySpan)bytes).ToDelimitedHexString(); + + public static unsafe string ToDelimitedHexString(this ReadOnlySpan bytes) + { + const uint delimiter = 0x20002C; // ", " + const char openBracket = '['; + const char closeBracket = ']'; + var length = Math.Max(2, bytes.Length * 4); // len * 2 + (len - 1) * 2 + 2 + + var result = new string((char)0, length); + fixed (char* resultP = result) + { + resultP[0] = openBracket; + resultP[length - 1] = closeBracket; + + var resultP2 = (uint*)(resultP + 1); + for (int a = 0, i = 0; a < bytes.Length; a++, i++) + { + if (a > 0) + { + resultP2[i++] = delimiter; + } + + resultP2[i] = m_Lookup32Chars[bytes[a]]; + } + } + + return result; + } + + public static unsafe void GetBytes(this string str, Span bytes) + { + fixed (char* strP = str) + { + var i = 0; + var j = 0; + while (i < str.Length) + { + int chr1 = strP[i++]; + int chr2 = strP[i++]; if (BitConverter.IsLittleEndian) { - result[i] = s[0] + ((uint)s[1] << 16); + bytes[j++] = (byte)(((chr1 - (chr1 >= 65 ? 55 : 48)) << 4) | (chr2 - (chr2 >= 65 ? 55 : 48))); } else { - result[i] = s[1] + ((uint)s[0] << 16); - } - } - - return result; - } - - public static string ToHexString(this byte[] bytes) => new ReadOnlySpan(bytes).ToHexString(); - - public static string ToHexString(this Span bytes) => ((ReadOnlySpan)bytes).ToHexString(); - - public static unsafe string ToHexString(this ReadOnlySpan bytes) - { - var result = new string((char)0, bytes.Length * 2); - fixed (char* resultP = result) - { - var resultP2 = (uint*)resultP; - for (var i = 0; i < bytes.Length; i++) - { - resultP2[i] = m_Lookup32Chars[bytes[i]]; - } - } - - return result; - } - - public static unsafe int ToSpacedHexString(this ReadOnlySpan bytes, Span result) - { - var charsWritten = 0; - fixed (char* resultP = result) - { - for (int i = 0; i < bytes.Length; i++) - { - var resultP2 = (uint*)(resultP + charsWritten); - *resultP2 = m_Lookup32Chars[bytes[i]]; - charsWritten += 2; - - if (i < bytes.Length - 1) - { - *(resultP + charsWritten++) = ' '; - } - } - } - - return charsWritten; - } - - public static string ToDelimitedHexString(this byte[] bytes) => ((ReadOnlySpan)bytes).ToDelimitedHexString(); - - public static unsafe string ToDelimitedHexString(this ReadOnlySpan bytes) - { - const uint delimiter = 0x20002C; // ", " - const char openBracket = '['; - const char closeBracket = ']'; - var length = Math.Max(2, bytes.Length * 4); // len * 2 + (len - 1) * 2 + 2 - - var result = new string((char)0, length); - fixed (char* resultP = result) - { - resultP[0] = openBracket; - resultP[length - 1] = closeBracket; - - var resultP2 = (uint*)(resultP + 1); - for (int a = 0, i = 0; a < bytes.Length; a++, i++) - { - if (a > 0) - { - resultP2[i++] = delimiter; - } - - resultP2[i] = m_Lookup32Chars[bytes[a]]; - } - } - - return result; - } - - public static unsafe void GetBytes(this string str, Span bytes) - { - fixed (char* strP = str) - { - var i = 0; - var j = 0; - while (i < str.Length) - { - int chr1 = strP[i++]; - int chr2 = strP[i++]; - if (BitConverter.IsLittleEndian) - { - bytes[j++] = (byte)(((chr1 - (chr1 >= 65 ? 55 : 48)) << 4) | (chr2 - (chr2 >= 65 ? 55 : 48))); - } - else - { - bytes[j++] = (byte)((chr1 - (chr1 >= 65 ? 55 : 48)) | ((chr2 - (chr2 >= 65 ? 55 : 48)) << 4)); - } + bytes[j++] = (byte)((chr1 - (chr1 >= 65 ? 55 : 48)) | ((chr2 - (chr2 >= 65 ? 55 : 48)) << 4)); } } } diff --git a/Projects/Server/Text/InsensitiveStringHelpers.cs b/Projects/Server/Text/InsensitiveStringHelpers.cs index c04fbc07c..acd552152 100644 --- a/Projects/Server/Text/InsensitiveStringHelpers.cs +++ b/Projects/Server/Text/InsensitiveStringHelpers.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: InsensitiveStringHelpers.cs * * * @@ -16,87 +16,86 @@ using System; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public static class InsensitiveStringHelpers { - public static class InsensitiveStringHelpers - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int InsensitiveCompare(this ReadOnlySpan a, ReadOnlySpan b) => - a.CompareTo(b, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int InsensitiveCompare(this ReadOnlySpan a, ReadOnlySpan b) => + a.CompareTo(b, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int InsensitiveCompare(this string a, string b) => StringComparer.Ordinal.Compare(a, b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int InsensitiveCompare(this string a, string b) => StringComparer.Ordinal.Compare(a, b); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveEquals(this ReadOnlySpan a, string b) => - a.Equals(b, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveEquals(this ReadOnlySpan a, string b) => + a.Equals(b, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveEquals(this string a, string b) => - a?.Equals(b, StringComparison.OrdinalIgnoreCase) ?? b == null; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveEquals(this string a, string b) => + a?.Equals(b, StringComparison.OrdinalIgnoreCase) ?? b == null; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveStartsWith(this ReadOnlySpan a, ReadOnlySpan b) => - a.StartsWith(b, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveStartsWith(this ReadOnlySpan a, ReadOnlySpan b) => + a.StartsWith(b, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveStartsWith(this string a, string b) => - a?.StartsWith(b, StringComparison.OrdinalIgnoreCase) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveStartsWith(this string a, string b) => + a?.StartsWith(b, StringComparison.OrdinalIgnoreCase) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveEndsWith(this string a, string b) => - a?.EndsWith(b, StringComparison.OrdinalIgnoreCase) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveEndsWith(this string a, string b) => + a?.EndsWith(b, StringComparison.OrdinalIgnoreCase) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveEndsWith(this ReadOnlySpan a, ReadOnlySpan b) => - a.EndsWith(b, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveEndsWith(this ReadOnlySpan a, ReadOnlySpan b) => + a.EndsWith(b, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveContains(this ReadOnlySpan a, string b) => - a.Contains(b, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveContains(this ReadOnlySpan a, string b) => + a.Contains(b, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveContains(this string a, string b) => - a?.Contains(b, StringComparison.OrdinalIgnoreCase) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveContains(this string a, string b) => + a?.Contains(b, StringComparison.OrdinalIgnoreCase) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveContains(this ReadOnlySpan a, ReadOnlySpan b) => - a.Contains(b, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveContains(this ReadOnlySpan a, ReadOnlySpan b) => + a.Contains(b, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool InsensitiveContains(this string a, char b) => - a?.Contains(b, StringComparison.OrdinalIgnoreCase) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InsensitiveContains(this string a, char b) => + a?.Contains(b, StringComparison.OrdinalIgnoreCase) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int InsensitiveIndexOf(this string a, char b) => - a?.IndexOf(b, StringComparison.OrdinalIgnoreCase) ?? -1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int InsensitiveIndexOf(this string a, char b) => + a?.IndexOf(b, StringComparison.OrdinalIgnoreCase) ?? -1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int InsensitiveIndexOf(this string a, string b) => - a?.IndexOf(b, StringComparison.OrdinalIgnoreCase) ?? -1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int InsensitiveIndexOf(this string a, string b) => + a?.IndexOf(b, StringComparison.OrdinalIgnoreCase) ?? -1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int InsensitiveIndexOf(this string a, string b, int startIndex) => - a?.IndexOf(b, startIndex, StringComparison.OrdinalIgnoreCase) ?? -1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int InsensitiveIndexOf(this string a, string b, int startIndex) => + a?.IndexOf(b, startIndex, StringComparison.OrdinalIgnoreCase) ?? -1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int InsensitiveIndexOf(this ReadOnlySpan a, ReadOnlySpan b) => - a.IndexOf(b, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int InsensitiveIndexOf(this ReadOnlySpan a, ReadOnlySpan b) => + a.IndexOf(b, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string InsensitiveReplace(this string a, string o, string n) => - a?.Replace(o, n, StringComparison.OrdinalIgnoreCase); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string InsensitiveReplace(this string a, string o, string n) => + a?.Replace(o, n, StringComparison.OrdinalIgnoreCase); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InsensitiveRemove( - this ReadOnlySpan a, - ReadOnlySpan b, - Span buffer, - out int size - ) => a.Remove(b, StringComparison.OrdinalIgnoreCase, buffer, out size); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InsensitiveRemove( + this ReadOnlySpan a, + ReadOnlySpan b, + Span buffer, + out int size + ) => a.Remove(b, StringComparison.OrdinalIgnoreCase, buffer, out size); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string InsensitiveRemove(this ReadOnlySpan a, ReadOnlySpan b) => - a.Remove(b, StringComparison.OrdinalIgnoreCase); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string InsensitiveRemove(this ReadOnlySpan a, ReadOnlySpan b) => + a.Remove(b, StringComparison.OrdinalIgnoreCase); } diff --git a/Projects/Server/Text/OrdinalStringHelpers.cs b/Projects/Server/Text/OrdinalStringHelpers.cs index 3bbe662a5..18a1ada4b 100644 --- a/Projects/Server/Text/OrdinalStringHelpers.cs +++ b/Projects/Server/Text/OrdinalStringHelpers.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OrdinalStringHelpers.cs * * * @@ -16,85 +16,84 @@ using System; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public static class OrdinalStringHelpers { - public static class OrdinalStringHelpers - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CompareOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => - a.CompareTo(b, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CompareOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => + a.CompareTo(b, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int CompareOrdinal(this string a, string b) => StringComparer.Ordinal.Compare(a, b); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CompareOrdinal(this string a, string b) => StringComparer.Ordinal.Compare(a, b); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EqualsOrdinal(this ReadOnlySpan a, string b) => - a.Equals(b, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this ReadOnlySpan a, string b) => + a.Equals(b, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EqualsOrdinal(this string a, string b) => - a?.Equals(b, StringComparison.Ordinal) ?? b == null; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsOrdinal(this string a, string b) => + a?.Equals(b, StringComparison.Ordinal) ?? b == null; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool StartsWithOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => - a.StartsWith(b, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWithOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => + a.StartsWith(b, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool StartsWithOrdinal(this string a, string b) => - a?.StartsWith(b, StringComparison.Ordinal) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool StartsWithOrdinal(this string a, string b) => + a?.StartsWith(b, StringComparison.Ordinal) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EndsWithOrdinal(this string a, string b) => - a?.EndsWith(b, StringComparison.Ordinal) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWithOrdinal(this string a, string b) => + a?.EndsWith(b, StringComparison.Ordinal) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EndsWithOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => - a.EndsWith(b, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EndsWithOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => + a.EndsWith(b, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ContainsOrdinal(this ReadOnlySpan a, string b) => - a.Contains(b, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ContainsOrdinal(this ReadOnlySpan a, string b) => + a.Contains(b, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ContainsOrdinal(this string a, string b) => - a?.Contains(b, StringComparison.Ordinal) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ContainsOrdinal(this string a, string b) => + a?.Contains(b, StringComparison.Ordinal) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ContainsOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => - a.Contains(b, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ContainsOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => + a.Contains(b, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ContainsOrdinal(this string a, char b) => - a?.Contains(b, StringComparison.Ordinal) == true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ContainsOrdinal(this string a, char b) => + a?.Contains(b, StringComparison.Ordinal) == true; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfOrdinal(this string a, char b) => a?.IndexOf(b, StringComparison.Ordinal) ?? -1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfOrdinal(this string a, char b) => a?.IndexOf(b, StringComparison.Ordinal) ?? -1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfOrdinal(this string a, string b) => a?.IndexOf(b, StringComparison.Ordinal) ?? -1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfOrdinal(this string a, string b) => a?.IndexOf(b, StringComparison.Ordinal) ?? -1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfOrdinal(this string a, string b, int startIndex) => - a?.IndexOf(b, startIndex, StringComparison.Ordinal) ?? -1; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfOrdinal(this string a, string b, int startIndex) => + a?.IndexOf(b, startIndex, StringComparison.Ordinal) ?? -1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => - a.IndexOf(b, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => + a.IndexOf(b, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ReplaceOrdinal(this string a, string o, string n) => - a?.Replace(o, n, StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReplaceOrdinal(this string a, string o, string n) => + a?.Replace(o, n, StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string RemoveOrdinal(this string a, string b) => - a?.Replace(b, "", StringComparison.Ordinal); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string RemoveOrdinal(this string a, string b) => + a?.Replace(b, "", StringComparison.Ordinal); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void RemoveOrdinal(this ReadOnlySpan a, ReadOnlySpan b, Span buffer, out int size) => - a.Remove(b, StringComparison.Ordinal, buffer, out size); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RemoveOrdinal(this ReadOnlySpan a, ReadOnlySpan b, Span buffer, out int size) => + a.Remove(b, StringComparison.Ordinal, buffer, out size); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string RemoveOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => - a.Remove(b, StringComparison.Ordinal); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string RemoveOrdinal(this ReadOnlySpan a, ReadOnlySpan b) => + a.Remove(b, StringComparison.Ordinal); } diff --git a/Projects/Server/Text/TextEncoding.cs b/Projects/Server/Text/TextEncoding.cs index 41d6df2b2..d50ca3f7e 100644 --- a/Projects/Server/Text/TextEncoding.cs +++ b/Projects/Server/Text/TextEncoding.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TextEncoding.cs * * * @@ -18,131 +18,130 @@ using System.Runtime.CompilerServices; using System.Text; using Server.Buffers; -namespace Server.Text +namespace Server.Text; + +public static class TextEncoding { - public static class TextEncoding + private static Encoding m_UTF8, m_Unicode, m_UnicodeLE; + + public static Encoding UTF8 => m_UTF8 ??= new UTF8Encoding(false, false); + public static Encoding Unicode => m_Unicode ??= new UnicodeEncoding(true, false, false); + public static Encoding UnicodeLE => m_UnicodeLE ??= new UnicodeEncoding(false, false, false); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesAscii(this string str) => GetBytes(str, Encoding.ASCII); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesBigUni(this string str) => GetBytes(str, Unicode); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesLittleUni(this string str) => GetBytes(str, UnicodeLE); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesUtf8(this string str) => GetBytes(str, UTF8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesAscii(this ReadOnlySpan str) => GetBytes(str, Encoding.ASCII); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesBigUni(this ReadOnlySpan str) => GetBytes(str, Unicode); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesLittleUni(this ReadOnlySpan str) => GetBytes(str, UnicodeLE); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] GetBytesUtf8(this ReadOnlySpan str) => GetBytes(str, UTF8); + + // Unlike the one built into the encoder, this avoids local init + public static byte[] GetBytes(ReadOnlySpan str, Encoding encoding) { - private static Encoding m_UTF8, m_Unicode, m_UnicodeLE; - - public static Encoding UTF8 => m_UTF8 ??= new UTF8Encoding(false, false); - public static Encoding Unicode => m_Unicode ??= new UnicodeEncoding(true, false, false); - public static Encoding UnicodeLE => m_UnicodeLE ??= new UnicodeEncoding(false, false, false); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesAscii(this string str) => GetBytes(str, Encoding.ASCII); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesBigUni(this string str) => GetBytes(str, Unicode); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesLittleUni(this string str) => GetBytes(str, UnicodeLE); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesUtf8(this string str) => GetBytes(str, UTF8); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesAscii(this ReadOnlySpan str) => GetBytes(str, Encoding.ASCII); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesBigUni(this ReadOnlySpan str) => GetBytes(str, Unicode); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesLittleUni(this ReadOnlySpan str) => GetBytes(str, UnicodeLE); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] GetBytesUtf8(this ReadOnlySpan str) => GetBytes(str, UTF8); - - // Unlike the one built into the encoder, this avoids local init - public static byte[] GetBytes(ReadOnlySpan str, Encoding encoding) + if (str.Length == 0) { - if (str.Length == 0) - { - return Array.Empty(); - } - - var bytes = GC.AllocateUninitializedArray(encoding.GetByteCount(str)); - encoding.GetBytes(str, bytes); - - return bytes; + return Array.Empty(); } - // Unlike the one built into the encoder, this avoids local init - public static byte[] GetBytes(string str, Encoding encoding) + var bytes = GC.AllocateUninitializedArray(encoding.GetByteCount(str)); + encoding.GetBytes(str, bytes); + + return bytes; + } + + // Unlike the one built into the encoder, this avoids local init + public static byte[] GetBytes(string str, Encoding encoding) + { + if (str.Length == 0) { - if (str.Length == 0) - { - return Array.Empty(); - } - - var bytes = GC.AllocateUninitializedArray(encoding.GetByteCount(str)); - encoding.GetBytes(str, 0, str.Length, bytes, 0); - - return bytes; + return Array.Empty(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesAscii(this string str, Span buffer) => Encoding.ASCII.GetBytes(str, buffer); + var bytes = GC.AllocateUninitializedArray(encoding.GetByteCount(str)); + encoding.GetBytes(str, 0, str.Length, bytes, 0); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesAscii(this ReadOnlySpan str, Span buffer) => Encoding.ASCII.GetBytes(str, buffer); + return bytes; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesBigUni(this string str, Span buffer) => Unicode.GetBytes(str, buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesAscii(this string str, Span buffer) => Encoding.ASCII.GetBytes(str, buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesBigUni(this ReadOnlySpan str, Span buffer) => Unicode.GetBytes(str, buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesAscii(this ReadOnlySpan str, Span buffer) => Encoding.ASCII.GetBytes(str, buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesLittleUni(this string str, Span buffer) => UnicodeLE.GetBytes(str, buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesBigUni(this string str, Span buffer) => Unicode.GetBytes(str, buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesLittleUni(this ReadOnlySpan str, Span buffer) => UnicodeLE.GetBytes(str, buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesBigUni(this ReadOnlySpan str, Span buffer) => Unicode.GetBytes(str, buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesUtf8(this string str, Span buffer) => UTF8.GetBytes(str, buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesLittleUni(this string str, Span buffer) => UnicodeLE.GetBytes(str, buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetBytesUtf8(this ReadOnlySpan str, Span buffer) => UTF8.GetBytes(str, buffer); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesLittleUni(this ReadOnlySpan str, Span buffer) => UnicodeLE.GetBytes(str, buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetByteLengthForEncoding(this Encoding encoding) => - encoding.BodyName switch - { - "utf-16BE" => 2, - "utf-16" => 2, - "utf-32BE" => 3, - "utf-32" => 3, - _ => 1 - }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesUtf8(this string str, Span buffer) => UTF8.GetBytes(str, buffer); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSafeChar(ushort c) => c >= 0x20 && c < 0xFFFE; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBytesUtf8(this ReadOnlySpan str, Span buffer) => UTF8.GetBytes(str, buffer); - public static string GetString(ReadOnlySpan span, Encoding encoding, bool safeString = false) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetByteLengthForEncoding(this Encoding encoding) => + encoding.BodyName switch { - string s = encoding.GetString(span); + "utf-16BE" => 2, + "utf-16" => 2, + "utf-32BE" => 3, + "utf-32" => 3, + _ => 1 + }; - if (!safeString) - { - return s; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeChar(ushort c) => c >= 0x20 && c < 0xFFFE; - ReadOnlySpan chars = s.AsSpan(); + public static string GetString(ReadOnlySpan span, Encoding encoding, bool safeString = false) + { + string s = encoding.GetString(span); - using var sb = new ValueStringBuilder(stackalloc char[256]); - var hasDoneAnyReplacements = false; - - for (int i = 0, last = 0; i < chars.Length; i++) - { - if (!IsSafeChar(chars[i]) && i == chars.Length - 1) - { - hasDoneAnyReplacements = true; - sb.Append(chars.Slice(last, i - last)); - last = i + 1; // Skip the unsafe char - } - } - - return !hasDoneAnyReplacements ? s : sb.ToString(); + if (!safeString) + { + return s; } + + ReadOnlySpan chars = s.AsSpan(); + + using var sb = new ValueStringBuilder(stackalloc char[256]); + var hasDoneAnyReplacements = false; + + for (int i = 0, last = 0; i < chars.Length; i++) + { + if (!IsSafeChar(chars[i]) && i == chars.Length - 1) + { + hasDoneAnyReplacements = true; + sb.Append(chars.Slice(last, i - last)); + last = i + 1; // Skip the unsafe char + } + } + + return !hasDoneAnyReplacements ? s : sb.ToString(); } } diff --git a/Projects/Server/TileData.cs b/Projects/Server/TileData.cs index 6e2b66aaf..d408e78d8 100644 --- a/Projects/Server/TileData.cs +++ b/Projects/Server/TileData.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TileData.cs * * * @@ -18,368 +18,367 @@ using System.IO; using System.Runtime.CompilerServices; using System.Text; -namespace Server +namespace Server; + +public struct LandData { - public struct LandData + public string Name { get; set; } + + public TileFlag Flags { get; set; } + + public LandData(string name, TileFlag flags) { - public string Name { get; set; } + Name = name; + Flags = flags; + } +} - public TileFlag Flags { get; set; } +[PropertyObject] +public struct ItemData +{ + private byte _weight; + private byte _quality; + private ushort _animation; + private byte _quantity; + private byte _value; + private byte _height; - public LandData(string name, TileFlag flags) - { - Name = name; - Flags = flags; - } + public ItemData(string name, TileFlag flags, int weight, int quality, int animation, int quantity, int value, int height) + { + Name = name; + Flags = flags; + _weight = (byte)weight; + _quality = (byte)quality; + _animation = (ushort)animation; + _quantity = (byte)quantity; + _value = (byte)value; + _height = (byte)height; } - [PropertyObject] - public struct ItemData + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public string Name { get; set; } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public TileFlag Flags { get; set; } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int Weight { - private byte _weight; - private byte _quality; - private ushort _animation; - private byte _quantity; - private byte _value; - private byte _height; - - public ItemData(string name, TileFlag flags, int weight, int quality, int animation, int quantity, int value, int height) - { - Name = name; - Flags = flags; - _weight = (byte)weight; - _quality = (byte)quality; - _animation = (ushort)animation; - _quantity = (byte)quantity; - _value = (byte)value; - _height = (byte)height; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public string Name { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public TileFlag Flags { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public int Weight - { - get => _weight; - set => _weight = (byte)value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public int Quality - { - get => _quality; - set => _quality = (byte)value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public int Animation - { - get => _animation; - set => _animation = (ushort)value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public int Quantity - { - get => _quantity; - set => _quantity = (byte)value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public int Value - { - get => _value; - set => _value = (byte)value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public int Height - { - get => _height; - set => _height = (byte)value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public int CalcHeight => Bridge ? _height / 2 : _height; - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Door - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Door]; - set => this[TileFlag.Door] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Background - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Background]; - set => this[TileFlag.Background] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Bridge - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Bridge]; - set => this[TileFlag.Bridge] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Wall - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Wall]; - set => this[TileFlag.Wall] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Window - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Window]; - set => this[TileFlag.Window] = value; - } - - public bool ImpassableSurface - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Impassable | TileFlag.Surface]; - set => this[TileFlag.Impassable | TileFlag.Surface] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Impassable - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Impassable]; - set => this[TileFlag.Impassable] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Surface - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Surface]; - set => this[TileFlag.Surface] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Roof - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Roof]; - set => this[TileFlag.Roof] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool LightSource - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.LightSource]; - set => this[TileFlag.LightSource] = value; - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Wet - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this[TileFlag.Wet]; - set => this[TileFlag.Wet] = value; - } - - public bool this[TileFlag flag] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (Flags & flag) != 0; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - if (value) - { - Flags |= flag; - } - else - { - Flags &= ~flag; - } - } - } + get => _weight; + set => _weight = (byte)value; } - [Flags] - public enum TileFlag : ulong + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int Quality { - None = 0x00000000, - Background = 0x00000001, - Weapon = 0x00000002, - Transparent = 0x00000004, - Translucent = 0x00000008, - Wall = 0x00000010, - Damaging = 0x00000020, - Impassable = 0x00000040, - Wet = 0x00000080, - Unknown1 = 0x00000100, - Surface = 0x00000200, - Bridge = 0x00000400, - Generic = 0x00000800, - Window = 0x00001000, - NoShoot = 0x00002000, - ArticleA = 0x00004000, - ArticleAn = 0x00008000, - Internal = 0x00010000, - Foliage = 0x00020000, - PartialHue = 0x00040000, - Unknown2 = 0x00080000, - Map = 0x00100000, - Container = 0x00200000, - Wearable = 0x00400000, - LightSource = 0x00800000, - Animation = 0x01000000, - NoDiagonal = 0x02000000, - Unknown3 = 0x04000000, - Armor = 0x08000000, - Roof = 0x10000000, - Door = 0x20000000, - StairBack = 0x40000000, - StairRight = 0x80000000, - - HS33 = 0x0000000100000000, - HS34 = 0x0000000200000000, - HS35 = 0x0000000400000000, - HS36 = 0x0000000800000000, - HS37 = 0x0000001000000000, - HS38 = 0x0000002000000000, - HS39 = 0x0000004000000000, - HS40 = 0x0000008000000000, - HS41 = 0x0000010000000000, - HS42 = 0x0000020000000000, - HS43 = 0x0000040000000000, - HS44 = 0x0000080000000000, - HS45 = 0x0000100000000000, - HS46 = 0x0000200000000000, - HS47 = 0x0000400000000000, - HS48 = 0x0000800000000000, - HS49 = 0x0001000000000000, - HS50 = 0x0002000000000000, - HS51 = 0x0004000000000000, - HS52 = 0x0008000000000000, - HS53 = 0x0010000000000000, - HS54 = 0x0020000000000000, - HS55 = 0x0040000000000000, - HS56 = 0x0080000000000000, - HS57 = 0x0100000000000000, - HS58 = 0x0200000000000000, - HS59 = 0x0400000000000000, - HS60 = 0x0800000000000000, - HS61 = 0x1000000000000000, - HS62 = 0x2000000000000000, - HS63 = 0x4000000000000000, - HS64 = 0x8000000000000000 + get => _quality; + set => _quality = (byte)value; } - public static class TileData + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int Animation { - public static LandData[] LandTable { get; } = new LandData[0x4000]; - public static ItemData[] ItemTable { get; } = new ItemData[0x10000]; - public static int MaxLandValue { get; private set; } - public static int MaxItemValue { get; private set; } + get => _animation; + set => _animation = (ushort)value; + } - static TileData() + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int Quantity + { + get => _quantity; + set => _quantity = (byte)value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int Value + { + get => _value; + set => _value = (byte)value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int Height + { + get => _height; + set => _height = (byte)value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int CalcHeight => Bridge ? _height / 2 : _height; + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Door + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Door]; + set => this[TileFlag.Door] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Background + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Background]; + set => this[TileFlag.Background] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Bridge + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Bridge]; + set => this[TileFlag.Bridge] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Wall + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Wall]; + set => this[TileFlag.Wall] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Window + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Window]; + set => this[TileFlag.Window] = value; + } + + public bool ImpassableSurface + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Impassable | TileFlag.Surface]; + set => this[TileFlag.Impassable | TileFlag.Surface] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Impassable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Impassable]; + set => this[TileFlag.Impassable] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Surface + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Surface]; + set => this[TileFlag.Surface] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Roof + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Roof]; + set => this[TileFlag.Roof] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool LightSource + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.LightSource]; + set => this[TileFlag.LightSource] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Wet + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this[TileFlag.Wet]; + set => this[TileFlag.Wet] = value; + } + + public bool this[TileFlag flag] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (Flags & flag) != 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { - if (Core.IsRunningFromXUnit) + if (value) { - return; - } - - Load(); - } - - private static unsafe void Load() - { - var filePath = Core.FindDataFile("tiledata.mul"); - - using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var bin = new BinaryReader(fs); - - bool is64BitFlags; - const int landLength = 0x4000; - int itemLength; - - if (fs.Length >= 3188736) // 7.0.9.0 - { - is64BitFlags = true; - itemLength = 0x10000; - } - else if (fs.Length >= 1644544) // 7.0.0.0 - { - is64BitFlags = false; - itemLength = 0x8000; + Flags |= flag; } else { - is64BitFlags = false; - itemLength = 0x4000; + Flags &= ~flag; } - - Span buffer = stackalloc byte[20]; - - for (var i = 0; i < landLength; i++) - { - if (is64BitFlags ? i == 1 || i > 0 && (i & 0x1F) == 0 : (i & 0x1F) == 0) - { - bin.ReadInt32(); // header - } - - var flags = (TileFlag)(is64BitFlags ? bin.ReadUInt64() : bin.ReadUInt32()); - bin.ReadInt16(); // skip 2 bytes -- textureID - - bin.Read(buffer); - var terminator = buffer.IndexOfTerminator(1); - var name = Encoding.ASCII.GetString(buffer[..(terminator < 0 ? buffer.Length : terminator)]); - LandTable[i] = new LandData(Utility.Intern(name), flags); - } - - for (var i = 0; i < itemLength; i++) - { - if ((i & 0x1F) == 0) - { - bin.ReadInt32(); // header - } - - var flags = (TileFlag)(is64BitFlags ? bin.ReadUInt64() : bin.ReadUInt32()); - int weight = bin.ReadByte(); - int quality = bin.ReadByte(); - int animation = bin.ReadUInt16(); - bin.ReadByte(); - int quantity = bin.ReadByte(); - bin.ReadInt32(); - bin.ReadByte(); - int value = bin.ReadByte(); - int height = bin.ReadByte(); - - bin.Read(buffer); - var terminator = buffer.IndexOfTerminator(1); - var name = Encoding.ASCII.GetString(buffer[..(terminator < 0 ? buffer.Length : terminator)]); - ItemTable[i] = new ItemData( - Utility.Intern(name), - flags, - weight, - quality, - animation, - quantity, - value, - height - ); - } - - MaxLandValue = LandTable.Length - 1; - MaxItemValue = ItemTable.Length - 1; } } } + +[Flags] +public enum TileFlag : ulong +{ + None = 0x00000000, + Background = 0x00000001, + Weapon = 0x00000002, + Transparent = 0x00000004, + Translucent = 0x00000008, + Wall = 0x00000010, + Damaging = 0x00000020, + Impassable = 0x00000040, + Wet = 0x00000080, + Unknown1 = 0x00000100, + Surface = 0x00000200, + Bridge = 0x00000400, + Generic = 0x00000800, + Window = 0x00001000, + NoShoot = 0x00002000, + ArticleA = 0x00004000, + ArticleAn = 0x00008000, + Internal = 0x00010000, + Foliage = 0x00020000, + PartialHue = 0x00040000, + Unknown2 = 0x00080000, + Map = 0x00100000, + Container = 0x00200000, + Wearable = 0x00400000, + LightSource = 0x00800000, + Animation = 0x01000000, + NoDiagonal = 0x02000000, + Unknown3 = 0x04000000, + Armor = 0x08000000, + Roof = 0x10000000, + Door = 0x20000000, + StairBack = 0x40000000, + StairRight = 0x80000000, + + HS33 = 0x0000000100000000, + HS34 = 0x0000000200000000, + HS35 = 0x0000000400000000, + HS36 = 0x0000000800000000, + HS37 = 0x0000001000000000, + HS38 = 0x0000002000000000, + HS39 = 0x0000004000000000, + HS40 = 0x0000008000000000, + HS41 = 0x0000010000000000, + HS42 = 0x0000020000000000, + HS43 = 0x0000040000000000, + HS44 = 0x0000080000000000, + HS45 = 0x0000100000000000, + HS46 = 0x0000200000000000, + HS47 = 0x0000400000000000, + HS48 = 0x0000800000000000, + HS49 = 0x0001000000000000, + HS50 = 0x0002000000000000, + HS51 = 0x0004000000000000, + HS52 = 0x0008000000000000, + HS53 = 0x0010000000000000, + HS54 = 0x0020000000000000, + HS55 = 0x0040000000000000, + HS56 = 0x0080000000000000, + HS57 = 0x0100000000000000, + HS58 = 0x0200000000000000, + HS59 = 0x0400000000000000, + HS60 = 0x0800000000000000, + HS61 = 0x1000000000000000, + HS62 = 0x2000000000000000, + HS63 = 0x4000000000000000, + HS64 = 0x8000000000000000 +} + +public static class TileData +{ + public static LandData[] LandTable { get; } = new LandData[0x4000]; + public static ItemData[] ItemTable { get; } = new ItemData[0x10000]; + public static int MaxLandValue { get; private set; } + public static int MaxItemValue { get; private set; } + + static TileData() + { + if (Core.IsRunningFromXUnit) + { + return; + } + + Load(); + } + + private static unsafe void Load() + { + var filePath = Core.FindDataFile("tiledata.mul"); + + using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var bin = new BinaryReader(fs); + + bool is64BitFlags; + const int landLength = 0x4000; + int itemLength; + + if (fs.Length >= 3188736) // 7.0.9.0 + { + is64BitFlags = true; + itemLength = 0x10000; + } + else if (fs.Length >= 1644544) // 7.0.0.0 + { + is64BitFlags = false; + itemLength = 0x8000; + } + else + { + is64BitFlags = false; + itemLength = 0x4000; + } + + Span buffer = stackalloc byte[20]; + + for (var i = 0; i < landLength; i++) + { + if (is64BitFlags ? i == 1 || i > 0 && (i & 0x1F) == 0 : (i & 0x1F) == 0) + { + bin.ReadInt32(); // header + } + + var flags = (TileFlag)(is64BitFlags ? bin.ReadUInt64() : bin.ReadUInt32()); + bin.ReadInt16(); // skip 2 bytes -- textureID + + bin.Read(buffer); + var terminator = buffer.IndexOfTerminator(1); + var name = Encoding.ASCII.GetString(buffer[..(terminator < 0 ? buffer.Length : terminator)]); + LandTable[i] = new LandData(Utility.Intern(name), flags); + } + + for (var i = 0; i < itemLength; i++) + { + if ((i & 0x1F) == 0) + { + bin.ReadInt32(); // header + } + + var flags = (TileFlag)(is64BitFlags ? bin.ReadUInt64() : bin.ReadUInt32()); + int weight = bin.ReadByte(); + int quality = bin.ReadByte(); + int animation = bin.ReadUInt16(); + bin.ReadByte(); + int quantity = bin.ReadByte(); + bin.ReadInt32(); + bin.ReadByte(); + int value = bin.ReadByte(); + int height = bin.ReadByte(); + + bin.Read(buffer); + var terminator = buffer.IndexOfTerminator(1); + var name = Encoding.ASCII.GetString(buffer[..(terminator < 0 ? buffer.Length : terminator)]); + ItemTable[i] = new ItemData( + Utility.Intern(name), + flags, + weight, + quality, + animation, + quantity, + value, + height + ); + } + + MaxLandValue = LandTable.Length - 1; + MaxItemValue = ItemTable.Length - 1; + } +} diff --git a/Projects/Server/TileList.cs b/Projects/Server/TileList.cs index 04f11c240..3622b537e 100644 --- a/Projects/Server/TileList.cs +++ b/Projects/Server/TileList.cs @@ -1,74 +1,73 @@ using System; -namespace Server +namespace Server; + +public class TileList { - public class TileList + private static readonly StaticTile[] m_EmptyTiles = Array.Empty(); + private StaticTile[] m_Tiles; + + public TileList() { - private static readonly StaticTile[] m_EmptyTiles = Array.Empty(); - private StaticTile[] m_Tiles; + m_Tiles = new StaticTile[8]; + Count = 0; + } - public TileList() + public int Count { get; private set; } + + public void AddRange(StaticTile[] tiles) + { + if (Count + tiles.Length > m_Tiles.Length) { - m_Tiles = new StaticTile[8]; - Count = 0; - } + var old = m_Tiles; + m_Tiles = new StaticTile[(Count + tiles.Length) * 2]; - public int Count { get; private set; } - - public void AddRange(StaticTile[] tiles) - { - if (Count + tiles.Length > m_Tiles.Length) + for (var i = 0; i < old.Length; ++i) { - var old = m_Tiles; - m_Tiles = new StaticTile[(Count + tiles.Length) * 2]; - - for (var i = 0; i < old.Length; ++i) - { - m_Tiles[i] = old[i]; - } - } - - for (var i = 0; i < tiles.Length; ++i) - { - m_Tiles[Count++] = tiles[i]; + m_Tiles[i] = old[i]; } } - public void Add(ushort id, sbyte z) + for (var i = 0; i < tiles.Length; ++i) { - if (Count + 1 > m_Tiles.Length) - { - var old = m_Tiles; - m_Tiles = new StaticTile[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - { - m_Tiles[i] = old[i]; - } - } - - m_Tiles[Count].m_ID = id; - m_Tiles[Count].m_Z = z; - ++Count; - } - - public StaticTile[] ToArray() - { - if (Count == 0) - { - return m_EmptyTiles; - } - - var tiles = new StaticTile[Count]; - - for (var i = 0; i < Count; ++i) - { - tiles[i] = m_Tiles[i]; - } - - Count = 0; - - return tiles; + m_Tiles[Count++] = tiles[i]; } } + + public void Add(ushort id, sbyte z) + { + if (Count + 1 > m_Tiles.Length) + { + var old = m_Tiles; + m_Tiles = new StaticTile[old.Length * 2]; + + for (var i = 0; i < old.Length; ++i) + { + m_Tiles[i] = old[i]; + } + } + + m_Tiles[Count].m_ID = id; + m_Tiles[Count].m_Z = z; + ++Count; + } + + public StaticTile[] ToArray() + { + if (Count == 0) + { + return m_EmptyTiles; + } + + var tiles = new StaticTile[Count]; + + for (var i = 0; i < Count; ++i) + { + tiles[i] = m_Tiles[i]; + } + + Count = 0; + + return tiles; + } } diff --git a/Projects/Server/TileMatrix/TileMatrixLoader.cs b/Projects/Server/TileMatrix/TileMatrixLoader.cs index 548024a88..8343c3182 100644 --- a/Projects/Server/TileMatrix/TileMatrixLoader.cs +++ b/Projects/Server/TileMatrix/TileMatrixLoader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TileMatrixLoader.cs * * * @@ -17,42 +17,41 @@ using System; using System.Diagnostics; using Server.Logging; -namespace Server +namespace Server; + +internal static class TileMatrixLoader { - internal static class TileMatrixLoader + private static readonly ILogger logger = LogFactory.GetLogger(typeof(TileMatrixLoader)); + + internal static void LoadTileMatrix() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(TileMatrixLoader)); + logger.Information("Loading maps"); - internal static void LoadTileMatrix() + var stopwatch = Stopwatch.StartNew(); + Exception exception = null; + + try { - logger.Information("Loading maps"); - - var stopwatch = Stopwatch.StartNew(); - Exception exception = null; - - try + foreach (var m in Map.AllMaps) { - foreach (var m in Map.AllMaps) - { - m.Tiles.Force(); // Forces the map file stream references to load - } - } - catch (Exception ex) - { - exception = ex; + m.Tiles.Force(); // Forces the map file stream references to load } + } + catch (Exception ex) + { + exception = ex; + } - stopwatch.Stop(); + stopwatch.Stop(); - if (exception == null) - { - logger.Information("Maps loaded ({Duration:F2} seconds)", stopwatch.Elapsed.TotalSeconds); - } - else - { - logger.Error(exception, "Loading maps failed ({Duration:F2} seconds)", stopwatch.Elapsed.TotalSeconds); - throw exception; - } + if (exception == null) + { + logger.Information("Maps loaded ({Duration:F2} seconds)", stopwatch.Elapsed.TotalSeconds); + } + else + { + logger.Error(exception, "Loading maps failed ({Duration:F2} seconds)", stopwatch.Elapsed.TotalSeconds); + throw exception; } } } diff --git a/Projects/Server/TimeZones/TimeZoneHandler.cs b/Projects/Server/TimeZones/TimeZoneHandler.cs index 5e9450f02..da3b2f984 100644 --- a/Projects/Server/TimeZones/TimeZoneHandler.cs +++ b/Projects/Server/TimeZones/TimeZoneHandler.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TimeZoneHandler.cs * * * @@ -19,9 +19,9 @@ using System.IO; using System.Runtime.CompilerServices; using Server.Json; -namespace Server -{ - /** +namespace Server; + +/** * TimeZoneHandler provides a cross platform compatible way to handle system timezones. * * By default the system timezone is used. @@ -37,44 +37,43 @@ namespace Server * Example: * "system.localTimeZone": "Europe/Lisbon" */ - public static class TimeZoneHandler +public static class TimeZoneHandler +{ + private static readonly Dictionary _timeZoneById = new(); + public static TimeZoneInfo SystemTimeZone { get; private set; } + + static TimeZoneHandler() { - private static readonly Dictionary _timeZoneById = new(); - public static TimeZoneInfo SystemTimeZone { get; private set; } - - static TimeZoneHandler() + var timezones = JsonConfig.Deserialize(Path.Combine(Core.BaseDirectory, "Data/timezones.json")); + foreach (var tz in timezones) { - var timezones = JsonConfig.Deserialize(Path.Combine(Core.BaseDirectory, "Data/timezones.json")); - foreach (var tz in timezones) + // Get the timezone if we are on Windows + var tzInfo = Core.IsWindows ? TimeZoneInfo.FindSystemTimeZoneById(tz.TimeZone) : null; + + foreach (var region in tz.Regions) { - // Get the timezone if we are on Windows - var tzInfo = Core.IsWindows ? TimeZoneInfo.FindSystemTimeZoneById(tz.TimeZone) : null; - - foreach (var region in tz.Regions) - { - // Get the timezone by region if we are on linux - tzInfo ??= TimeZoneInfo.FindSystemTimeZoneById(region); - _timeZoneById[region] = tzInfo; - } - - _timeZoneById[tz.TimeZone] = tzInfo; + // Get the timezone by region if we are on linux + tzInfo ??= TimeZoneInfo.FindSystemTimeZoneById(region); + _timeZoneById[region] = tzInfo; } - } - public static void Configure() - { - var tzId = ServerConfiguration.GetSetting("system.localTimeZone", TimeZoneInfo.Local.Id); - SystemTimeZone = FindTimeZoneById(tzId); + _timeZoneById[tz.TimeZone] = tzInfo; } + } - /** + public static void Configure() + { + var tzId = ServerConfiguration.GetSetting("system.localTimeZone", TimeZoneInfo.Local.Id); + SystemTimeZone = FindTimeZoneById(tzId); + } + + /** * Cross platform version of TimeZoneInfo.FindSystemTimeZoneById */ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TimeZoneInfo FindTimeZoneById(string id) => _timeZoneById[id]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TimeZoneInfo FindTimeZoneById(string id) => _timeZoneById[id]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static DateTime ToSystemLocalTime(this DateTime date) => - TimeZoneInfo.ConvertTimeFromUtc(date, SystemTimeZone); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DateTime ToSystemLocalTime(this DateTime date) => + TimeZoneInfo.ConvertTimeFromUtc(date, SystemTimeZone); } diff --git a/Projects/Server/TimeZones/TimeZoneWithRegions.cs b/Projects/Server/TimeZones/TimeZoneWithRegions.cs index 4c19a0086..7d1ab3a6d 100644 --- a/Projects/Server/TimeZones/TimeZoneWithRegions.cs +++ b/Projects/Server/TimeZones/TimeZoneWithRegions.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TimeZoneWithRegions.cs * * * @@ -15,14 +15,13 @@ using System.Text.Json.Serialization; -namespace Server -{ - public record TimeZoneWithRegions - { - [JsonPropertyName("timezone")] - public string TimeZone { get; init; } +namespace Server; - [JsonPropertyName("regions")] - public string[] Regions { get; init; } - } +public record TimeZoneWithRegions +{ + [JsonPropertyName("timezone")] + public string TimeZone { get; init; } + + [JsonPropertyName("regions")] + public string[] Regions { get; init; } } diff --git a/Projects/Server/Timer/Timer.DelayCall.cs b/Projects/Server/Timer/Timer.DelayCall.cs index 312801e64..8a5f93fe1 100644 --- a/Projects/Server/Timer/Timer.DelayCall.cs +++ b/Projects/Server/Timer/Timer.DelayCall.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Timer.DelayCall.cs * * * diff --git a/Projects/Server/Timer/Timer.DelayStateCall.cs b/Projects/Server/Timer/Timer.DelayStateCall.cs index 9ba6557d1..bb71097d6 100644 --- a/Projects/Server/Timer/Timer.DelayStateCall.cs +++ b/Projects/Server/Timer/Timer.DelayStateCall.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Timer.DelayStateCall.cs * * * diff --git a/Projects/Server/Timer/Timer.Pool.cs b/Projects/Server/Timer/Timer.Pool.cs index b48fbb131..96488ebbc 100644 --- a/Projects/Server/Timer/Timer.Pool.cs +++ b/Projects/Server/Timer/Timer.Pool.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Timer.Pool.cs * * * diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs index fcd0450e1..0462fc307 100644 --- a/Projects/Server/Timer/Timer.TimerWheel.cs +++ b/Projects/Server/Timer/Timer.TimerWheel.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Timer.TimerWheel.cs * * * diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs index 3743b7b3e..cd163aef0 100644 --- a/Projects/Server/Timer/Timer.cs +++ b/Projects/Server/Timer/Timer.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Timer.cs * * * diff --git a/Projects/Server/Timer/TimerExecutionToken.cs b/Projects/Server/Timer/TimerExecutionToken.cs index bcb54e005..05c8f7d65 100644 --- a/Projects/Server/Timer/TimerExecutionToken.cs +++ b/Projects/Server/Timer/TimerExecutionToken.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TimerExecutionToken.cs * * * diff --git a/Projects/Server/Utilities/ActivatorExtensions.cs b/Projects/Server/Utilities/ActivatorExtensions.cs index 287f7cdb5..eccf7389d 100644 --- a/Projects/Server/Utilities/ActivatorExtensions.cs +++ b/Projects/Server/Utilities/ActivatorExtensions.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ActivatorExtensions.cs * * * @@ -16,138 +16,137 @@ using System; using System.Reflection; -namespace Server.Utilities +namespace Server.Utilities; + +public static class ActivatorExtensions { - public static class ActivatorExtensions + public static ConstructorInfo GetConstructor( + this Type type, + Predicate predicate = null, + Type[] args = null + ) => type.GetConstructor(predicate, args, out _); + + public static ConstructorInfo GetConstructor( + this Type type, + Predicate predicate, + Type[] args, + out int paramCount + ) { - public static ConstructorInfo GetConstructor( - this Type type, - Predicate predicate = null, - Type[] args = null - ) => type.GetConstructor(predicate, args, out _); + args ??= Array.Empty(); + var ctors = type.GetConstructors(); - public static ConstructorInfo GetConstructor( - this Type type, - Predicate predicate, - Type[] args, - out int paramCount - ) + try { - args ??= Array.Empty(); - var ctors = type.GetConstructors(); - - try + for (int i = 0; i < ctors.Length; i++) { - for (int i = 0; i < ctors.Length; i++) + ConstructorInfo info = ctors[i]; + + if (predicate?.Invoke(info) == false) { - ConstructorInfo info = ctors[i]; + continue; + } - if (predicate?.Invoke(info) == false) + var parameters = info.GetParameters(); + paramCount = parameters.Length; + + if (args.Length > parameters.Length) + { + continue; + } + + bool validated = true; + + // Check that all args match params + for (var j = 0; j < parameters.Length; j++) + { + ParameterInfo param = parameters[j]; + + // All extra parameters must be optional + if (j >= args.Length) { - continue; - } - - var parameters = info.GetParameters(); - paramCount = parameters.Length; - - if (args.Length > parameters.Length) - { - continue; - } - - bool validated = true; - - // Check that all args match params - for (var j = 0; j < parameters.Length; j++) - { - ParameterInfo param = parameters[j]; - - // All extra parameters must be optional - if (j >= args.Length) - { - if (!param.IsOptional) - { - validated = false; - break; - } - - continue; - } - - var arg = args[j]; - if (arg == null && param.ParameterType.IsValueType) + if (!param.IsOptional) { validated = false; break; } - if (arg != null && !param.ParameterType.IsAssignableFrom(arg)) - { - validated = false; - break; - } + continue; } - if (validated) + var arg = args[j]; + if (arg == null && param.ParameterType.IsValueType) { - return info; + validated = false; + break; + } + + if (arg != null && !param.ParameterType.IsAssignableFrom(arg)) + { + validated = false; + break; } } - } - catch (Exception e) - { - Console.WriteLine(e); - } - paramCount = 0; - return null; - } - - public static T CreateInstance( - this Type type, - params object[] args - ) where T : class => type.CreateInstance(null, args); - - public static T CreateInstance( - this Type type, - Predicate predicate, - object[] args = null - ) where T : class - { - var argLength = args?.Length ?? 0; - - var types = argLength > 0 ? new Type[argLength] : Array.Empty(); - for (int i = 0; i < types.Length; i++) - { - types[i] = args![i]?.GetType(); - } - - var ctor = type.GetConstructor(predicate, types, out var paramCount); - if (ctor == null) - { - Console.WriteLine("There is no constructor for {0} that matches the given predicate.", type); - return default; - } - - object[] paramArgs; - if (paramCount == 0) - { - paramArgs = Array.Empty(); - } - else if (argLength == paramCount) - { - paramArgs = args; - } - else - { - paramArgs = new object[paramCount]; - for (int i = 0; i < paramCount; i++) + if (validated) { - paramArgs[i] = i < argLength ? args![i] : Type.Missing; + return info; } } - - return ctor.Invoke(paramArgs) as T; } + catch (Exception e) + { + Console.WriteLine(e); + } + + paramCount = 0; + return null; + } + + public static T CreateInstance( + this Type type, + params object[] args + ) where T : class => type.CreateInstance(null, args); + + public static T CreateInstance( + this Type type, + Predicate predicate, + object[] args = null + ) where T : class + { + var argLength = args?.Length ?? 0; + + var types = argLength > 0 ? new Type[argLength] : Array.Empty(); + for (int i = 0; i < types.Length; i++) + { + types[i] = args![i]?.GetType(); + } + + var ctor = type.GetConstructor(predicate, types, out var paramCount); + if (ctor == null) + { + Console.WriteLine("There is no constructor for {0} that matches the given predicate.", type); + return default; + } + + object[] paramArgs; + if (paramCount == 0) + { + paramArgs = Array.Empty(); + } + else if (argLength == paramCount) + { + paramArgs = args; + } + else + { + paramArgs = new object[paramCount]; + for (int i = 0; i < paramCount; i++) + { + paramArgs[i] = i < argLength ? args![i] : Type.Missing; + } + } + + return ctor.Invoke(paramArgs) as T; } } diff --git a/Projects/Server/Utilities/EntityActivatorExtensions.cs b/Projects/Server/Utilities/EntityActivatorExtensions.cs index 21afc6420..eea323b47 100644 --- a/Projects/Server/Utilities/EntityActivatorExtensions.cs +++ b/Projects/Server/Utilities/EntityActivatorExtensions.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EntityActivatorExtensions.cs * * * @@ -16,31 +16,30 @@ using System; using System.Reflection; -namespace Server.Utilities +namespace Server.Utilities; + +public static class EntityActivatorExtensions { - public static class EntityActivatorExtensions + public static T CreateEntityInstance( + this Type type, + params object[] args + ) where T : IEntity => type.CreateEntityInstance(null, args); + + public static T CreateEntityInstance( + this Type type, + Predicate predicate, + object[] args = null + ) where T : IEntity { - public static T CreateEntityInstance( - this Type type, - params object[] args - ) where T : IEntity => type.CreateEntityInstance(null, args); + var entity = type.CreateInstance(predicate, args); - public static T CreateEntityInstance( - this Type type, - Predicate predicate, - object[] args = null - ) where T : IEntity + if (entity is T t) { - var entity = type.CreateInstance(predicate, args); - - if (entity is T t) - { - return t; - } - - // Handles memory leaks by deleting the offending entity - entity?.Delete(); - return default; + return t; } + + // Handles memory leaks by deleting the offending entity + entity?.Delete(); + return default; } } diff --git a/Projects/Server/Utilities/PathUtility.cs b/Projects/Server/Utilities/PathUtility.cs index b85b7b07d..107e85e38 100644 --- a/Projects/Server/Utilities/PathUtility.cs +++ b/Projects/Server/Utilities/PathUtility.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PathUtility.cs * * * diff --git a/Projects/Server/VirtueInfo.cs b/Projects/Server/VirtueInfo.cs index 336ccd656..448451458 100644 --- a/Projects/Server/VirtueInfo.cs +++ b/Projects/Server/VirtueInfo.cs @@ -1,148 +1,147 @@ -namespace Server +namespace Server; + +[PropertyObject] +public class VirtueInfo { - [PropertyObject] - public class VirtueInfo + public VirtueInfo() { - public VirtueInfo() - { - } + } - public VirtueInfo(IGenericReader reader) - { - int version = reader.ReadByte(); + public VirtueInfo(IGenericReader reader) + { + int version = reader.ReadByte(); - switch (version) - { - case 1: // Changed the values throughout the virtue system - case 0: + switch (version) + { + case 1: // Changed the values throughout the virtue system + case 0: + { + int mask = reader.ReadByte(); + + if (mask != 0) { - int mask = reader.ReadByte(); + Values = new int[8]; - if (mask != 0) + for (var i = 0; i < 8; ++i) { - Values = new int[8]; - - for (var i = 0; i < 8; ++i) + if ((mask & (1 << i)) != 0) { - if ((mask & (1 << i)) != 0) - { - Values[i] = reader.ReadInt(); - } + Values[i] = reader.ReadInt(); } } - - break; } - } - if (version == 0) - { - Compassion *= 200; - Sacrifice *= 250; // Even though 40 (the max) only gives 10k, It's because it was formerly too easy - - // No direct conversion factor for Justice, this is just an approximation - Justice *= 500; - - // All the other virtues haven't been defined at 'version 0' point in time in the scripts. - } - } - - public int[] Values { get; private set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Humility - { - get => GetValue(0); - set => SetValue(0, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Sacrifice - { - get => GetValue(1); - set => SetValue(1, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Compassion - { - get => GetValue(2); - set => SetValue(2, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Spirituality - { - get => GetValue(3); - set => SetValue(3, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Valor - { - get => GetValue(4); - set => SetValue(4, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Honor - { - get => GetValue(5); - set => SetValue(5, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Justice - { - get => GetValue(6); - set => SetValue(6, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Honesty - { - get => GetValue(7); - set => SetValue(7, value); - } - - public int GetValue(int index) => Values?[index] ?? 0; - - public void SetValue(int index, int value) - { - Values ??= new int[8]; - Values[index] = value; - } - - public override string ToString() => "..."; - - public static void Serialize(IGenericWriter writer, VirtueInfo info) - { - writer.Write((byte)1); // version - - if (info.Values == null) - { - writer.Write((byte)0); - } - else - { - var mask = 0; - - for (var i = 0; i < 8; ++i) - { - if (info.Values[i] != 0) - { - mask |= 1 << i; - } + break; } + } - writer.Write((byte)mask); + if (version == 0) + { + Compassion *= 200; + Sacrifice *= 250; // Even though 40 (the max) only gives 10k, It's because it was formerly too easy - for (var i = 0; i < 8; ++i) + // No direct conversion factor for Justice, this is just an approximation + Justice *= 500; + + // All the other virtues haven't been defined at 'version 0' point in time in the scripts. + } + } + + public int[] Values { get; private set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Humility + { + get => GetValue(0); + set => SetValue(0, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Sacrifice + { + get => GetValue(1); + set => SetValue(1, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Compassion + { + get => GetValue(2); + set => SetValue(2, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Spirituality + { + get => GetValue(3); + set => SetValue(3, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Valor + { + get => GetValue(4); + set => SetValue(4, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Honor + { + get => GetValue(5); + set => SetValue(5, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Justice + { + get => GetValue(6); + set => SetValue(6, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Honesty + { + get => GetValue(7); + set => SetValue(7, value); + } + + public int GetValue(int index) => Values?[index] ?? 0; + + public void SetValue(int index, int value) + { + Values ??= new int[8]; + Values[index] = value; + } + + public override string ToString() => "..."; + + public static void Serialize(IGenericWriter writer, VirtueInfo info) + { + writer.Write((byte)1); // version + + if (info.Values == null) + { + writer.Write((byte)0); + } + else + { + var mask = 0; + + for (var i = 0; i < 8; ++i) + { + if (info.Values[i] != 0) { - if (info.Values[i] != 0) - { - writer.Write(info.Values[i]); - } + mask |= 1 << i; + } + } + + writer.Write((byte)mask); + + for (var i = 0; i < 8; ++i) + { + if (info.Values[i] != 0) + { + writer.Write(info.Values[i]); } } } diff --git a/Projects/Server/World/EntityPersistence.cs b/Projects/Server/World/EntityPersistence.cs index df0feffb7..8586ae7dd 100644 --- a/Projects/Server/World/EntityPersistence.cs +++ b/Projects/Server/World/EntityPersistence.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EntityPersistence.cs * * * @@ -21,314 +21,313 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; -namespace Server +namespace Server; + +public static class EntityPersistence { - public static class EntityPersistence + private const int _idxVersion = 1; + + public static void WriteEntities( + IIndexInfo indexInfo, + Dictionary entities, + List types, + string savePath, + out Dictionary counts + ) where T : class, ISerializable { - private const int _idxVersion = 1; + counts = new Dictionary(); - public static void WriteEntities( - IIndexInfo indexInfo, - Dictionary entities, - List types, - string savePath, - out Dictionary counts - ) where T : class, ISerializable + var typeName = indexInfo.TypeName; + + var path = Path.Combine(savePath, typeName); + + PathUtility.EnsureDirectory(path); + + string idxPath = Path.Combine(path, $"{typeName}.idx"); + string tdbPath = Path.Combine(path, $"{typeName}.tdb"); + string binPath = Path.Combine(path, $"{typeName}.bin"); + + using var idx = new BinaryFileWriter(idxPath, false); + using var tdb = new BinaryFileWriter(tdbPath, false); + using var bin = new BinaryFileWriter(binPath, true); + + idx.Write(1); // Version + idx.Write(entities.Count); + foreach (var e in entities.Values) { - counts = new Dictionary(); + long start = bin.Position; - var typeName = indexInfo.TypeName; + idx.Write(e.TypeRef); + idx.Write(e.Serial); + idx.Write(e.Created.Ticks); + idx.Write(e.LastSerialized.Ticks); + idx.Write(start); - var path = Path.Combine(savePath, typeName); + e.SerializeTo(bin); - PathUtility.EnsureDirectory(path); + idx.Write((int)(bin.Position - start)); - string idxPath = Path.Combine(path, $"{typeName}.idx"); - string tdbPath = Path.Combine(path, $"{typeName}.tdb"); - string binPath = Path.Combine(path, $"{typeName}.bin"); - - using var idx = new BinaryFileWriter(idxPath, false); - using var tdb = new BinaryFileWriter(tdbPath, false); - using var bin = new BinaryFileWriter(binPath, true); - - idx.Write(1); // Version - idx.Write(entities.Count); - foreach (var e in entities.Values) + var type = e.GetType().FullName; + if (type != null) { - long start = bin.Position; - - idx.Write(e.TypeRef); - idx.Write(e.Serial); - idx.Write(e.Created.Ticks); - idx.Write(e.LastSerialized.Ticks); - idx.Write(start); - - e.SerializeTo(bin); - - idx.Write((int)(bin.Position - start)); - - var type = e.GetType().FullName; - if (type != null) - { - counts[type] = (counts.TryGetValue(type, out var count) ? count : 0) + 1; - } - } - - tdb.Write(types.Count); - for (int i = 0; i < types.Count; ++i) - { - tdb.Write(types[i].FullName); + counts[type] = (counts.TryGetValue(type, out var count) ? count : 0) + 1; } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SaveEntities( - IEnumerable list, - Action serializer - ) where T : class, ISerializable => Parallel.ForEach(list, serializer); - - public static Dictionary LoadIndex( - string path, - IIndexInfo indexInfo, - out List> entities - ) where T : class, ISerializable + tdb.Write(types.Count); + for (int i = 0; i < types.Count; ++i) { - var map = new Dictionary(); - object[] ctorArgs = new object[1]; + tdb.Write(types[i].FullName); + } + } - var indexType = indexInfo.TypeName; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SaveEntities( + IEnumerable list, + Action serializer + ) where T : class, ISerializable => Parallel.ForEach(list, serializer); - string indexPath = Path.Combine(path, indexType, $"{indexType}.idx"); - string typesPath = Path.Combine(path, indexType, $"{indexType}.tdb"); + public static Dictionary LoadIndex( + string path, + IIndexInfo indexInfo, + out List> entities + ) where T : class, ISerializable + { + var map = new Dictionary(); + object[] ctorArgs = new object[1]; - entities = new List>(); + var indexType = indexInfo.TypeName; - if (!File.Exists(indexPath) || !File.Exists(typesPath)) - { - return map; - } + string indexPath = Path.Combine(path, indexType, $"{indexType}.idx"); + string typesPath = Path.Combine(path, indexType, $"{indexType}.tdb"); - using FileStream idx = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - BinaryReader idxReader = new BinaryReader(idx); - - using FileStream tdb = new FileStream(typesPath, FileMode.Open, FileAccess.Read, FileShare.Read); - BinaryReader tdbReader = new BinaryReader(tdb); - - List> types = ReadTypes(tdbReader); - - int count; - var version = idxReader.ReadInt32(); - - // Handle non-versioned (version 0). - if (version > _idxVersion || idx.Length - 4 - version * 20 == 0) - { - count = version; - version = 0; - } - else - { - count = idxReader.ReadInt32(); - } - - var now = DateTime.UtcNow; - - for (int i = 0; i < count; ++i) - { - var typeID = idxReader.ReadInt32(); - var serial = idxReader.ReadUInt32(); - var created = version == 0 ? now : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc); - var lastSerialized = version == 0 ? DateTime.MinValue : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc); - var pos = idxReader.ReadInt64(); - var length = idxReader.ReadInt32(); - - Tuple objs = types[typeID]; - - if (objs == null) - { - continue; - } - - ConstructorInfo ctor = objs.Item1; - I indexer = indexInfo.CreateIndex(serial); - - ctorArgs[0] = indexer; - - if (ctor.Invoke(ctorArgs) is T t) - { - t.Created = created; - t.LastSerialized = lastSerialized; - entities.Add(new EntityIndex(t, typeID, pos, length)); - map[indexer] = t; - } - } - - tdbReader.Close(); - idxReader.Close(); + entities = new List>(); + if (!File.Exists(indexPath) || !File.Exists(typesPath)) + { return map; } - public static void LoadData( - string path, - IIndexInfo indexInfo, - List> entities - ) where T : class, ISerializable + using FileStream idx = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read); + BinaryReader idxReader = new BinaryReader(idx); + + using FileStream tdb = new FileStream(typesPath, FileMode.Open, FileAccess.Read, FileShare.Read); + BinaryReader tdbReader = new BinaryReader(tdb); + + List> types = ReadTypes(tdbReader); + + int count; + var version = idxReader.ReadInt32(); + + // Handle non-versioned (version 0). + if (version > _idxVersion || idx.Length - 4 - version * 20 == 0) { - var indexType = indexInfo.TypeName; + count = version; + version = 0; + } + else + { + count = idxReader.ReadInt32(); + } - string dataPath = Path.Combine(path, indexType, $"{indexType}.bin"); + var now = DateTime.UtcNow; - if (!File.Exists(dataPath) || new FileInfo(dataPath).Length == 0) + for (int i = 0; i < count; ++i) + { + var typeID = idxReader.ReadInt32(); + var serial = idxReader.ReadUInt32(); + var created = version == 0 ? now : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc); + var lastSerialized = version == 0 ? DateTime.MinValue : new DateTime(idxReader.ReadInt64(), DateTimeKind.Utc); + var pos = idxReader.ReadInt64(); + var length = idxReader.ReadInt32(); + + Tuple objs = types[typeID]; + + if (objs == null) { - return; + continue; } - using var mmf = MemoryMappedFile.CreateFromFile(dataPath, FileMode.Open); - using var stream = mmf.CreateViewStream(); - BufferReader br = null; + ConstructorInfo ctor = objs.Item1; + I indexer = indexInfo.CreateIndex(serial); - var deleteAllFailures = false; + ctorArgs[0] = indexer; - foreach (var entry in entities) + if (ctor.Invoke(ctorArgs) is T t) { - T t = entry.Entity; - - var position = entry.Position; - stream.Seek(position, SeekOrigin.Begin); - - // Skip this entry - if (t == null) - { - continue; - } - - if (entry.Length == 0) - { - t.Delete(); - continue; - } - - var buffer = GC.AllocateUninitializedArray(entry.Length); - if (br == null) - { - br = new BufferReader(buffer, t.LastSerialized); - } - else - { - br.Reset(buffer, out _); - } - - stream.Read(buffer.AsSpan()); - string error; - - try - { - t.Deserialize(br); - - error = br.Position != entry.Length - ? $"Serialized object was {entry.Length} bytes, but {br.Position} bytes deserialized" - : null; - } - catch (Exception e) - { - error = e.ToString(); - } - - if (error == null) - { - t.InitializeSaveBuffer(buffer); - } - else - { - Console.WriteLine($"***** Bad deserialize of {t.GetType()} *****"); - Console.WriteLine(error); - - ConsoleKey pressedKey; - - if (!deleteAllFailures) - { - Console.WriteLine("Delete the object and continue? (y/n/a)"); - pressedKey = Console.ReadKey(true).Key; - - if (pressedKey == ConsoleKey.A) - { - deleteAllFailures = true; - } - else if (pressedKey != ConsoleKey.Y) - { - throw new Exception("Deserialization failed."); - } - } - - t.Delete(); - } + t.Created = created; + t.LastSerialized = lastSerialized; + entities.Add(new EntitySpan(t, typeID, pos, length)); + map[indexer] = t; } } - private static List> ReadTypes(BinaryReader tdbReader) + tdbReader.Close(); + idxReader.Close(); + + return map; + } + + public static void LoadData( + string path, + IIndexInfo indexInfo, + List> entities + ) where T : class, ISerializable + { + var indexType = indexInfo.TypeName; + + string dataPath = Path.Combine(path, indexType, $"{indexType}.bin"); + + if (!File.Exists(dataPath) || new FileInfo(dataPath).Length == 0) { - var constructorTypes = new[] { typeof(I) }; - - var count = tdbReader.ReadInt32(); - - var types = new List>(count); - - for (var i = 0; i < count; ++i) - { - var typeName = tdbReader.ReadString(); - - var t = AssemblyHandler.FindTypeByFullName(typeName, false); - - if (t?.IsAbstract != false) - { - Console.WriteLine("failed"); - - var issue = t?.IsAbstract == true ? "marked abstract" : "not found"; - - Console.WriteLine($"Error: Type '{typeName}' was {issue}. Delete all of those types? (y/n)"); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - types.Add(null); - Console.WriteLine("Loading..."); - continue; - } - - Console.WriteLine("Types will not be deleted. An exception will be thrown."); - - throw new Exception($"Bad type '{typeName}'"); - } - - var ctor = t.GetConstructor(constructorTypes); - - if (ctor != null) - { - types.Add(new Tuple(ctor, typeName)); - } - else - { - throw new Exception($"Type '{t}' does not have a serialization constructor"); - } - } - - return types; + return; } - private static void SerializeTo(this ISerializable entity, IGenericWriter writer) - { - var saveBuffer = entity.SaveBuffer; + using var mmf = MemoryMappedFile.CreateFromFile(dataPath, FileMode.Open); + using var stream = mmf.CreateViewStream(); + BufferReader br = null; - // If nothing was serialized we expect the object to be deleted on deserialization - if (saveBuffer.Position == 0) + var deleteAllFailures = false; + + foreach (var entry in entities) + { + T t = entry.Entity; + + var position = entry.Position; + stream.Seek(position, SeekOrigin.Begin); + + // Skip this entry + if (t == null) { - return; + continue; } - // Resize to the exact size - saveBuffer.Resize((int)saveBuffer.Position); + if (entry.Length == 0) + { + t.Delete(); + continue; + } - // Write that amount - writer.Write(saveBuffer.Buffer); + var buffer = GC.AllocateUninitializedArray(entry.Length); + if (br == null) + { + br = new BufferReader(buffer, t.LastSerialized); + } + else + { + br.Reset(buffer, out _); + } + + stream.Read(buffer.AsSpan()); + string error; + + try + { + t.Deserialize(br); + + error = br.Position != entry.Length + ? $"Serialized object was {entry.Length} bytes, but {br.Position} bytes deserialized" + : null; + } + catch (Exception e) + { + error = e.ToString(); + } + + if (error == null) + { + t.InitializeSaveBuffer(buffer); + } + else + { + Console.WriteLine($"***** Bad deserialize of {t.GetType()} *****"); + Console.WriteLine(error); + + ConsoleKey pressedKey; + + if (!deleteAllFailures) + { + Console.WriteLine("Delete the object and continue? (y/n/a)"); + pressedKey = Console.ReadKey(true).Key; + + if (pressedKey == ConsoleKey.A) + { + deleteAllFailures = true; + } + else if (pressedKey != ConsoleKey.Y) + { + throw new Exception("Deserialization failed."); + } + } + + t.Delete(); + } } } + + private static List> ReadTypes(BinaryReader tdbReader) + { + var constructorTypes = new[] { typeof(I) }; + + var count = tdbReader.ReadInt32(); + + var types = new List>(count); + + for (var i = 0; i < count; ++i) + { + var typeName = tdbReader.ReadString(); + + var t = AssemblyHandler.FindTypeByFullName(typeName, false); + + if (t?.IsAbstract != false) + { + Console.WriteLine("failed"); + + var issue = t?.IsAbstract == true ? "marked abstract" : "not found"; + + Console.WriteLine($"Error: Type '{typeName}' was {issue}. Delete all of those types? (y/n)"); + + if (Console.ReadKey(true).Key == ConsoleKey.Y) + { + types.Add(null); + Console.WriteLine("Loading..."); + continue; + } + + Console.WriteLine("Types will not be deleted. An exception will be thrown."); + + throw new Exception($"Bad type '{typeName}'"); + } + + var ctor = t.GetConstructor(constructorTypes); + + if (ctor != null) + { + types.Add(new Tuple(ctor, typeName)); + } + else + { + throw new Exception($"Type '{t}' does not have a serialization constructor"); + } + } + + return types; + } + + private static void SerializeTo(this ISerializable entity, IGenericWriter writer) + { + var saveBuffer = entity.SaveBuffer; + + // If nothing was serialized we expect the object to be deleted on deserialization + if (saveBuffer.Position == 0) + { + return; + } + + // Resize to the exact size + saveBuffer.Resize((int)saveBuffer.Position); + + // Write that amount + writer.Write(saveBuffer.Buffer); + } } diff --git a/Projects/Server/World/EntityIndex.cs b/Projects/Server/World/EntitySpan.cs similarity index 62% rename from Projects/Server/World/EntityIndex.cs rename to Projects/Server/World/EntitySpan.cs index ff46fa7e5..16f8670df 100644 --- a/Projects/Server/World/EntityIndex.cs +++ b/Projects/Server/World/EntitySpan.cs @@ -1,8 +1,8 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: EntityIndex.cs * + * File: EntitySpan.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 * @@ -13,24 +13,23 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server +namespace Server; + +public struct EntitySpan where T : ISerializable { - public readonly struct EntityIndex where T : ISerializable + public T Entity { get; } + + public int TypeID { get; } + + public long Position { get; } + + public int Length { get; } + + public EntitySpan(T entity, int typeID, long position, int length) { - public T Entity { get; } - - public int TypeID { get; } - - public long Position { get; } - - public int Length { get; } - - public EntityIndex(T entity, int typeID, long position, int length) - { - Entity = entity; - TypeID = typeID; - Position = position; - Length = length; - } + Entity = entity; + TypeID = typeID; + Position = position; + Length = length; } } diff --git a/Projects/Server/World/EntityTypeIndex.cs b/Projects/Server/World/EntityTypeIndex.cs index 219ac4340..0f3fcf866 100644 --- a/Projects/Server/World/EntityTypeIndex.cs +++ b/Projects/Server/World/EntityTypeIndex.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EntityTypeIndex.cs * * * @@ -13,14 +13,13 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server +namespace Server; + +public readonly struct EntityTypeIndex : IIndexInfo { - public readonly struct EntityTypeIndex : IIndexInfo - { - public string TypeName { get; } + public string TypeName { get; } - public EntityTypeIndex(string typeName) => TypeName = typeName; + public EntityTypeIndex(string typeName) => TypeName = typeName; - public Serial CreateIndex(uint num) => (Serial)num; - } + public Serial CreateIndex(uint num) => (Serial)num; } diff --git a/Projects/Server/World/IIndexInfo.cs b/Projects/Server/World/IIndexInfo.cs index 5538eaf85..bb0fb575c 100644 --- a/Projects/Server/World/IIndexInfo.cs +++ b/Projects/Server/World/IIndexInfo.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EntityTypeIndex.cs * * * @@ -13,12 +13,11 @@ * along with this program. If not, see . * *************************************************************************/ -namespace Server -{ - public interface IIndexInfo - { - I CreateIndex(uint num); +namespace Server; - string TypeName { get; } - } +public interface IIndexInfo +{ + I CreateIndex(uint num); + + string TypeName { get; } } diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs index 84d6cd2af..818a67883 100644 --- a/Projects/Server/World/World.cs +++ b/Projects/Server/World/World.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: World.cs * * * @@ -25,652 +25,651 @@ using Server.Guilds; using Server.Logging; using Server.Network; -namespace Server +namespace Server; + +public enum WorldState { - public enum WorldState + Initial, + Loading, + Running, + Saving, + WritingSave +} + +public static class World +{ + private static readonly ILogger logger = LogFactory.GetLogger(typeof(World)); + + private static readonly ManualResetEvent m_DiskWriteHandle = new(true); + private static readonly Dictionary _pendingAdd = new(); + private static readonly Dictionary _pendingDelete = new(); + private static readonly ConcurrentQueue _decayQueue = new(); + + private static string _tempSavePath; // Path to the temporary folder for the save + private static bool _enableSaveStats; + + public const bool DirtyTrackingEnabled = false; + public const uint ItemOffset = 0x40000000; + public const uint MaxItemSerial = 0x7FFFFFFF; + public const uint MaxMobileSerial = ItemOffset - 1; + private const uint _maxItems = MaxItemSerial - ItemOffset + 1; + + private static Serial _lastMobile = Serial.Zero; + private static Serial _lastItem = (Serial)ItemOffset; + private static Serial _lastGuild = Serial.Zero; + + public static Serial NewMobile { - Initial, - Loading, - Running, - Saving, - WritingSave + get + { + var last = _lastMobile; + var maxMobile = (Serial)MaxMobileSerial; + + for (int i = 0; i < MaxMobileSerial; i++) + { + last++; + + if (last > maxMobile) + { + last = (Serial)1; + } + + if (FindMobile(last) == null) + { + return _lastMobile = last; + } + } + + OutOfMemory("No serials left to allocate for mobiles"); + return Serial.MinusOne; + } } - public static class World + public static Serial NewItem { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(World)); - - private static readonly ManualResetEvent m_DiskWriteHandle = new(true); - private static readonly Dictionary _pendingAdd = new(); - private static readonly Dictionary _pendingDelete = new(); - private static readonly ConcurrentQueue _decayQueue = new(); - - private static string _tempSavePath; // Path to the temporary folder for the save - private static bool _enableSaveStats; - - public const bool DirtyTrackingEnabled = false; - public const uint ItemOffset = 0x40000000; - public const uint MaxItemSerial = 0x7FFFFFFF; - public const uint MaxMobileSerial = ItemOffset - 1; - private const uint _maxItems = MaxItemSerial - ItemOffset + 1; - - private static Serial _lastMobile = Serial.Zero; - private static Serial _lastItem = (Serial)ItemOffset; - private static Serial _lastGuild = Serial.Zero; - - public static Serial NewMobile + get { - get + var last = _lastItem; + + for (int i = 0; i < _maxItems; i++) { - var last = _lastMobile; - var maxMobile = (Serial)MaxMobileSerial; + last++; - for (int i = 0; i < MaxMobileSerial; i++) + if (last > MaxItemSerial) { - last++; - - if (last > maxMobile) - { - last = (Serial)1; - } - - if (FindMobile(last) == null) - { - return _lastMobile = last; - } + last = (Serial)ItemOffset; } - OutOfMemory("No serials left to allocate for mobiles"); - return Serial.MinusOne; - } - } - - public static Serial NewItem - { - get - { - var last = _lastItem; - - for (int i = 0; i < _maxItems; i++) + if (FindItem(last) == null) { - last++; - - if (last > MaxItemSerial) - { - last = (Serial)ItemOffset; - } - - if (FindItem(last) == null) - { - return _lastItem = last; - } + return _lastItem = last; } - - OutOfMemory("No serials left to allocate for items"); - return Serial.MinusOne; } + + OutOfMemory("No serials left to allocate for items"); + return Serial.MinusOne; + } + } + + public static Serial NewGuild + { + get + { + while (FindGuild(_lastGuild += 1) != null) + { + } + + return _lastGuild; + } + } + + private static void OutOfMemory(string message) => throw new OutOfMemoryException(message); + + internal static List ItemTypes { get; } = new(); + internal static List MobileTypes { get; } = new(); + internal static List GuildTypes { get; } = new(); + + public static string SavePath { get; private set; } + + public static WorldState WorldState { get; private set; } + public static bool Saving => WorldState == WorldState.Saving; + public static bool Running => WorldState is not WorldState.Loading and not WorldState.Initial; + public static bool Loading => WorldState == WorldState.Loading; + + public static Dictionary Mobiles { get; private set; } + public static Dictionary Items { get; private set; } + public static Dictionary Guilds { get; private set; } + + public static void Configure() + { + var tempSavePath = ServerConfiguration.GetSetting("world.tempSavePath", "temp"); + _tempSavePath = PathUtility.GetFullPath(tempSavePath); + + var savePath = ServerConfiguration.GetOrUpdateSetting("world.savePath", "Saves"); + SavePath = PathUtility.GetFullPath(savePath); + + _enableSaveStats = ServerConfiguration.GetOrUpdateSetting("world.enableSaveStats", false); + + // Mobiles & Items + Persistence.Register("Mobiles & Items", SaveEntities, WriteEntities, LoadEntities, 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WaitForWriteCompletion() + { + m_DiskWriteHandle.WaitOne(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnqueueForDecay(Item item) + { + if (WorldState != WorldState.Saving) + { + logger.Warning($"Attempting to queue {item} for decay but the world is not saving"); + return; } - public static Serial NewGuild + _decayQueue.Enqueue(item); + } + + public static void Broadcast(int hue, bool ascii, string text) + { + var length = OutgoingMessagePackets.GetMaxMessageLength(text); + + Span buffer = stackalloc byte[length].InitializePacket(); + + foreach (var ns in TcpServer.Instances) { - get + if (ns.Mobile == null) { - while (FindGuild(_lastGuild += 1) != null) - { - } - - return _lastGuild; - } - } - - private static void OutOfMemory(string message) => throw new OutOfMemoryException(message); - - internal static List ItemTypes { get; } = new(); - internal static List MobileTypes { get; } = new(); - internal static List GuildTypes { get; } = new(); - - public static string SavePath { get; private set; } - - public static WorldState WorldState { get; private set; } - public static bool Saving => WorldState == WorldState.Saving; - public static bool Running => WorldState is not WorldState.Loading and not WorldState.Initial; - public static bool Loading => WorldState == WorldState.Loading; - - public static Dictionary Mobiles { get; private set; } - public static Dictionary Items { get; private set; } - public static Dictionary Guilds { get; private set; } - - public static void Configure() - { - var tempSavePath = ServerConfiguration.GetSetting("world.tempSavePath", "temp"); - _tempSavePath = PathUtility.GetFullPath(tempSavePath); - - var savePath = ServerConfiguration.GetOrUpdateSetting("world.savePath", "Saves"); - SavePath = PathUtility.GetFullPath(savePath); - - _enableSaveStats = ServerConfiguration.GetOrUpdateSetting("world.enableSaveStats", false); - - // Mobiles & Items - Persistence.Register("Mobiles & Items", SaveEntities, WriteEntities, LoadEntities, 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WaitForWriteCompletion() - { - m_DiskWriteHandle.WaitOne(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EnqueueForDecay(Item item) - { - if (WorldState != WorldState.Saving) - { - logger.Warning($"Attempting to queue {item} for decay but the world is not saving"); - return; + continue; } - _decayQueue.Enqueue(item); - } - - public static void Broadcast(int hue, bool ascii, string text) - { - var length = OutgoingMessagePackets.GetMaxMessageLength(text); - - Span buffer = stackalloc byte[length].InitializePacket(); - - foreach (var ns in TcpServer.Instances) - { - if (ns.Mobile == null) - { - continue; - } - - length = OutgoingMessagePackets.CreateMessage( - buffer, Serial.MinusOne, -1, MessageType.Regular, hue, 3, ascii, "ENU", "System", text - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - ns.Send(buffer); - } - - NetState.FlushAll(); - } - - public static void BroadcastStaff(int hue, bool ascii, string text) - { - var length = OutgoingMessagePackets.GetMaxMessageLength(text); - - Span buffer = stackalloc byte[length].InitializePacket(); - - foreach (var ns in TcpServer.Instances) - { - if (ns.Mobile == null || ns.Mobile.AccessLevel < AccessLevel.GameMaster) - { - continue; - } - - length = OutgoingMessagePackets.CreateMessage( - buffer, Serial.MinusOne, -1, MessageType.Regular, hue, 3, ascii, "ENU", "System", text - ); - - if (length != buffer.Length) - { - buffer = buffer[..length]; // Adjust to the actual size - } - - ns.Send(buffer); - } - - NetState.FlushAll(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Broadcast(int hue, bool ascii, string format, params object[] args) => - Broadcast(hue, ascii, string.Format(format, args)); - - internal static void LoadEntities(string basePath) - { - IIndexInfo itemIndexInfo = new EntityTypeIndex("Items"); - IIndexInfo mobileIndexInfo = new EntityTypeIndex("Mobiles"); - IIndexInfo guildIndexInfo = new EntityTypeIndex("Guilds"); - - Mobiles = EntityPersistence.LoadIndex(basePath, mobileIndexInfo, out List> mobiles); - Items = EntityPersistence.LoadIndex(basePath, itemIndexInfo, out List> items); - Guilds = EntityPersistence.LoadIndex(basePath, guildIndexInfo, out List> guilds); - - if (Mobiles.Count > 0) - { - _lastMobile = Mobiles.Keys.Max(); - } - - if (Items.Count > 0) - { - _lastItem = Items.Keys.Max(); - } - - if (Guilds.Count > 0) - { - _lastGuild = Guilds.Keys.Max(); - } - - EntityPersistence.LoadData(basePath, mobileIndexInfo, mobiles); - EntityPersistence.LoadData(basePath, itemIndexInfo, items); - EntityPersistence.LoadData(basePath, guildIndexInfo, guilds); - } - - public static void Load() - { - if (WorldState != WorldState.Initial) - { - return; - } - - WorldState = WorldState.Loading; - - logger.Information("Loading world"); - var watch = Stopwatch.StartNew(); - - Persistence.Load(SavePath); - EventSink.InvokeWorldLoad(); - - ProcessSafetyQueues(); - - foreach (var item in Items.Values) - { - if (item.Parent == null) - { - item.UpdateTotals(); - } - - item.ClearProperties(); - } - - foreach (var m in Mobiles.Values) - { - m.UpdateRegion(); // Is this really needed? - m.UpdateTotals(); - - m.ClearProperties(); - } - - watch.Stop(); - - logger.Information("World loaded ({ItemCount} items, {MobileCount} mobiles) ({Duration:F2} seconds)", - Items.Count, - Mobiles.Count, - watch.Elapsed.TotalSeconds + length = OutgoingMessagePackets.CreateMessage( + buffer, Serial.MinusOne, -1, MessageType.Regular, hue, 3, ascii, "ENU", "System", text ); - WorldState = WorldState.Running; - } - - private static void ProcessSafetyQueues() - { - foreach (var entity in _pendingAdd.Values) + if (length != buffer.Length) { - AddEntity(entity); + buffer = buffer[..length]; // Adjust to the actual size } - foreach (var entity in _pendingDelete.Values) + ns.Send(buffer); + } + + NetState.FlushAll(); + } + + public static void BroadcastStaff(int hue, bool ascii, string text) + { + var length = OutgoingMessagePackets.GetMaxMessageLength(text); + + Span buffer = stackalloc byte[length].InitializePacket(); + + foreach (var ns in TcpServer.Instances) + { + if (ns.Mobile == null || ns.Mobile.AccessLevel < AccessLevel.GameMaster) { - if (_pendingAdd.ContainsKey(entity.Serial)) + continue; + } + + length = OutgoingMessagePackets.CreateMessage( + buffer, Serial.MinusOne, -1, MessageType.Regular, hue, 3, ascii, "ENU", "System", text + ); + + if (length != buffer.Length) + { + buffer = buffer[..length]; // Adjust to the actual size + } + + ns.Send(buffer); + } + + NetState.FlushAll(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Broadcast(int hue, bool ascii, string format, params object[] args) => + Broadcast(hue, ascii, string.Format(format, args)); + + internal static void LoadEntities(string basePath) + { + IIndexInfo itemIndexInfo = new EntityTypeIndex("Items"); + IIndexInfo mobileIndexInfo = new EntityTypeIndex("Mobiles"); + IIndexInfo guildIndexInfo = new EntityTypeIndex("Guilds"); + + Mobiles = EntityPersistence.LoadIndex(basePath, mobileIndexInfo, out List> mobiles); + Items = EntityPersistence.LoadIndex(basePath, itemIndexInfo, out List> items); + Guilds = EntityPersistence.LoadIndex(basePath, guildIndexInfo, out List> guilds); + + if (Mobiles.Count > 0) + { + _lastMobile = Mobiles.Keys.Max(); + } + + if (Items.Count > 0) + { + _lastItem = Items.Keys.Max(); + } + + if (Guilds.Count > 0) + { + _lastGuild = Guilds.Keys.Max(); + } + + EntityPersistence.LoadData(basePath, mobileIndexInfo, mobiles); + EntityPersistence.LoadData(basePath, itemIndexInfo, items); + EntityPersistence.LoadData(basePath, guildIndexInfo, guilds); + } + + public static void Load() + { + if (WorldState != WorldState.Initial) + { + return; + } + + WorldState = WorldState.Loading; + + logger.Information("Loading world"); + var watch = Stopwatch.StartNew(); + + Persistence.Load(SavePath); + EventSink.InvokeWorldLoad(); + + ProcessSafetyQueues(); + + foreach (var item in Items.Values) + { + if (item.Parent == null) + { + item.UpdateTotals(); + } + + item.ClearProperties(); + } + + foreach (var m in Mobiles.Values) + { + m.UpdateRegion(); // Is this really needed? + m.UpdateTotals(); + + m.ClearProperties(); + } + + watch.Stop(); + + logger.Information("World loaded ({ItemCount} items, {MobileCount} mobiles) ({Duration:F2} seconds)", + Items.Count, + Mobiles.Count, + watch.Elapsed.TotalSeconds + ); + + WorldState = WorldState.Running; + } + + private static void ProcessSafetyQueues() + { + foreach (var entity in _pendingAdd.Values) + { + AddEntity(entity); + } + + foreach (var entity in _pendingDelete.Values) + { + if (_pendingAdd.ContainsKey(entity.Serial)) + { + logger.Warning("Entity {Entity} was both pending both deletion and addition after save", entity); + } + + RemoveEntity(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."; + + logger.Information(message); + + try + { + using var op = new StreamWriter("world-save-errors.log", true); + op.WriteLine("{0}\t{1}", DateTime.UtcNow, message); + op.WriteLine(new StackTrace(2).ToString()); + op.WriteLine(); + } + catch + { + // ignored + } + } + + private static void FinishWorldSave() + { + WorldState = WorldState.Running; + + ProcessDecay(); + ProcessSafetyQueues(); + } + + private static void TraceSave(params IEnumerable>[] entityTypes) + { + try + { + int count = 0; + + var timestamp = Utility.GetTimeStamp(); + var saveStatsPath = Path.Combine(Core.BaseDirectory, $"Logs/Saves/Save-Stats-{timestamp}.log"); + PathUtility.EnsureDirectory(saveStatsPath); + + using var op = new StreamWriter(saveStatsPath, true); + + for (var i = 0; i < entityTypes.Length; i++) + { + foreach (var (t, c) in entityTypes[i]) { - logger.Warning("Entity {Entity} was both pending both deletion and addition after save", entity); - } - - RemoveEntity(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."; - - logger.Information(message); - - try - { - using var op = new StreamWriter("world-save-errors.log", true); - op.WriteLine("{0}\t{1}", DateTime.UtcNow, message); - op.WriteLine(new StackTrace(2).ToString()); - op.WriteLine(); - } - catch - { - // ignored - } - } - - private static void FinishWorldSave() - { - WorldState = WorldState.Running; - - ProcessDecay(); - ProcessSafetyQueues(); - } - - private static void TraceSave(params IEnumerable>[] entityTypes) - { - try - { - int count = 0; - - var timestamp = Utility.GetTimeStamp(); - var saveStatsPath = Path.Combine(Core.BaseDirectory, $"Logs/Saves/Save-Stats-{timestamp}.log"); - PathUtility.EnsureDirectory(saveStatsPath); - - using var op = new StreamWriter(saveStatsPath, true); - - for (var i = 0; i < entityTypes.Length; i++) - { - foreach (var (t, c) in entityTypes[i]) - { - op.WriteLine("{0}: {1}", t, c); - count++; - } - } - - op.WriteLine("- Total: {0}", count); - - op.WriteLine(); - op.WriteLine(); - } - catch - { - // ignored - } - } - - internal static void WriteEntities(string basePath) - { - IIndexInfo itemIndexInfo = new EntityTypeIndex("Items"); - IIndexInfo mobileIndexInfo = new EntityTypeIndex("Mobiles"); - IIndexInfo guildIndexInfo = new EntityTypeIndex("Guilds"); - - EntityPersistence.WriteEntities(mobileIndexInfo, Mobiles, MobileTypes, basePath, out var mobileCounts); - EntityPersistence.WriteEntities(itemIndexInfo, Items, ItemTypes, basePath, out var itemCounts); - EntityPersistence.WriteEntities(guildIndexInfo, Guilds, GuildTypes, basePath, out var guildCounts); - - if (_enableSaveStats) - { - TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList()); - } - } - - public static void WriteFiles(object state) - { - Exception exception = null; - - var tempPath = PathUtility.EnsureRandomPath(_tempSavePath); - - try - { - var watch = Stopwatch.StartNew(); - logger.Information("Writing world save snapshot"); - - Persistence.WriteSnapshot(tempPath); - - watch.Stop(); - - logger.Information("Writing world save snapshot done ({Duration:F2} seconds)", watch.Elapsed.TotalSeconds); - } - catch (Exception ex) - { - exception = ex; - } - - if (exception != null) - { - logger.Error(exception, "Writing world save snapshot failed."); - Persistence.TraceException(exception); - - BroadcastStaff(0x35, true, "Writing world save snapshot failed."); - } - else - { - try - { - EventSink.InvokeWorldSavePostSnapshot(SavePath, tempPath); - PathUtility.MoveDirectory(tempPath, SavePath); - } - catch (Exception ex) - { - Persistence.TraceException(ex); + op.WriteLine("{0}: {1}", t, c); + count++; } } - m_DiskWriteHandle.Set(); + op.WriteLine("- Total: {0}", count); - Core.LoopContext.Post(FinishWorldSave); + op.WriteLine(); + op.WriteLine(); } - - private static void ProcessDecay() + catch { - while (_decayQueue.TryDequeue(out var item)) - { - if (item.OnDecay()) - { - // TODO: Add Logging - item.Delete(); - } - } + // ignored } + } - private static DateTime _serializationStart; + internal static void WriteEntities(string basePath) + { + IIndexInfo itemIndexInfo = new EntityTypeIndex("Items"); + IIndexInfo mobileIndexInfo = new EntityTypeIndex("Mobiles"); + IIndexInfo guildIndexInfo = new EntityTypeIndex("Guilds"); - internal static void SaveEntities() + EntityPersistence.WriteEntities(mobileIndexInfo, Mobiles, MobileTypes, basePath, out var mobileCounts); + EntityPersistence.WriteEntities(itemIndexInfo, Items, ItemTypes, basePath, out var itemCounts); + EntityPersistence.WriteEntities(guildIndexInfo, Guilds, GuildTypes, basePath, out var guildCounts); + + if (_enableSaveStats) { - _serializationStart = DateTime.UtcNow; - EntityPersistence.SaveEntities(Items.Values, SaveEntity); - EntityPersistence.SaveEntities(Mobiles.Values, SaveEntity); - EntityPersistence.SaveEntities(Guilds.Values, SaveEntity); + TraceSave(mobileCounts?.ToList(), itemCounts?.ToList(), guildCounts?.ToList()); } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void SaveEntity(T entity) where T : class, ISerializable + public static void WriteFiles(object state) + { + Exception exception = null; + + var tempPath = PathUtility.EnsureRandomPath(_tempSavePath); + + try { - if (entity is Item item && item.CanDecay() && item.LastMoved + item.DecayTime <= _serializationStart) - { - EnqueueForDecay(item); - } - - entity.Serialize(); - } - - public static void Save() - { - if (WorldState != WorldState.Running) - { - return; - } - - WaitForWriteCompletion(); // Blocks Save until current disk flush is done. - - WorldState = WorldState.Saving; - - m_DiskWriteHandle.Reset(); - - Broadcast(0x35, true, "The world is saving, please wait."); - - logger.Information("Saving world"); - var watch = Stopwatch.StartNew(); + logger.Information("Writing world save snapshot"); - Exception exception = null; - - try - { - Persistence.Serialize(); - EventSink.InvokeWorldSave(); - } - catch (Exception ex) - { - exception = ex; - } - - WorldState = WorldState.WritingSave; + Persistence.WriteSnapshot(tempPath); watch.Stop(); - if (exception == null) - { - var duration = watch.Elapsed.TotalSeconds; - logger.Information("World save completed ({Duration:F2} seconds)", duration); - - // Only broadcast if it took at least 150ms - if (duration >= 0.15) - { - Broadcast(0x35, true, $"World Save completed in {duration:F2} seconds."); - } - } - else - { - logger.Error(exception, "World save failed"); - Persistence.TraceException(exception); - - BroadcastStaff(0x35, true, "World save failed."); - } - - ThreadPool.QueueUserWorkItem(WriteFiles); + logger.Information("Writing world save snapshot done ({Duration:F2} seconds)", watch.Elapsed.TotalSeconds); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEntity FindEntity(Serial serial, bool returnDeleted = false) => FindEntity(serial, returnDeleted); - - public static T FindEntity(Serial serial, bool returnDeleted = false) where T : class, IEntity + catch (Exception ex) { - switch (WorldState) - { - default: return default; - case WorldState.Loading: - case WorldState.Saving: - case WorldState.WritingSave: - { - if (_pendingDelete.TryGetValue(serial, out var entity)) - { - return !returnDeleted ? null : entity as T; - } - - if (_pendingAdd.TryGetValue(serial, out entity)) - { - return entity as T; - } - - goto case WorldState.Running; - } - case WorldState.Running: - { - if (serial.IsItem) - { - Items.TryGetValue(serial, out var item); - return item as T; - } - - if (serial.IsMobile) - { - Mobiles.TryGetValue(serial, out var mob); - return mob as T; - } - - return default; - } - } + exception = ex; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Item FindItem(Serial serial, bool returnDeleted = false) => FindEntity(serial, returnDeleted); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Mobile FindMobile(Serial serial, bool returnDeleted = false) => - FindEntity(serial, returnDeleted); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static BaseGuild FindGuild(Serial serial) => Guilds.TryGetValue(serial, out var guild) ? guild : null; - - public static void AddEntity(T entity) where T : class, IEntity + if (exception != null) { - switch (WorldState) - { - default: // Not Running - { - throw new Exception($"Added {entity.GetType().Name} before world load.\n"); - } - case WorldState.Saving: - { - AppendSafetyLog("add", entity); - goto case WorldState.WritingSave; - } - case WorldState.Loading: - case WorldState.WritingSave: - { - if (_pendingDelete.Remove(entity.Serial)) - { - logger.Warning($"Deleted then added {entity.GetType().Name} during {WorldState.ToString()} state."); - } - _pendingAdd[entity.Serial] = entity; - break; - } - case WorldState.Running: - { - if (entity.Serial.IsItem) - { - Items[entity.Serial] = entity as Item; - } + logger.Error(exception, "Writing world save snapshot failed."); + Persistence.TraceException(exception); - if (entity.Serial.IsMobile) - { - Mobiles[entity.Serial] = entity as Mobile; - } - break; - } - } + BroadcastStaff(0x35, true, "Writing world save snapshot failed."); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AddGuild(BaseGuild guild) => Guilds[guild.Serial] = guild; - - public static void RemoveEntity(T entity) where T : class, IEntity + else { - switch (WorldState) + try { - default: // Not Running - { - throw new Exception($"Removed {entity.GetType().Name} before world load.\n"); - } - case WorldState.Saving: - { - AppendSafetyLog("delete", entity); - goto case WorldState.WritingSave; - } - case WorldState.Loading: - case WorldState.WritingSave: - { - _pendingAdd.Remove(entity.Serial); - _pendingDelete[entity.Serial] = entity; - break; - } - case WorldState.Running: - { - if (entity.Serial.IsItem) - { - Items.Remove(entity.Serial); - } - - if (entity.Serial.IsMobile) - { - Mobiles.Remove(entity.Serial); - } - break; - } + EventSink.InvokeWorldSavePostSnapshot(SavePath, tempPath); + PathUtility.MoveDirectory(tempPath, SavePath); + } + catch (Exception ex) + { + Persistence.TraceException(ex); } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void RemoveGuild(BaseGuild guild) => Guilds.Remove(guild.Serial); + m_DiskWriteHandle.Set(); + + Core.LoopContext.Post(FinishWorldSave); } + + private static void ProcessDecay() + { + while (_decayQueue.TryDequeue(out var item)) + { + if (item.OnDecay()) + { + // TODO: Add Logging + item.Delete(); + } + } + } + + private static DateTime _serializationStart; + + internal static void SaveEntities() + { + _serializationStart = DateTime.UtcNow; + EntityPersistence.SaveEntities(Items.Values, SaveEntity); + EntityPersistence.SaveEntities(Mobiles.Values, SaveEntity); + EntityPersistence.SaveEntities(Guilds.Values, SaveEntity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SaveEntity(T entity) where T : class, ISerializable + { + if (entity is Item item && item.CanDecay() && item.LastMoved + item.DecayTime <= _serializationStart) + { + EnqueueForDecay(item); + } + + entity.Serialize(); + } + + public static void Save() + { + if (WorldState != WorldState.Running) + { + return; + } + + WaitForWriteCompletion(); // Blocks Save until current disk flush is done. + + WorldState = WorldState.Saving; + + m_DiskWriteHandle.Reset(); + + Broadcast(0x35, true, "The world is saving, please wait."); + + logger.Information("Saving world"); + + var watch = Stopwatch.StartNew(); + + Exception exception = null; + + try + { + Persistence.Serialize(); + EventSink.InvokeWorldSave(); + } + catch (Exception ex) + { + exception = ex; + } + + WorldState = WorldState.WritingSave; + + watch.Stop(); + + if (exception == null) + { + var duration = watch.Elapsed.TotalSeconds; + logger.Information("World save completed ({Duration:F2} seconds)", duration); + + // Only broadcast if it took at least 150ms + if (duration >= 0.15) + { + Broadcast(0x35, true, $"World Save completed in {duration:F2} seconds."); + } + } + else + { + logger.Error(exception, "World save failed"); + Persistence.TraceException(exception); + + BroadcastStaff(0x35, true, "World save failed."); + } + + ThreadPool.QueueUserWorkItem(WriteFiles); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEntity FindEntity(Serial serial, bool returnDeleted = false) => FindEntity(serial, returnDeleted); + + public static T FindEntity(Serial serial, bool returnDeleted = false) where T : class, IEntity + { + switch (WorldState) + { + default: return default; + case WorldState.Loading: + case WorldState.Saving: + case WorldState.WritingSave: + { + if (_pendingDelete.TryGetValue(serial, out var entity)) + { + return !returnDeleted ? null : entity as T; + } + + if (_pendingAdd.TryGetValue(serial, out entity)) + { + return entity as T; + } + + goto case WorldState.Running; + } + case WorldState.Running: + { + if (serial.IsItem) + { + Items.TryGetValue(serial, out var item); + return item as T; + } + + if (serial.IsMobile) + { + Mobiles.TryGetValue(serial, out var mob); + return mob as T; + } + + return default; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Item FindItem(Serial serial, bool returnDeleted = false) => FindEntity(serial, returnDeleted); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Mobile FindMobile(Serial serial, bool returnDeleted = false) => + FindEntity(serial, returnDeleted); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BaseGuild FindGuild(Serial serial) => Guilds.TryGetValue(serial, out var guild) ? guild : null; + + public static void AddEntity(T entity) where T : class, IEntity + { + switch (WorldState) + { + default: // Not Running + { + throw new Exception($"Added {entity.GetType().Name} before world load.\n"); + } + case WorldState.Saving: + { + AppendSafetyLog("add", entity); + goto case WorldState.WritingSave; + } + case WorldState.Loading: + case WorldState.WritingSave: + { + if (_pendingDelete.Remove(entity.Serial)) + { + logger.Warning($"Deleted then added {entity.GetType().Name} during {WorldState.ToString()} state."); + } + _pendingAdd[entity.Serial] = entity; + break; + } + case WorldState.Running: + { + if (entity.Serial.IsItem) + { + Items[entity.Serial] = entity as Item; + } + + if (entity.Serial.IsMobile) + { + Mobiles[entity.Serial] = entity as Mobile; + } + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddGuild(BaseGuild guild) => Guilds[guild.Serial] = guild; + + public static void RemoveEntity(T entity) where T : class, IEntity + { + switch (WorldState) + { + default: // Not Running + { + throw new Exception($"Removed {entity.GetType().Name} before world load.\n"); + } + case WorldState.Saving: + { + AppendSafetyLog("delete", entity); + goto case WorldState.WritingSave; + } + case WorldState.Loading: + case WorldState.WritingSave: + { + _pendingAdd.Remove(entity.Serial); + _pendingDelete[entity.Serial] = entity; + break; + } + case WorldState.Running: + { + if (entity.Serial.IsItem) + { + Items.Remove(entity.Serial); + } + + if (entity.Serial.IsMobile) + { + Mobiles.Remove(entity.Serial); + } + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RemoveGuild(BaseGuild guild) => Guilds.Remove(guild.Serial); } diff --git a/Projects/Server/World/WorldEvents.cs b/Projects/Server/World/WorldEvents.cs index 66e8b07e5..3b76807a4 100644 --- a/Projects/Server/World/WorldEvents.cs +++ b/Projects/Server/World/WorldEvents.cs @@ -1,33 +1,32 @@ using System; using System.Runtime.CompilerServices; -namespace Server +namespace Server; + +public class WorldSavePostSnapshotEventArgs { - public class WorldSavePostSnapshotEventArgs + public string OldSavePath { get; } + public string NewSavePath { get; } + + public WorldSavePostSnapshotEventArgs(string oldSavePath, string newSavePath) { - public string OldSavePath { get; } - public string NewSavePath { get; } - - public WorldSavePostSnapshotEventArgs(string oldSavePath, string newSavePath) - { - OldSavePath = oldSavePath; - NewSavePath = newSavePath; - } - } - - public static partial class EventSink - { - public static event Action WorldLoad; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeWorldLoad() => WorldLoad?.Invoke(); - - public static event Action WorldSave; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeWorldSave() => WorldSave?.Invoke(); - - public static event Action WorldSavePostSnapshot; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InvokeWorldSavePostSnapshot(string oldSavePath, string newSavePath) => - WorldSavePostSnapshot?.Invoke(new WorldSavePostSnapshotEventArgs(oldSavePath, newSavePath)); + OldSavePath = oldSavePath; + NewSavePath = newSavePath; } } + +public static partial class EventSink +{ + public static event Action WorldLoad; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeWorldLoad() => WorldLoad?.Invoke(); + + public static event Action WorldSave; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeWorldSave() => WorldSave?.Invoke(); + + public static event Action WorldSavePostSnapshot; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeWorldSavePostSnapshot(string oldSavePath, string newSavePath) => + WorldSavePostSnapshot?.Invoke(new WorldSavePostSnapshotEventArgs(oldSavePath, newSavePath)); +} diff --git a/Projects/UOContent/Accounting/Accounts.cs b/Projects/UOContent/Accounting/Accounts.cs index d3464b796..0d81363b9 100644 --- a/Projects/UOContent/Accounting/Accounts.cs +++ b/Projects/UOContent/Accounting/Accounts.cs @@ -86,7 +86,7 @@ namespace Server.Accounting IIndexInfo indexInfo = new EntityTypeIndex("Accounts"); - _accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List> accounts); + _accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List> accounts); if (_accountsById.Count > 0) { diff --git a/Projects/UOContent/Accounting/Security/AccountSecurity.cs b/Projects/UOContent/Accounting/Security/AccountSecurity.cs index b6d697f6a..946acd8cb 100644 --- a/Projects/UOContent/Accounting/Security/AccountSecurity.cs +++ b/Projects/UOContent/Accounting/Security/AccountSecurity.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: AccountSecurity.cs * * * diff --git a/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs b/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs index 9851960bc..510e93df8 100644 --- a/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/Argon2PasswordProtection.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: Argon2PasswordProtection.cs * * * diff --git a/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs b/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs index 5a689b3ce..59da42a7a 100644 --- a/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/HashAlgorithmPasswordProtection.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: HashAlgorithmPasswordProtection.cs * * * diff --git a/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs b/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs index 75cc3b6a1..4c7e03f2e 100644 --- a/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PBKDF2PasswordProtection.cs * * * diff --git a/Projects/UOContent/Commands/Object Creation/CAGCategory.cs b/Projects/UOContent/Commands/Object Creation/CAGCategory.cs index 79cc93f6b..5622575b6 100644 --- a/Projects/UOContent/Commands/Object Creation/CAGCategory.cs +++ b/Projects/UOContent/Commands/Object Creation/CAGCategory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CAGCategory.cs * * * diff --git a/Projects/UOContent/Commands/Object Creation/CAGLoader.cs b/Projects/UOContent/Commands/Object Creation/CAGLoader.cs index bb3329264..661cca9a3 100644 --- a/Projects/UOContent/Commands/Object Creation/CAGLoader.cs +++ b/Projects/UOContent/Commands/Object Creation/CAGLoader.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CAGLoader.cs * * * diff --git a/Projects/UOContent/Commands/Object Creation/CAGNode.cs b/Projects/UOContent/Commands/Object Creation/CAGNode.cs index db38c388c..8667dc16c 100644 --- a/Projects/UOContent/Commands/Object Creation/CAGNode.cs +++ b/Projects/UOContent/Commands/Object Creation/CAGNode.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CAGNode.cs * * * diff --git a/Projects/UOContent/Commands/Object Creation/CAGObject.cs b/Projects/UOContent/Commands/Object Creation/CAGObject.cs index 4b8f19968..277f20f0f 100644 --- a/Projects/UOContent/Commands/Object Creation/CAGObject.cs +++ b/Projects/UOContent/Commands/Object Creation/CAGObject.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CAGObject.cs * * * diff --git a/Projects/UOContent/Configuration/EmailConfiguration.cs b/Projects/UOContent/Configuration/EmailConfiguration.cs index 8726a5df3..0446b889c 100644 --- a/Projects/UOContent/Configuration/EmailConfiguration.cs +++ b/Projects/UOContent/Configuration/EmailConfiguration.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EmailConfiguration.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs index 643a1c2b4..9bf40c7d4 100644 --- a/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs +++ b/Projects/UOContent/Engines/CannedEvil/CannedEvilTimer.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CannedEvilTimer.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs b/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs index 4094d4fd2..3e41ff33c 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionAltar.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionAltar.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs b/Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs index a3a3d7e59..40eb06473 100644 --- a/Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionCommands.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionCommands.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs b/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs index c68bd16bc..0ef33dda8 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionPlatform.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionPlatform.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs index 37fa7d0db..72e634e8f 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkull.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionSkull.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs index 70b6fcef2..146a214dd 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullBrazier.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionSkullBrazier.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs index c6a3b2814..c60f6747d 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullPlatform.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionSkullPlatform.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs index fe6f48b0b..2c1fac7d4 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSkullType.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionSkullType.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs index 16d89198d..f9360a879 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionSpawn.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs b/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs index e1c70d7b9..546fc46ca 100755 --- a/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs +++ b/Projects/UOContent/Engines/CannedEvil/ChampionSpawnType.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChampionSpawnType.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs index c4eedc84b..f1bd4a0fe 100644 --- a/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/DungeonChampionSpawn.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: DungeonChampionSpawn.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs b/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs index ec697f2e3..3efaba7db 100644 --- a/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs +++ b/Projects/UOContent/Engines/CannedEvil/GenChampEntry.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GenChampEntry.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs index 073c50411..a21547a24 100644 --- a/Projects/UOContent/Engines/CannedEvil/GenChamps.cs +++ b/Projects/UOContent/Engines/CannedEvil/GenChamps.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GenChamps.cs * * * diff --git a/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs index b4144bad4..6ce233709 100644 --- a/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs +++ b/Projects/UOContent/Engines/CannedEvil/LLChampionSpawn.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: LLChampionSpawn.cs * * * diff --git a/Projects/UOContent/Engines/Chat/ChatPackets.cs b/Projects/UOContent/Engines/Chat/ChatPackets.cs index 31c63bdb6..7cb93c734 100644 --- a/Projects/UOContent/Engines/Chat/ChatPackets.cs +++ b/Projects/UOContent/Engines/Chat/ChatPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ChatPackets.cs * * * diff --git a/Projects/UOContent/Engines/Help/HelpTopic.cs b/Projects/UOContent/Engines/Help/HelpTopic.cs index 1b9179169..bed95c6ab 100644 --- a/Projects/UOContent/Engines/Help/HelpTopic.cs +++ b/Projects/UOContent/Engines/Help/HelpTopic.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: HelpTopic.cs * * * diff --git a/Projects/UOContent/Engines/ML Quests/MLQuestPackets.cs b/Projects/UOContent/Engines/ML Quests/MLQuestPackets.cs index 485313ffe..d005fb9cc 100644 --- a/Projects/UOContent/Engines/ML Quests/MLQuestPackets.cs +++ b/Projects/UOContent/Engines/ML Quests/MLQuestPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MLQuestPackets.cs * * * diff --git a/Projects/UOContent/Engines/Party/PartyPackets.cs b/Projects/UOContent/Engines/Party/PartyPackets.cs index 97c2bb952..3b2e7a5dc 100644 --- a/Projects/UOContent/Engines/Party/PartyPackets.cs +++ b/Projects/UOContent/Engines/Party/PartyPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: PartyPackets.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs b/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs index 75f79c0e5..59327e205 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ConvertPremiumSpawners.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs index 53219a790..420cc3d55 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/EditSpawnerCommand.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EditSpawnerCommand.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs index e3092ebe3..6d13d8296 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ExportSpawnersCommand.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs index 4c17ea231..7d86464c6 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/GenerateSpawnersCommand.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GenerateSpawnersCommand.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs index 9d0fcc742..d0335362c 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/RespawnCommand.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: RespawnCommand.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs index 76076d6a8..fa9ba52ac 100644 --- a/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs +++ b/Projects/UOContent/Engines/Spawners/Commands/SpawnPropsCommand.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: SpawnPropsCommand.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs b/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs index dcc295b1b..74cdaffe3 100644 --- a/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs +++ b/Projects/UOContent/Engines/Spawners/ProximitySpawner.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ProximitySpawner.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/RegionSpawner.cs b/Projects/UOContent/Engines/Spawners/RegionSpawner.cs index d3446e22a..a28c4f35e 100644 --- a/Projects/UOContent/Engines/Spawners/RegionSpawner.cs +++ b/Projects/UOContent/Engines/Spawners/RegionSpawner.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: RegionSpawner.cs * * * diff --git a/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs b/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs index 684b3d477..a8160ec06 100644 --- a/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs +++ b/Projects/UOContent/Engines/Spawners/SpawnPropsGump.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: GlobalPropsGump.cs * * * diff --git a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePackets.cs b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePackets.cs index a66d08291..9e6be6b21 100644 --- a/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePackets.cs +++ b/Projects/UOContent/Engines/Veteran Rewards/Character Statue Maker/CharacterStatuePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CharacterStatuePackets.cs * * * diff --git a/Projects/UOContent/Items/Books/BookPackets.cs b/Projects/UOContent/Items/Books/BookPackets.cs index 8e9c6d257..cc2b27309 100644 --- a/Projects/UOContent/Items/Books/BookPackets.cs +++ b/Projects/UOContent/Items/Books/BookPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BookPackets.cs * * * diff --git a/Projects/UOContent/Items/Bulletin Boards/BulletinBoardPackets.cs b/Projects/UOContent/Items/Bulletin Boards/BulletinBoardPackets.cs index 53086b70f..e8df062be 100644 --- a/Projects/UOContent/Items/Bulletin Boards/BulletinBoardPackets.cs +++ b/Projects/UOContent/Items/Bulletin Boards/BulletinBoardPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BulletinBoardPackets.cs * * * diff --git a/Projects/UOContent/Items/Games/Mahjong/MahjongPackets.cs b/Projects/UOContent/Items/Games/Mahjong/MahjongPackets.cs index 8d6dfe42e..b4d7fb7c4 100644 --- a/Projects/UOContent/Items/Games/Mahjong/MahjongPackets.cs +++ b/Projects/UOContent/Items/Games/Mahjong/MahjongPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MahjongPackets.cs * * * diff --git a/Projects/UOContent/Items/Maps/MapItemPackets.cs b/Projects/UOContent/Items/Maps/MapItemPackets.cs index 1783aa284..728db3227 100644 --- a/Projects/UOContent/Items/Maps/MapItemPackets.cs +++ b/Projects/UOContent/Items/Maps/MapItemPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MapItemPackets.cs * * * diff --git a/Projects/UOContent/Items/Misc/Corpses/CorpsePackets.cs b/Projects/UOContent/Items/Misc/Corpses/CorpsePackets.cs index b78300f70..8299b372e 100644 --- a/Projects/UOContent/Items/Misc/Corpses/CorpsePackets.cs +++ b/Projects/UOContent/Items/Misc/Corpses/CorpsePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: CorpsePackets.cs * * * diff --git a/Projects/UOContent/Json/Converters/TextDefinitionConverter.cs b/Projects/UOContent/Json/Converters/TextDefinitionConverter.cs index 3835365ff..2a7f95989 100644 --- a/Projects/UOContent/Json/Converters/TextDefinitionConverter.cs +++ b/Projects/UOContent/Json/Converters/TextDefinitionConverter.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TextDefinitionConverter.cs * * * diff --git a/Projects/UOContent/Json/Converters/TextDefinitionConverterFactory.cs b/Projects/UOContent/Json/Converters/TextDefinitionConverterFactory.cs index 01dc9a1b7..8bd19578c 100644 --- a/Projects/UOContent/Json/Converters/TextDefinitionConverterFactory.cs +++ b/Projects/UOContent/Json/Converters/TextDefinitionConverterFactory.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TextDefinitionConverterFactory.cs * * * diff --git a/Projects/UOContent/Multis/Boats/BoatPackets.cs b/Projects/UOContent/Multis/Boats/BoatPackets.cs index 65a074123..808ee0150 100644 --- a/Projects/UOContent/Multis/Boats/BoatPackets.cs +++ b/Projects/UOContent/Multis/Boats/BoatPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: BoatPackets.cs * * * diff --git a/Projects/UOContent/Multis/Houses/HousePackets.cs b/Projects/UOContent/Multis/Houses/HousePackets.cs index f8c41a47f..6f1ad7b27 100644 --- a/Projects/UOContent/Multis/Houses/HousePackets.cs +++ b/Projects/UOContent/Multis/Houses/HousePackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: HousePackets.cs * * * diff --git a/Projects/UOContent/Network/EntityPackets.cs b/Projects/UOContent/Network/EntityPackets.cs index 678b91b56..efa784c83 100644 --- a/Projects/UOContent/Network/EntityPackets.cs +++ b/Projects/UOContent/Network/EntityPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: EntityPackets.cs * * * diff --git a/Projects/UOContent/Network/FreeshardProtocol.cs b/Projects/UOContent/Network/FreeshardProtocol.cs index 62efd6abd..9f203c7cb 100644 --- a/Projects/UOContent/Network/FreeshardProtocol.cs +++ b/Projects/UOContent/Network/FreeshardProtocol.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: FreeshardProtocol.cs * * * diff --git a/Projects/UOContent/Network/MapUO.cs b/Projects/UOContent/Network/MapUO.cs index 751d0402f..331bbddf4 100644 --- a/Projects/UOContent/Network/MapUO.cs +++ b/Projects/UOContent/Network/MapUO.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: MapUO.cs * * * diff --git a/Projects/UOContent/Network/ProtocolExtensions.cs b/Projects/UOContent/Network/ProtocolExtensions.cs index 6df2a6a8b..e51ae3678 100644 --- a/Projects/UOContent/Network/ProtocolExtensions.cs +++ b/Projects/UOContent/Network/ProtocolExtensions.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: ProtocolExtension.cs * * * diff --git a/Projects/UOContent/Network/UOGateway.cs b/Projects/UOContent/Network/UOGateway.cs index b3b3bdcc4..7af49d228 100644 --- a/Projects/UOContent/Network/UOGateway.cs +++ b/Projects/UOContent/Network/UOGateway.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright (C) 2019-2021 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: UOGateway.cs * * * diff --git a/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs b/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs index 6bf666de4..d7f996d57 100644 --- a/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs +++ b/Projects/UOContent/Skills/Tracking/OutgoingArrowPackets.cs @@ -1,6 +1,6 @@ /************************************************************************* * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * + * Copyright 2019-2022 - ModernUO Development Team * * Email: hi@modernuo.com * * File: OutgoingArrowPackets.cs * * * diff --git a/Projects/UOContent/World Saves/AutoArchive.cs b/Projects/UOContent/World Saves/AutoArchive.cs index 837c826e1..0eaf3b180 100755 --- a/Projects/UOContent/World Saves/AutoArchive.cs +++ b/Projects/UOContent/World Saves/AutoArchive.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Threading; -using System.Threading.Tasks; using Microsoft.Toolkit.HighPerformance; using Server.Compression; using Server.Logging; diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0e98e89f1..4faf09949 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,7 @@ jobs: displayName: 'Install .NET 6' inputs: packageType: sdk - version: 6.0.400 + version: 6.0.401 - task: NuGetAuthenticate@0 - script: ./publish.cmd Release displayName: 'Build' @@ -49,7 +49,7 @@ jobs: displayName: 'Install .NET 6' inputs: packageType: sdk - version: 6.0.400 + version: 6.0.401 - task: NuGetAuthenticate@0 - script: ./publish.cmd Release displayName: 'Build'