From ea5d09a7d7c130da22353aac5c6ad5f979a533bf Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 14 Feb 2021 16:06:49 -0800 Subject: [PATCH] fix(core): Fixes map issues at New Haven (#509) - [X] Fixes map issues at New Haven by turning off static diffs - [X] Some code cleanup and reformatting of tile matrix, tile matrix patch, and tile data - [X] Adds NetState.Flush for generating spawners so it doesn't feel like the server is frozen - [X] Reverts NativeReader changes from a while back. - [X] Adds more string reading for BufferReader. Notes: BufferReader is still `little endian` compared to `SpanReader` which is `big endian` (for packets). To that end, the RunUO deserialization `ReadString()` was made obsolete since it is ambiguous, and contains extra fields other than simply reading a string. Furthermore, we shouldn't be using UTF8 (for now) since it is slow. --- .gitignore | 1 + ModernUO.sln | 4 +- .../Packets/Outgoing/ItemPacketTests.cs | 3 + .../Packets/Outgoing/MapPacketTests.cs | 13 +- Projects/Server/Buffers/SpanReader.cs | 24 + Projects/Server/ExpansionInfo.cs | 8 +- Projects/Server/Main.cs | 1 + Projects/Server/Mobiles/Mobile.cs | 5 +- Projects/Server/NativeReader.cs | 131 ++++-- .../NetState/NetState.ClientVersion.cs | 3 + .../Network/Packets/IncomingAccountPackets.cs | 5 +- .../Packets/IncomingTargetingPackets.cs | 4 +- .../Network/Packets/OutgoingMapPackets.cs | 53 ++- Projects/Server/Serialization/BufferReader.cs | 83 +++- Projects/Server/Text/StringHelpers.cs | 9 + Projects/Server/TileData.cs | 352 ++++++++------ Projects/Server/TileMatrix/TileMatrix.cs | 429 +++++++++--------- Projects/Server/TileMatrix/TileMatrixPatch.cs | 71 ++- .../Engines/Spawners/GenerateSpawners.cs | 3 + Projects/UOContent/Spells/Base/SpellHelper.cs | 2 +- version.json | 2 +- 21 files changed, 724 insertions(+), 482 deletions(-) diff --git a/.gitignore b/.gitignore index 5db3887f9..f65f7a1c6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /Distribution/Assemblies /Distribution/Configuration/modernuo.json /Distribution/Configuration/email-settings.json +/Distribution/Configuration/throttles.json /Distribution/Logs /Distribution/Backups /Distribution/Saves diff --git a/ModernUO.sln b/ModernUO.sln index e71252615..bfd1dd1c9 100644 --- a/ModernUO.sln +++ b/ModernUO.sln @@ -10,7 +10,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server.Tests", "Projects\Se EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UOContent.Tests", "Projects\UOContent.Tests\UOContent.Tests.csproj", "{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Projects\Benchmarks\Benchmarks.csproj", "{38B7E4A1-FDDD-486C-98EE-BA7B13712528}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Projects\Benchmarks\Benchmarks.csproj", "{38B7E4A1-FDDD-486C-98EE-BA7B13712528}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -43,6 +43,8 @@ Global {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|x64.Build.0 = Debug|x64 {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|x64.ActiveCfg = Release|x64 {3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|x64.Build.0 = Release|x64 + {38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Analyze|x64.ActiveCfg = Release|x64 + {38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Analyze|x64.Build.0 = Release|x64 {38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Debug|x64.ActiveCfg = Debug|x64 {38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Debug|x64.Build.0 = Debug|x64 {38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Release|x64.ActiveCfg = Release|x64 diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs index a994fa5c7..a6d975a2a 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/ItemPacketTests.cs @@ -20,6 +20,7 @@ namespace Server.Tests.Network 1, 1, 1, + 1, 1 ); @@ -55,6 +56,7 @@ namespace Server.Tests.Network 1, 1, 1, + 1, 1 ); @@ -90,6 +92,7 @@ namespace Server.Tests.Network 1, 1, 1, + 1, 1 ); diff --git a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs index 6b404df59..9d61e4089 100644 --- a/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs +++ b/Projects/Server.Tests/Tests/Network/Packets/Outgoing/MapPacketTests.cs @@ -6,12 +6,17 @@ namespace Server.Tests.Network { public class MapPatchesTests : IClassFixture { - [Fact] - public void TestMapPatches() + [Theory] + [InlineData(ProtocolChanges.Version500a, ClientFlags.Malas | ClientFlags.Trammel | ClientFlags.Felucca)] + [InlineData(ProtocolChanges.Version7090, ClientFlags.TerMur | ClientFlags.Trammel | ClientFlags.Felucca)] + public void TestMapPatches(ProtocolChanges protocolChanges, ClientFlags flags) { - var expected = new MapPatches().Compile(); - var ns = PacketTestUtilities.CreateTestNetState(); + ns.ProtocolChanges = protocolChanges; + ns.Flags = flags; + + var expected = ns.ProtocolChanges >= ProtocolChanges.Version6000 ? Span.Empty : new MapPatches().Compile(); + ns.SendMapPatches(); var result = ns.SendPipe.Reader.TryRead(); diff --git a/Projects/Server/Buffers/SpanReader.cs b/Projects/Server/Buffers/SpanReader.cs index e8e67b6b4..90eec159a 100644 --- a/Projects/Server/Buffers/SpanReader.cs +++ b/Projects/Server/Buffers/SpanReader.cs @@ -139,6 +139,30 @@ namespace System.Buffers return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long ReadInt64() + { + if (!BinaryPrimitives.TryReadInt64BigEndian(_buffer.Slice(Position), out var value)) + { + throw new OutOfMemoryException(); + } + + Position += 8; + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong ReadUInt64() + { + if (!BinaryPrimitives.TryReadUInt64BigEndian(_buffer.Slice(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) { diff --git a/Projects/Server/ExpansionInfo.cs b/Projects/Server/ExpansionInfo.cs index db9034647..4ed98bea1 100644 --- a/Projects/Server/ExpansionInfo.cs +++ b/Projects/Server/ExpansionInfo.cs @@ -249,9 +249,7 @@ namespace Server FeatureFlags supportedFeatures, CharacterListFlags charListFlags, HousingFlags customHousingFlag - ) - : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => - ClientFlags = clientFlags; + ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => ClientFlags = clientFlags; public ExpansionInfo( int id, @@ -260,9 +258,7 @@ namespace Server FeatureFlags supportedFeatures, CharacterListFlags charListFlags, HousingFlags customHousingFlag - ) - : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => - RequiredClient = requiredClient; + ) : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => RequiredClient = requiredClient; private ExpansionInfo( int id, diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index 90064f114..eafc01d00 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -459,6 +459,7 @@ namespace Server AssemblyHandler.Invoke("Configure"); TileMatrixLoader.LoadTileMatrix(); + RegionLoader.LoadRegions(); World.Load(); diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index c81daced5..a1f1cfa5e 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -2814,10 +2814,7 @@ namespace Server { ns.SendMapChange(Map); - if (!Core.SE && ns.ProtocolChanges < ProtocolChanges.Version6000) - { - ns.SendMapPatches(); - } + ns.SendMapPatches(); ns.SendSeasonChange((byte)GetSeason(), true); diff --git a/Projects/Server/NativeReader.cs b/Projects/Server/NativeReader.cs index 148068ac6..026ca99b4 100644 --- a/Projects/Server/NativeReader.cs +++ b/Projects/Server/NativeReader.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Runtime.InteropServices; using System.Threading; @@ -8,61 +9,121 @@ namespace Server { private static readonly INativeReader m_NativeReader; - static NativeReader() - { - if (Core.Unix) - { - m_NativeReader = new NativeReaderUnix(); - } - else - { - m_NativeReader = new NativeReaderWin32(); - } - } + static NativeReader() => m_NativeReader = Core.Unix ? (INativeReader)new NativeReaderUnix() : new NativeReaderWin32(); - public static unsafe void Read(IntPtr p, void* buffer, int length) - { - m_NativeReader.Read(p, buffer, length); - } + public static unsafe int Read(FileStream source, void* buffer, int length) => + m_NativeReader.Read(source, buffer, length); + + public static unsafe int Read(FileStream source, void* buffer, int bufferIndex, int length) => + m_NativeReader.Read(source, buffer, bufferIndex, length); + + public static unsafe int Read(FileStream source, int sourceIndex, void* buffer, int length) => + m_NativeReader.Read(source, sourceIndex, buffer, length); + + public static unsafe int Read(FileStream source, int sourceIndex, void* buffer, int bufferIndex, int length) => + m_NativeReader.Read(source, sourceIndex, buffer, bufferIndex, length); } public interface INativeReader { - unsafe void Read(IntPtr p, void* buffer, int length); + unsafe int Read(FileStream source, void* buffer, int length); + unsafe int Read(FileStream source, void* buffer, int bufferIndex, int length); + unsafe int Read(FileStream source, int sourceIndex, void* buffer, int length); + unsafe int Read(FileStream source, int sourceIndex, void* buffer, int bufferIndex, int length); } public sealed class NativeReaderWin32 : INativeReader { - public unsafe void Read(IntPtr p, void* buffer, int length) + internal class UnsafeNativeMethods { - uint lpNumberOfBytesRead = 0; - UnsafeNativeMethods.ReadFile(p, buffer, (uint)length, ref lpNumberOfBytesRead, null); + [DllImport("kernel32")] + internal static extern unsafe bool ReadFile(IntPtr hFile, void* lpBuffer, uint nNumberOfBytesToRead, ref uint lpNumberOfBytesRead, NativeOverlapped* lpOverlapped); } - internal static class UnsafeNativeMethods - { - /*[DllImport("kernel32")] - internal unsafe static extern int _lread(IntPtr hFile, void* lpBuffer, int wBytes);*/ + public unsafe int Read(FileStream source, void* buffer, int length) => InternalRead(source, buffer, 0, length); - [DllImport("kernel32")] - internal static extern unsafe bool ReadFile( - IntPtr hFile, void* lpBuffer, uint nNumberOfBytesToRead, - ref uint lpNumberOfBytesRead, NativeOverlapped* lpOverlapped - ); + public unsafe int Read(FileStream source, void* buffer, int bufferIndex, int length) => InternalRead(source, buffer, bufferIndex, length); + + public unsafe int Read(FileStream source, int sourceIndex, void* buffer, int length) + { + if (source.Seek(sourceIndex, SeekOrigin.Begin) == sourceIndex) + { + return InternalRead(source, buffer, 0, length); + } + + return -1; + } + + public unsafe int Read(FileStream source, int sourceIndex, void* buffer, int bufferIndex, int length) + { + if (source.Seek(sourceIndex, SeekOrigin.Begin) == sourceIndex) + { + return InternalRead(source, buffer, bufferIndex, length); + } + + return -1; + } + + internal unsafe int InternalRead(FileStream source, void* buffer, int bufferIndex, int length) + { + var byteCount = 0U; + + if (!UnsafeNativeMethods.ReadFile(source.SafeFileHandle!.DangerousGetHandle(), (byte*)buffer + bufferIndex, (uint)length, ref byteCount, null)) + { + return -1; + } + + if (byteCount > 0) + { + source.Seek(byteCount, SeekOrigin.Current); + } + + return (int)byteCount; } } public sealed class NativeReaderUnix : INativeReader { - public unsafe void Read(IntPtr p, void* buffer, int length) - { - _ = UnsafeNativeMethods.read(p, buffer, length); - } - - internal static class UnsafeNativeMethods + internal class UnsafeNativeMethods { [DllImport("libc")] - internal static extern unsafe int read(IntPtr p, void* buffer, int length); + internal static extern unsafe int read(IntPtr ptr, void* buffer, int length); + } + + public unsafe int Read(FileStream source, void* buffer, int length) => InternalRead(source, buffer, 0, length); + + public unsafe int Read(FileStream source, void* buffer, int bufferIndex, int length) => InternalRead(source, buffer, bufferIndex, length); + + public unsafe int Read(FileStream source, int sourceIndex, void* buffer, int length) + { + if (source.Seek(sourceIndex, SeekOrigin.Begin) == sourceIndex) + { + return InternalRead(source, buffer, 0, length); + } + + return -1; + } + + public unsafe int Read(FileStream source, int sourceIndex, void* buffer, int bufferIndex, int length) + { + if (source.Seek(sourceIndex, SeekOrigin.Begin) == sourceIndex) + { + return InternalRead(source, buffer, bufferIndex, length); + } + + return -1; + } + + internal unsafe int InternalRead(FileStream source, void* buffer, int bufferIndex, int length) + { + var byteCount = UnsafeNativeMethods.read(source.Handle, (byte*)buffer + bufferIndex, length); + + if (byteCount > 0) + { + source.Seek(byteCount, SeekOrigin.Current); + } + + return byteCount; } } } diff --git a/Projects/Server/Network/NetState/NetState.ClientVersion.cs b/Projects/Server/Network/NetState/NetState.ClientVersion.cs index 090accb3f..4302ec0b1 100644 --- a/Projects/Server/Network/NetState/NetState.ClientVersion.cs +++ b/Projects/Server/Network/NetState/NetState.ClientVersion.cs @@ -39,6 +39,9 @@ namespace Server.Network public ProtocolChanges ProtocolChanges { get; set; } public ClientFlags Flags { get; set; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasFlag(ClientFlags flag) => (Flags & flag) != 0; + public ClientVersion Version { get => _version; diff --git a/Projects/Server/Network/Packets/IncomingAccountPackets.cs b/Projects/Server/Network/Packets/IncomingAccountPackets.cs index 3041ec8f4..229934a4f 100644 --- a/Projects/Server/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/Server/Network/Packets/IncomingAccountPackets.cs @@ -306,10 +306,7 @@ namespace Server.Network state.SendMapChange(m.Map); - if (!Core.SE && state.ProtocolChanges < ProtocolChanges.Version6000) - { - state.SendMapPatches(); - } + state.SendMapPatches(); state.SendSeasonChange((byte)m.GetSeason(), true); diff --git a/Projects/Server/Network/Packets/IncomingTargetingPackets.cs b/Projects/Server/Network/Packets/IncomingTargetingPackets.cs index 1bdd1d1c5..5dea5a795 100644 --- a/Projects/Server/Network/Packets/IncomingTargetingPackets.cs +++ b/Projects/Server/Network/Packets/IncomingTargetingPackets.cs @@ -13,6 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ +using System; using Server.Diagnostics; using Server.Targeting; @@ -33,7 +34,8 @@ namespace Server.Network Serial serial = reader.ReadUInt32(); int x = reader.ReadInt16(); int y = reader.ReadInt16(); - int z = reader.ReadInt16(); + reader.ReadByte(); + int z = reader.ReadSByte(); int graphic = reader.ReadUInt16(); if (targetID == unchecked((int)0xDEADBEEF)) diff --git a/Projects/Server/Network/Packets/OutgoingMapPackets.cs b/Projects/Server/Network/Packets/OutgoingMapPackets.cs index 8813821cd..4deb94e29 100644 --- a/Projects/Server/Network/Packets/OutgoingMapPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingMapPackets.cs @@ -22,28 +22,55 @@ namespace Server.Network { public static void SendMapPatches(this NetState ns) { - if (ns == null) + if (ns == null || ns.ProtocolChanges >= ProtocolChanges.Version6000) { return; } - var writer = new SpanWriter(stackalloc byte[41]); + int count; + + if (ns.HasFlag(ClientFlags.TerMur)) + { + count = 6; + } + else if (ns.HasFlag(ClientFlags.Tokuno)) + { + count = 5; + } + else if (ns.HasFlag(ClientFlags.Malas)) + { + count = 4; + } + else if (ns.HasFlag(ClientFlags.Ilshenar)) + { + count = 3; + } + else if (ns.HasFlag(ClientFlags.Trammel)) + { + count = 2; + } + else if (ns.HasFlag(ClientFlags.Felucca)) + { + count = 1; + } + else + { + return; + } + + var writer = new SpanWriter(stackalloc byte[9 + count * 8]); writer.Write((byte)0xBF); // Packet ID writer.Write((ushort)41); // Length writer.Write((ushort)0x18); // Subpacket - writer.Write(4); // Map count? + writer.Write(count); - writer.Write(Map.Felucca.Tiles.Patch.StaticBlocks); - writer.Write(Map.Felucca.Tiles.Patch.LandBlocks); + for (int i = 0; i < count; i++) + { + var map = Map.Maps[i]; - writer.Write(Map.Trammel.Tiles.Patch.StaticBlocks); - writer.Write(Map.Trammel.Tiles.Patch.LandBlocks); - - writer.Write(Map.Ilshenar.Tiles.Patch.StaticBlocks); - writer.Write(Map.Ilshenar.Tiles.Patch.LandBlocks); - - writer.Write(Map.Malas.Tiles.Patch.StaticBlocks); - writer.Write(Map.Malas.Tiles.Patch.LandBlocks); + writer.Write(map.Tiles.Patch.StaticBlocks); + writer.Write(map.Tiles.Patch.LandBlocks); + } ns.Send(writer.Span); } diff --git a/Projects/Server/Serialization/BufferReader.cs b/Projects/Server/Serialization/BufferReader.cs index 59f8611d1..7b02f5362 100644 --- a/Projects/Server/Serialization/BufferReader.cs +++ b/Projects/Server/Serialization/BufferReader.cs @@ -19,8 +19,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; +using System.Runtime.CompilerServices; using System.Text; using Server.Guilds; +using Server.Network; using Server.Text; namespace Server @@ -48,6 +50,7 @@ namespace Server _position = 0; } + [Obsolete("RunUO backward compatible method that should be replaced.")] public string ReadString() { if (!ReadBool()) @@ -56,16 +59,86 @@ namespace Server } var length = ReadEncodedInt(); - if (length <= 0) + return length <= 0 ? "" : ReadString(_encoding, false, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1) + { + int sizeT = TextEncoding.GetByteLengthForEncoding(encoding); + + bool isFixedLength = fixedLength > -1; + + var remaining = _buffer.Length - _position; + int size; + + if (isFixedLength) { - return ""; + size = fixedLength * sizeT; + if (size > remaining) + { + throw new OutOfMemoryException(); + } + } + else + { + size = remaining - (remaining & (sizeT - 1)); } - var s = _encoding.GetString(_buffer.AsSpan(Position, length)); - _position += length; - return s; + var buffer = _buffer.AsSpan(Position, size); + + int index = buffer.IndexOfTerminator(sizeT); + + var span = buffer.SliceToLength(index < 0 ? size : index); + _position += isFixedLength || index < 0 ? size : index + sizeT; + 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); + public DateTime ReadDateTime() => new(ReadLong(), DateTimeKind.Utc); public TimeSpan ReadTimeSpan() => new(ReadLong()); diff --git a/Projects/Server/Text/StringHelpers.cs b/Projects/Server/Text/StringHelpers.cs index 63a8d5a81..46630d7c2 100644 --- a/Projects/Server/Text/StringHelpers.cs +++ b/Projects/Server/Text/StringHelpers.cs @@ -243,6 +243,15 @@ namespace Server return list; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int IndexOfTerminator(this Span buffer, int sizeT) => + sizeT switch + { + 2 => MemoryMarshal.Cast(buffer).IndexOf((char)0) * 2, + 4 => MemoryMarshal.Cast(buffer).IndexOf((uint)0) * 4, + _ => buffer.IndexOf((byte)0) + }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int IndexOfTerminator(this ReadOnlySpan buffer, int sizeT) => sizeT switch diff --git a/Projects/Server/TileData.cs b/Projects/Server/TileData.cs index e10cf880a..a83afdd21 100644 --- a/Projects/Server/TileData.cs +++ b/Projects/Server/TileData.cs @@ -1,139 +1,183 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: TileData.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.Buffers; using System.IO; +using System.Runtime.CompilerServices; using System.Text; +using Server.Network; namespace Server { public struct LandData { + public string Name { get; set; } + + public TileFlag Flags { get; set; } + public LandData(string name, TileFlag flags) { Name = name; Flags = flags; } - - public string Name { get; set; } - - public TileFlag Flags { get; set; } } + [PropertyObject] public struct ItemData { - private byte m_Weight; - private byte m_Quality; - private byte m_Quantity; - private byte m_Value; - private byte m_Height; + 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 quantity, int value, int height) + public ItemData(string name, TileFlag flags, int weight, int quality, int animation, int quantity, int value, int height) { Name = name; Flags = flags; - m_Weight = (byte)weight; - m_Quality = (byte)quality; - m_Quantity = (byte)quantity; - m_Value = (byte)value; - m_Height = (byte)height; + _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; } - public bool Bridge - { - get => (Flags & TileFlag.Bridge) != 0; - set - { - if (value) - { - Flags |= TileFlag.Bridge; - } - else - { - Flags &= ~TileFlag.Bridge; - } - } - } - - public bool Impassable - { - get => (Flags & TileFlag.Impassable) != 0; - set - { - if (value) - { - Flags |= TileFlag.Impassable; - } - else - { - Flags &= ~TileFlag.Impassable; - } - } - } - - public bool Surface - { - get => (Flags & TileFlag.Surface) != 0; - set - { - if (value) - { - Flags |= TileFlag.Surface; - } - else - { - Flags &= ~TileFlag.Surface; - } - } - } - + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] public int Weight { - get => m_Weight; - set => m_Weight = (byte)value; + get => _weight; + set => _weight = (byte)value; } + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] public int Quality { - get => m_Quality; - set => m_Quality = (byte)value; + 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 => m_Quantity; - set => m_Quantity = (byte)value; + get => _quantity; + set => _quantity = (byte)value; } + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] public int Value { - get => m_Value; - set => m_Value = (byte)value; + get => _value; + set => _value = (byte)value; } + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] public int Height { - get => m_Height; - set => m_Height = (byte)value; + get => _height; + set => _height = (byte)value; } - public int CalcHeight - { - get - { - if ((Flags & TileFlag.Bridge) != 0) - { - return m_Height / 2; - } + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public int CalcHeight => (Flags & TileFlag.Bridge) != 0 ? _height / 2 : _height; - return m_Height; + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Bridge + { + get => this[TileFlag.Bridge]; + set => this[TileFlag.Bridge] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Wall + { + get => this[TileFlag.Wall]; + set => this[TileFlag.Wall] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Window + { + get => this[TileFlag.Window]; + set => this[TileFlag.Window] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Impassable + { + get => this[TileFlag.Impassable]; + set => this[TileFlag.Impassable] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Surface + { + get => this[TileFlag.Surface]; + set => this[TileFlag.Surface] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Roof + { + get => this[TileFlag.Roof]; + set => this[TileFlag.Roof] = value; + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool LightSource + { + get => this[TileFlag.LightSource]; + set => this[TileFlag.LightSource] = value; + } + + public bool this[TileFlag flag] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => (Flags & flag) != 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if (value) + { + Flags |= flag; + } + else + { + Flags &= ~flag; + } } } } [Flags] - public enum TileFlag : long + public enum TileFlag : ulong { None = 0x00000000, Background = 0x00000001, @@ -167,78 +211,125 @@ namespace Server Roof = 0x10000000, Door = 0x20000000, StairBack = 0x40000000, - StairRight = 0x80000000 + 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 { - private static readonly byte[] m_StringBuffer = new byte[20]; + 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() { - ItemTable = new ItemData[0x10000]; - LandTable = new LandData[0x4000]; - if (Core.IsRunningFromXUnit) { return; } + Load(); + } + + private static void Load() + { var filePath = Core.FindDataFile("tiledata.mul"); using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - var bin = new BinaryReader(fs); + var buffer = GC.AllocateUninitializedArray((int)fs.Length); + fs.Read(buffer); + var bin = new BufferReader(buffer); - var is7090 = fs.Length == 3188736; - var is7000 = fs.Length == 1644544; + bool is64BitFlags; + const int landLength = 0x4000; + int itemLength; - for (var i = 0; i < 0x4000; i++) + if (fs.Length >= 3188736) // 7.0.9.0 { - // header - if (is7090) - { - if (i == 1 || i > 0 && (i & 0x1F) == 0) - { - bin.ReadInt32(); - } - } - else if ((i & 0x1F) == 0) - { - bin.ReadInt32(); - } - - var flags = (TileFlag)(is7090 ? bin.ReadInt64() : bin.ReadInt32()); - bin.ReadInt16(); // skip 2 bytes -- textureID - - LandTable[i] = new LandData(ReadNameString(bin), flags); + is64BitFlags = true; + itemLength = 0x10000; + } + else if (fs.Length >= 1644544) // 7.0.0.0 + { + is64BitFlags = false; + itemLength = 0x8000; + } + else + { + is64BitFlags = false; + itemLength = 0x4000; } - var length = is7090 ? 0x10000 : - is7000 ? 0x8000 : 0x4000; + for (var i = 0; i < landLength; i++) + { + if (is64BitFlags ? i == 1 || i > 0 && (i & 0x1F) == 0 : (i & 0x1F) == 0) + { + bin.ReadInt(); // header + } - for (var i = 0; i < length; i++) + var flags = (TileFlag)(is64BitFlags ? bin.ReadULong() : bin.ReadUInt()); + bin.ReadShort(); // skip 2 bytes -- textureID + + LandTable[i] = new LandData(bin.ReadAscii(20), flags); + } + + for (var i = 0; i < itemLength; i++) { if ((i & 0x1F) == 0) { - bin.ReadInt32(); // header + bin.ReadInt(); // header } - var flags = (TileFlag)(is7090 ? bin.ReadInt64() : bin.ReadInt32()); + var flags = (TileFlag)(is64BitFlags ? bin.ReadULong() : bin.ReadUInt()); int weight = bin.ReadByte(); int quality = bin.ReadByte(); - bin.ReadInt16(); + int animation = bin.ReadUShort(); bin.ReadByte(); int quantity = bin.ReadByte(); - bin.ReadInt32(); + bin.ReadInt(); bin.ReadByte(); int value = bin.ReadByte(); int height = bin.ReadByte(); ItemTable[i] = new ItemData( - ReadNameString(bin), + bin.ReadAscii(20), flags, weight, quality, + animation, quantity, value, height @@ -248,32 +339,5 @@ namespace Server MaxLandValue = LandTable.Length - 1; MaxItemValue = ItemTable.Length - 1; } - - public static LandData[] LandTable { get; } - - public static ItemData[] ItemTable { get; } - - public static int MaxLandValue { get; } - - public static int MaxItemValue { get; } - - private static string ReadNameString(BinaryReader bin) - { - bin.Read(m_StringBuffer, 0, 20); - - var count = 0; - - while (count < 20) - { - if (m_StringBuffer[count] == 0) - { - break; - } - - count++; - } - - return Encoding.ASCII.GetString(new ReadOnlySpan(m_StringBuffer, 0, count)); - } } } diff --git a/Projects/Server/TileMatrix/TileMatrix.cs b/Projects/Server/TileMatrix/TileMatrix.cs index 5a4bd3271..74fb818f2 100644 --- a/Projects/Server/TileMatrix/TileMatrix.cs +++ b/Projects/Server/TileMatrix/TileMatrix.cs @@ -8,81 +8,73 @@ namespace Server { public class TileMatrix { - private static readonly List m_Instances = new(); + private static readonly List _instances = new(); - private readonly int m_FileIndex; - private readonly List m_FileShare = new(); + private readonly StaticTile[][][][][] _staticTiles; + private readonly LandTile[][][] _landTiles; + private readonly LandTile[] _invalidLandBlock; + private readonly StaticTile[][][] _emptyStaticBlock; + private readonly UOPIndex _mapIndex; + private readonly int _fileIndex; + private readonly Map _map; + private readonly int[][] _staticPatches; + private readonly int[][] _landPatches; + private readonly List _fileShare = new(); - private readonly BinaryReader m_IndexReader; - - private readonly LandTile[] m_InvalidLandBlock; - private readonly int[][] m_LandPatches; - private readonly LandTile[][][] m_LandTiles; - - private readonly UOPIndex m_MapIndex; - - private readonly FileStream m_MapStream; - - private readonly Map m_Owner; - - private readonly int[][] m_StaticPatches; - private readonly StaticTile[][][][][] m_StaticTiles; - - private readonly TileList m_TilesList = new(); - - private TileList[][] m_Lists; - private DateTime m_NextLandWarning; - - private DateTime m_NextStaticWarning; - - private StaticTile[] m_TileBuffer = new StaticTile[128]; + public TileMatrixPatch Patch { get; } + public int BlockWidth { get; } + public int BlockHeight { get; } + public FileStream MapStream { get; } + public FileStream IndexStream { get; } + public FileStream DataStream { get; } + public BinaryReader IndexReader { get; } public TileMatrix(Map owner, int fileIndex, int mapID, int width, int height) { - lock (m_Instances) + lock (_instances) { - for (var i = 0; i < m_Instances.Count; ++i) + for (var i = 0; i < _instances.Count; ++i) { - var tm = m_Instances[i]; + var tm = _instances[i]; - if (tm.m_FileIndex == fileIndex) + if (tm._fileIndex == fileIndex) { - lock (m_FileShare) + lock (_fileShare) { - lock (tm.m_FileShare) + lock (tm._fileShare) { - tm.m_FileShare.Add(this); - m_FileShare.Add(tm); + tm._fileShare.Add(this); + _fileShare.Add(tm); } } } } - m_Instances.Add(this); + _instances.Add(this); } - m_FileIndex = fileIndex; + _fileIndex = fileIndex; BlockWidth = width >> 3; BlockHeight = height >> 3; - m_Owner = owner; + _map = owner; if (fileIndex != 0x7F) { - var mapPath = Core.FindDataFile($"map{fileIndex}LegacyMUL.uop", false); + var mapPath = Core.FindDataFile($"map{fileIndex}.mul", false); if (mapPath != null) { - m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - m_MapIndex = new UOPIndex(m_MapStream); + MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } else { - mapPath = Core.FindDataFile($"map{fileIndex}.mul", false, true); + mapPath = Core.FindDataFile($"map{fileIndex}LegacyMUL.uop", false, true); if (mapPath != null) { - m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + _mapIndex = new UOPIndex(MapStream); } } @@ -91,7 +83,7 @@ namespace Server if (indexPath != null) { IndexStream = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - m_IndexReader = new BinaryReader(IndexStream); + IndexReader = new BinaryReader(IndexStream); } var staticsPath = Core.FindDataFile($"statics{fileIndex}.mul", false, true); @@ -102,39 +94,29 @@ namespace Server } } - EmptyStaticBlock = new StaticTile[8][][]; + _emptyStaticBlock = new StaticTile[8][][]; for (var i = 0; i < 8; ++i) { - EmptyStaticBlock[i] = new StaticTile[8][]; + _emptyStaticBlock[i] = new StaticTile[8][]; for (var j = 0; j < 8; ++j) { - EmptyStaticBlock[i][j] = Array.Empty(); + _emptyStaticBlock[i][j] = new StaticTile[0]; } } - m_InvalidLandBlock = new LandTile[196]; + _invalidLandBlock = new LandTile[196]; - m_LandTiles = new LandTile[BlockWidth][][]; - m_StaticTiles = new StaticTile[BlockWidth][][][][]; - m_StaticPatches = new int[BlockWidth][]; - m_LandPatches = new int[BlockWidth][]; + _landTiles = new LandTile[BlockWidth][][]; + _staticTiles = new StaticTile[BlockWidth][][][][]; + _staticPatches = new int[BlockWidth][]; + _landPatches = new int[BlockWidth][]; Patch = new TileMatrixPatch(this, mapID); } - public FileStream IndexStream { get; } - - public FileStream DataStream { get; } - - public TileMatrixPatch Patch { get; } - - public int BlockWidth { get; } - - public int BlockHeight { get; } - - public StaticTile[][][] EmptyStaticBlock { get; } + public StaticTile[][][] EmptyStaticBlock => _emptyStaticBlock; [MethodImpl(MethodImplOptions.Synchronized)] public void SetStaticBlock(int x, int y, StaticTile[][][] value) @@ -144,11 +126,11 @@ namespace Server return; } - m_StaticTiles[x] ??= new StaticTile[BlockHeight][][][]; - m_StaticTiles[x][y] = value; + _staticTiles[x] ??= new StaticTile[BlockHeight][][][]; + _staticTiles[x][y] = value; - m_StaticPatches[x] ??= new int[(BlockHeight + 31) >> 5]; - m_StaticPatches[x][y >> 5] |= 1 << (y & 0x1F); + _staticPatches[x] ??= new int[(BlockHeight + 31) >> 5]; + _staticPatches[x][y >> 5] |= 1 << (y & 0x1F); } [MethodImpl(MethodImplOptions.Synchronized)] @@ -156,89 +138,102 @@ namespace Server { if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || DataStream == null || IndexStream == null) { - return EmptyStaticBlock; + return _emptyStaticBlock; } - m_StaticTiles[x] ??= new StaticTile[BlockHeight][][][]; + _staticTiles[x] ??= new StaticTile[BlockHeight][][][]; - var tiles = m_StaticTiles[x][y]; + var tiles = _staticTiles[x][y]; - if (tiles != null) + if (tiles == null) { - return tiles; - } - - lock (m_FileShare) - { - for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) + lock (_fileShare) { - var shared = m_FileShare[i]; - - lock (shared) + for (var i = 0; tiles == null && i < _fileShare.Count; ++i) { - if (x < shared.BlockWidth && y < shared.BlockHeight) + var shared = _fileShare[i]; + + lock (shared) { - var theirTiles = shared.m_StaticTiles[x]; - - if (theirTiles != null) + if (x < shared.BlockWidth && y < shared.BlockHeight) { - tiles = theirTiles[y]; - } + var theirTiles = shared._staticTiles[x]; - if (tiles != null) - { - var theirBits = shared.m_StaticPatches[x]; - - if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) + if (theirTiles != null) { - tiles = null; + tiles = theirTiles[y]; + } + + if (tiles != null) + { + var theirBits = shared._staticPatches[x]; + + if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) + { + tiles = null; + } } } } } } + + tiles ??= ReadStaticBlock(x, y); + + _staticTiles[x][y] = tiles; } - return m_StaticTiles[x][y] = tiles ?? ReadStaticBlock(x, y); + return tiles; } - public StaticTile[] GetStaticTiles(int x, int y) => GetStaticBlock(x >> 3, y >> 3)[x & 0x7][y & 0x7]; + public StaticTile[] GetStaticTiles(int x, int y) + { + var tiles = GetStaticBlock(x >> 3, y >> 3); + + return tiles[x & 0x7][y & 0x7]; + } + + private readonly TileList m_TilesList = new(); [MethodImpl(MethodImplOptions.Synchronized)] public StaticTile[] GetStaticTiles(int x, int y, bool multis) { var tiles = GetStaticBlock(x >> 3, y >> 3); - if (!multis) + if (multis) { - return tiles[x & 0x7][y & 0x7]; + var eable = _map.GetMultiTilesAt(x, y); + + if (eable == Map.NullEnumerable.Instance) + { + return tiles[x & 0x7][y & 0x7]; + } + + var any = false; + + foreach (var multiTiles in eable) + { + if (!any) + { + any = true; + } + + m_TilesList.AddRange(multiTiles); + } + + eable.Free(); + + if (!any) + { + return tiles[x & 0x7][y & 0x7]; + } + + m_TilesList.AddRange(tiles[x & 0x7][y & 0x7]); + + return m_TilesList.ToArray(); } - var eable = m_Owner.GetMultiTilesAt(x, y); - - if (eable == Map.NullEnumerable.Instance) - { - return tiles[x & 0x7][y & 0x7]; - } - - var any = false; - - foreach (StaticTile[] multiTiles in eable) - { - any = true; - m_TilesList.AddRange(multiTiles); - } - - eable.Free(); - - if (!any) - { - return tiles[x & 0x7][y & 0x7]; - } - - m_TilesList.AddRange(tiles[x & 0x7][y & 0x7]); - - return m_TilesList.ToArray(); + return tiles[x & 0x7][y & 0x7]; } [MethodImpl(MethodImplOptions.Synchronized)] @@ -249,41 +244,41 @@ namespace Server return; } - m_LandTiles[x] ??= new LandTile[BlockHeight][]; - m_LandTiles[x][y] = value; + _landTiles[x] ??= new LandTile[BlockHeight][]; + _landTiles[x][y] = value; - m_LandPatches[x] ??= new int[(BlockHeight + 31) >> 5]; - m_LandPatches[x][y >> 5] |= 1 << (y & 0x1F); + _landPatches[x] ??= new int[(BlockHeight + 31) >> 5]; + _landPatches[x][y >> 5] |= 1 << (y & 0x1F); } [MethodImpl(MethodImplOptions.Synchronized)] public LandTile[] GetLandBlock(int x, int y) { - if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || m_MapStream == null) + if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || MapStream == null) { - return m_InvalidLandBlock; + return _invalidLandBlock; } - m_LandTiles[x] ??= new LandTile[BlockHeight][]; + _landTiles[x] ??= new LandTile[BlockHeight][]; - var tiles = m_LandTiles[x][y]; + var tiles = _landTiles[x][y]; if (tiles != null) { return tiles; } - lock (m_FileShare) + lock (_fileShare) { - for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) + for (var i = 0; tiles == null && i < _fileShare.Count; ++i) { - var shared = m_FileShare[i]; + var shared = _fileShare[i]; lock (shared) { if (x < shared.BlockWidth && y < shared.BlockHeight) { - var theirTiles = shared.m_LandTiles[x]; + var theirTiles = shared._landTiles[x]; if (theirTiles != null) { @@ -292,7 +287,7 @@ namespace Server if (tiles != null) { - var theirBits = shared.m_LandPatches[x]; + var theirBits = shared._landPatches[x]; if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) { @@ -304,24 +299,37 @@ namespace Server } } - return m_LandTiles[x][y] = tiles ?? ReadLandBlock(x, y); + tiles ??= ReadLandBlock(x, y); + + _landTiles[x][y] = tiles; + + return tiles; } - public LandTile GetLandTile(int x, int y) => GetLandBlock(x >> 3, y >> 3)[((y & 0x7) << 3) + (x & 0x7)]; + public LandTile GetLandTile(int x, int y) + { + var tiles = GetLandBlock(x >> 3, y >> 3); + + return tiles[((y & 0x7) << 3) + (x & 0x7)]; + } + + private TileList[][] m_Lists; + + private StaticTile[] m_TileBuffer = new StaticTile[128]; [MethodImpl(MethodImplOptions.Synchronized)] private unsafe StaticTile[][][] ReadStaticBlock(int x, int y) { try { - m_IndexReader.BaseStream.Seek((x * BlockHeight + y) * 12, SeekOrigin.Begin); + IndexReader.BaseStream.Seek((x * BlockHeight + y) * 12, SeekOrigin.Begin); - var lookup = m_IndexReader.ReadInt32(); - var length = m_IndexReader.ReadInt32(); + var lookup = IndexReader.ReadInt32(); + var length = IndexReader.ReadInt32(); if (lookup < 0 || length <= 0) { - return EmptyStaticBlock; + return _emptyStaticBlock; } var count = length / 7; @@ -333,16 +341,11 @@ namespace Server m_TileBuffer = new StaticTile[count]; } - var staTiles = m_TileBuffer; // new StaticTile[tileCount]; + var staTiles = m_TileBuffer; //new StaticTile[tileCount]; fixed (StaticTile* pTiles = staTiles) { - var ptr = DataStream.SafeFileHandle?.DangerousGetHandle(); - if (ptr == null) - { - throw new Exception($"Cannot open {DataStream.Name}"); - } - NativeReader.Read(ptr.Value, pTiles, length); + NativeReader.Read(DataStream, pTiles, length); if (m_Lists == null) { @@ -366,7 +369,8 @@ namespace Server while (pCur < pEnd) { lists[pCur->m_X & 0x7][pCur->m_Y & 0x7].Add(pCur->m_ID, pCur->m_Z); - pCur += 1; + + pCur = pCur + 1; } var tiles = new StaticTile[8][][]; @@ -384,24 +388,29 @@ namespace Server return tiles; } } - catch (EndOfStreamException) + catch (EndOfStreamException ex) { if (DateTime.UtcNow >= m_NextStaticWarning) { - Console.WriteLine("Warning: Static EOS for {0} ({1}, {2})", m_Owner, x, y); + Console.WriteLine("Warning: Static EOS for {0} ({1}, {2})", _map, x, y); m_NextStaticWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); } - return EmptyStaticBlock; + return _emptyStaticBlock; } } + private DateTime m_NextStaticWarning; + private DateTime m_NextLandWarning; + public void Force() { - if ((AssemblyHandler.Assemblies?.Length ?? 0) == 0) + if (AssemblyHandler.Assemblies?.Length > 0) { - throw new Exception(); + return; } + + throw new Exception("No assemblies were loaded, therefore we cannot load TileMatrix."); } [MethodImpl(MethodImplOptions.Synchronized)] @@ -411,45 +420,40 @@ namespace Server { var offset = (x * BlockHeight + y) * 196 + 4; - if (m_MapIndex != null) + if (_mapIndex != null) { - offset = m_MapIndex.Lookup(offset); + offset = _mapIndex.Lookup(offset); } - m_MapStream.Seek(offset, SeekOrigin.Begin); + MapStream.Seek(offset, SeekOrigin.Begin); var tiles = new LandTile[64]; fixed (LandTile* pTiles = tiles) { - var ptr = m_MapStream.SafeFileHandle?.DangerousGetHandle(); - if (ptr == null) - { - throw new Exception($"Cannot open {m_MapStream.Name}"); - } - NativeReader.Read(ptr.Value, pTiles, 192); + NativeReader.Read(MapStream, pTiles, 192); } return tiles; } - catch + catch (Exception ex) { if (DateTime.UtcNow >= m_NextLandWarning) { - Console.WriteLine("Warning: Land EOS for {0} ({1}, {2})", m_Owner, x, y); + Console.WriteLine("Warning: Land EOS for {0} ({1}, {2})", _map, x, y); m_NextLandWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); } - return m_InvalidLandBlock; + return _invalidLandBlock; } } public void Dispose() { - m_MapIndex?.Close(); - m_MapStream?.Close(); + _mapIndex?.Close(); + MapStream?.Close(); DataStream?.Close(); - m_IndexReader?.Close(); + IndexReader?.Close(); } } @@ -461,11 +465,7 @@ namespace Server public int ID => m_ID; - public int Z - { - get => m_Z; - set => m_Z = (sbyte)value; - } + public int Z { get => m_Z; set => m_Z = (sbyte)value; } public int Height => 0; @@ -495,29 +495,13 @@ namespace Server public int ID => m_ID; - public int X - { - get => m_X; - set => m_X = (byte)value; - } + public int X { get => m_X; set => m_X = (byte)value; } - public int Y - { - get => m_Y; - set => m_Y = (byte)value; - } + public int Y { get => m_Y; set => m_Y = (byte)value; } - public int Z - { - get => m_Z; - set => m_Z = (sbyte)value; - } + public int Z { get => m_Z; set => m_Z = (sbyte)value; } - public int Hue - { - get => m_Hue; - set => m_Hue = (short)value; - } + public int Hue { get => m_Hue; set => m_Hue = (short)value; } public int Height => TileData.ItemTable[m_ID & TileData.MaxItemValue].Height; @@ -558,10 +542,37 @@ namespace Server public class UOPIndex { - private readonly UOPEntry[] m_Entries; - private readonly int m_Length; + private class UOPEntry : IComparable + { + public int m_Offset; + public readonly int m_Length; + public int m_Order; + + public UOPEntry(int offset, int length) + { + m_Offset = offset; + m_Length = length; + m_Order = 0; + } + + public int CompareTo(UOPEntry other) + { + return m_Order.CompareTo(other.m_Order); + } + } + + private class OffsetComparer : IComparer + { + public static readonly IComparer Instance = new OffsetComparer(); + + public int Compare(UOPEntry x, UOPEntry y) => x!.m_Offset.CompareTo(y!.m_Offset); + } private readonly BinaryReader m_Reader; + private readonly int m_Length; + private readonly UOPEntry[] m_Entries; + + public int Version { get; } public UOPIndex(FileStream stream) { @@ -603,7 +614,8 @@ namespace Server stream.Seek(18, SeekOrigin.Current); } - } while (nextTable != 0 && nextTable < m_Length); + } + while (nextTable != 0 && nextTable < m_Length); entries.Sort(OffsetComparer.Instance); @@ -622,8 +634,6 @@ namespace Server m_Entries = entries.ToArray(); } - public int Version { get; } - public int Lookup(int offset) { var total = 0; @@ -647,30 +657,5 @@ namespace Server { m_Reader.Close(); } - - private class UOPEntry : IComparable - { - public readonly int m_Length; - public int m_Offset; - public int m_Order; - - public UOPEntry(int offset, int length) - { - m_Offset = offset; - m_Length = length; - m_Order = 0; - } - - public int CompareTo(UOPEntry other) => m_Order.CompareTo(other.m_Order); - } - - private class OffsetComparer : IComparer - { - public static readonly IComparer Instance = new OffsetComparer(); - - public int Compare(UOPEntry x, UOPEntry y) => - x == null ? y == null ? 0 : 1 : - y == null ? -1 : x.m_Offset.CompareTo(y.m_Offset); - } } } diff --git a/Projects/Server/TileMatrix/TileMatrixPatch.cs b/Projects/Server/TileMatrix/TileMatrixPatch.cs index f406b1901..ac6c86443 100644 --- a/Projects/Server/TileMatrix/TileMatrixPatch.cs +++ b/Projects/Server/TileMatrix/TileMatrixPatch.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Runtime.CompilerServices; @@ -6,45 +5,47 @@ namespace Server { public class TileMatrixPatch { - private StaticTile[] m_TileBuffer = new StaticTile[128]; + private StaticTile[] _tileBuffer = new StaticTile[128]; + + public static bool PatchLandEnabled { get; private set; } + public static bool PatchStaticsEnabled { get; private set; } + + public int LandBlocks { get; } + public int StaticBlocks { get; } public TileMatrixPatch(TileMatrix matrix, int index) { - if (!Enabled) + if (PatchLandEnabled) { - return; + var mapDataPath = Core.FindDataFile($"mapdif{index}.mul", false); + var mapIndexPath = Core.FindDataFile($"mapdifl{index}.mul", false); + + if (mapDataPath != null && mapIndexPath != null) + { + LandBlocks = PatchLand(matrix, mapDataPath, mapIndexPath); + } } - var mapDataPath = Core.FindDataFile($"mapdif{index}.mul", false); - var mapIndexPath = Core.FindDataFile($"mapdifl{index}.mul", false); - - if (File.Exists(mapDataPath) && File.Exists(mapIndexPath)) + if (PatchStaticsEnabled) { - LandBlocks = PatchLand(matrix, mapDataPath, mapIndexPath); - } + var staDataPath = Core.FindDataFile($"stadif{index}.mul", false); + var staIndexPath = Core.FindDataFile($"stadifl{index}.mul", false); + var staLookupPath = Core.FindDataFile($"stadifi{index}.mul", false); - var staDataPath = Core.FindDataFile($"stadif{index}.mul", false); - var staIndexPath = Core.FindDataFile($"stadifl{index}.mul", false); - var staLookupPath = Core.FindDataFile($"stadifi{index}.mul", false); - - if (File.Exists(staDataPath) && File.Exists(staIndexPath) && File.Exists(staLookupPath)) - { - StaticBlocks = PatchStatics(matrix, staDataPath, staIndexPath, staLookupPath); + if (staDataPath != null && staIndexPath != null && staLookupPath != null) + { + StaticBlocks = PatchStatics(matrix, staDataPath, staIndexPath, staLookupPath); + } } } - public static bool Enabled { get; set; } - public static void Configure() { // Using this requires the old mapDif files to be present. Only needed to support Clients < 6.0.0.0 - Enabled = ServerConfiguration.GetOrUpdateSetting("maps.enableTileMatrixPatches", !Core.SE); + PatchLandEnabled = ServerConfiguration.GetOrUpdateSetting("maps.enableMapDiffPatches", false); + PatchStaticsEnabled = ServerConfiguration.GetOrUpdateSetting("maps.enableStaticsDiffPatches", false); } - public int LandBlocks { get; } - - public int StaticBlocks { get; } - [MethodImpl(MethodImplOptions.Synchronized)] private unsafe int PatchLand(TileMatrix matrix, string dataPath, string indexPath) { @@ -66,13 +67,7 @@ namespace Server fixed (LandTile* pTiles = tiles) { - var ptr = fsData.SafeFileHandle?.DangerousGetHandle(); - if (ptr == null) - { - throw new Exception($"Cannot open {fsData.Name}"); - } - - NativeReader.Read(ptr.Value, pTiles, 192); + NativeReader.Read(fsData, pTiles, 192); } matrix.SetLandBlock(x, y, tiles); @@ -126,22 +121,16 @@ namespace Server var tileCount = length / 7; - if (m_TileBuffer.Length < tileCount) + if (_tileBuffer.Length < tileCount) { - m_TileBuffer = new StaticTile[tileCount]; + _tileBuffer = new StaticTile[tileCount]; } - var staTiles = m_TileBuffer; + var staTiles = _tileBuffer; fixed (StaticTile* pTiles = staTiles) { - var ptr = fsData.SafeFileHandle?.DangerousGetHandle(); - if (ptr == null) - { - throw new Exception($"Cannot open {fsData.Name}"); - } - - NativeReader.Read(ptr.Value, pTiles, length); + NativeReader.Read(fsData, pTiles, length); StaticTile* pCur = pTiles, pEnd = pTiles + tileCount; diff --git a/Projects/UOContent/Engines/Spawners/GenerateSpawners.cs b/Projects/UOContent/Engines/Spawners/GenerateSpawners.cs index 69de6c662..6619b348c 100644 --- a/Projects/UOContent/Engines/Spawners/GenerateSpawners.cs +++ b/Projects/UOContent/Engines/Spawners/GenerateSpawners.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text.Json; using Server.Json; +using Server.Network; using Server.Utilities; namespace Server.Engines.Spawners @@ -43,6 +44,8 @@ namespace Server.Engines.Spawners var file = files[i]; from.SendMessage("GenerateSpawners: Generating spawners for {0}...", file.Name); + NetState.FlushAll(); + try { var spawners = JsonConfig.Deserialize>(file.FullName); diff --git a/Projects/UOContent/Spells/Base/SpellHelper.cs b/Projects/UOContent/Spells/Base/SpellHelper.cs index c3b342dfb..d78d0e819 100644 --- a/Projects/UOContent/Spells/Base/SpellHelper.cs +++ b/Projects/UOContent/Spells/Base/SpellHelper.cs @@ -259,7 +259,7 @@ namespace Server.Spells return false; } - public static bool CanRevealCaster(Mobile m) => m is BaseCreature c && !c.Controlled; + public static bool CanRevealCaster(Mobile m) => m is BaseCreature { Controlled: false }; public static void GetSurfaceTop(ref IPoint3D p) { diff --git a/version.json b/version.json index 403de5449..629bc3bab 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.8.1" + "version": "0.8.2" }