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.
This commit is contained in:
Kamron Batman 2021-02-14 16:06:49 -08:00 committed by GitHub
parent d070efeab3
commit ea5d09a7d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 724 additions and 482 deletions

1
.gitignore vendored
View file

@ -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

View file

@ -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

View file

@ -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
);

View file

@ -6,12 +6,17 @@ namespace Server.Tests.Network
{
public class MapPatchesTests : IClassFixture<ServerFixture>
{
[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<byte>.Empty : new MapPatches().Compile();
ns.SendMapPatches();
var result = ns.SendPipe.Reader.TryRead();

View file

@ -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)
{

View file

@ -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,

View file

@ -459,6 +459,7 @@ namespace Server
AssemblyHandler.Invoke("Configure");
TileMatrixLoader.LoadTileMatrix();
RegionLoader.LoadRegions();
World.Load();

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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);

View file

@ -13,6 +13,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
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))

View file

@ -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);
}

View file

@ -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());

View file

@ -243,6 +243,15 @@ namespace Server
return list;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfTerminator(this Span<byte> buffer, int sizeT) =>
sizeT switch
{
2 => MemoryMarshal.Cast<byte, char>(buffer).IndexOf((char)0) * 2,
4 => MemoryMarshal.Cast<byte, uint>(buffer).IndexOf((uint)0) * 4,
_ => buffer.IndexOf((byte)0)
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfTerminator(this ReadOnlySpan<byte> buffer, int sizeT) =>
sizeT switch

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<byte>((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<byte>(m_StringBuffer, 0, count));
}
}
}

View file

@ -8,81 +8,73 @@ namespace Server
{
public class TileMatrix
{
private static readonly List<TileMatrix> m_Instances = new();
private static readonly List<TileMatrix> _instances = new();
private readonly int m_FileIndex;
private readonly List<TileMatrix> 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<TileMatrix> _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<StaticTile>();
_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<StaticTile[]>.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<StaticTile[]>.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<UOPEntry>
{
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<UOPEntry>
{
public static readonly IComparer<UOPEntry> 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<UOPEntry>
{
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<UOPEntry>
{
public static readonly IComparer<UOPEntry> 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);
}
}
}

View file

@ -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;

View file

@ -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<List<DynamicJson>>(file.FullName);

View file

@ -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)
{

View file

@ -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"
}