Adds BufferReader and BufferWriter. Updates UOP handling. (#101)
This commit is contained in:
parent
2c0d97cd36
commit
038c3be258
98 changed files with 2458 additions and 2185 deletions
|
|
@ -68,6 +68,7 @@ namespace Server
|
|||
var types = FindTypesByName(name, ignoreCase).ToList();
|
||||
if (types.Count == 0)
|
||||
return null;
|
||||
|
||||
if (predicate != null)
|
||||
return types.FirstOrDefault(predicate);
|
||||
if (types.Count == 1)
|
||||
|
|
@ -76,10 +77,13 @@ namespace Server
|
|||
// Check for exact match of the FullName or Name
|
||||
// Then check for case-insensitive match of FullName or Name
|
||||
// Otherwise just return the first entry
|
||||
return (!ignoreCase ? types.FirstOrDefault(x => x.FullName == name || x.Name == name) : null)
|
||||
?? types.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x.FullName, name) || StringComparer.OrdinalIgnoreCase.Equals(x.Name, name))
|
||||
?? types[0];
|
||||
var stringComparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
||||
|
||||
return types.FirstOrDefault(x =>
|
||||
stringComparer.Equals(x.FullName, name) || stringComparer.Equals(x.Name, name)) ??
|
||||
types[0];
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> FindTypesByName(string name, bool ignoreCase = false)
|
||||
{
|
||||
List<Type> types = new List<Type>();
|
||||
|
|
@ -122,31 +126,30 @@ namespace Server
|
|||
refs.Add(index);
|
||||
else
|
||||
{
|
||||
refs = new HashSet<int>();
|
||||
refs.Add(index);
|
||||
refs = new HashSet<int> {index};
|
||||
nameMap.Add(key, refs);
|
||||
}
|
||||
};
|
||||
Type current;
|
||||
Type aliasType = typeof(TypeAliasAttribute);
|
||||
TypeAliasAttribute alias;
|
||||
for (int i = 0, j = 0; i < m_Types.Length; i++)
|
||||
for (int i = 0; i < m_Types.Length; i++)
|
||||
{
|
||||
current = m_Types[i];
|
||||
addToRefs(i, current.Name);
|
||||
addToRefs(i, current.Name.ToLower());
|
||||
addToRefs(i, current.FullName);
|
||||
addToRefs(i, current.FullName.ToLower());
|
||||
addToRefs(i, current.FullName?.ToLower());
|
||||
alias = current.GetCustomAttribute(aliasType, false) as TypeAliasAttribute;
|
||||
if (alias != null)
|
||||
for (j = 0; j < alias.Aliases.Length; j++)
|
||||
for (int j = 0; j < alias.Aliases.Length; j++)
|
||||
{
|
||||
addToRefs(i, alias.Aliases[j]);
|
||||
addToRefs(i, alias.Aliases[j].ToLower());
|
||||
}
|
||||
}
|
||||
foreach (var entry in nameMap)
|
||||
m_NameMap[entry.Key] = entry.Value.ToArray();
|
||||
foreach (var (key, value) in nameMap)
|
||||
m_NameMap[key] = value.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
314
Projects/Server/Buffers/BufferReader.cs
Normal file
314
Projects/Server/Buffers/BufferReader.cs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
// Copyright (c) Harry Pierson. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
public ref struct BufferReader<T>
|
||||
{
|
||||
private readonly bool usingSequence;
|
||||
private readonly ReadOnlySequence<T> sequence;
|
||||
private SequencePosition currentPosition;
|
||||
private SequencePosition nextPosition;
|
||||
private bool moreData;
|
||||
private readonly long length;
|
||||
|
||||
public BufferReader(ReadOnlySpan<T> span)
|
||||
{
|
||||
usingSequence = false;
|
||||
CurrentSpanIndex = 0;
|
||||
Consumed = 0;
|
||||
sequence = default;
|
||||
currentPosition = default;
|
||||
length = span.Length;
|
||||
|
||||
CurrentSpan = span;
|
||||
nextPosition = default;
|
||||
moreData = span.Length > 0;
|
||||
}
|
||||
|
||||
public BufferReader(in ReadOnlySequence<T> sequence)
|
||||
{
|
||||
usingSequence = true;
|
||||
CurrentSpanIndex = 0;
|
||||
Consumed = 0;
|
||||
this.sequence = sequence;
|
||||
currentPosition = sequence.Start;
|
||||
length = -1;
|
||||
|
||||
var first = sequence.First.Span;
|
||||
CurrentSpan = first;
|
||||
nextPosition = sequence.GetPosition(first.Length);
|
||||
moreData = first.Length > 0;
|
||||
|
||||
if (!moreData && !sequence.IsSingleSegment)
|
||||
{
|
||||
moreData = true;
|
||||
GetNextSpan();
|
||||
}
|
||||
}
|
||||
|
||||
public readonly bool End => !moreData;
|
||||
|
||||
public ReadOnlySpan<T> CurrentSpan { get; private set; }
|
||||
|
||||
public int CurrentSpanIndex { get; private set; }
|
||||
|
||||
public readonly ReadOnlySpan<T> UnreadSpan
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => CurrentSpan.Slice(CurrentSpanIndex);
|
||||
}
|
||||
|
||||
public long Consumed { get; private set; }
|
||||
|
||||
public readonly long Remaining => Length - Consumed;
|
||||
|
||||
public readonly long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (length < 0)
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
// Cast-away readonly to initialize lazy field
|
||||
Volatile.Write(ref Unsafe.AsRef(length), sequence.Length);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryPeek([MaybeNullWhen(false)] out T value)
|
||||
{
|
||||
if (moreData)
|
||||
{
|
||||
value = CurrentSpan[CurrentSpanIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryRead([MaybeNullWhen(false)] out T value)
|
||||
{
|
||||
if (End)
|
||||
{
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = CurrentSpan[CurrentSpanIndex];
|
||||
CurrentSpanIndex++;
|
||||
Consumed++;
|
||||
|
||||
if (CurrentSpanIndex >= CurrentSpan.Length)
|
||||
{
|
||||
if (usingSequence)
|
||||
GetNextSpan();
|
||||
else
|
||||
moreData = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Rewind(long count)
|
||||
{
|
||||
if ((ulong)count > (ulong)Consumed) throw new ArgumentOutOfRangeException(nameof(count));
|
||||
|
||||
Consumed -= count;
|
||||
|
||||
if (CurrentSpanIndex >= count)
|
||||
{
|
||||
CurrentSpanIndex -= (int)count;
|
||||
moreData = true;
|
||||
}
|
||||
else if (usingSequence)
|
||||
{
|
||||
// Current segment doesn't have enough data, scan backward through segments
|
||||
RetreatToPreviousSpan(Consumed);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Rewind went past the start of the memory by {count}.", nameof(count));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void RetreatToPreviousSpan(long consumed)
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
ResetReader();
|
||||
Advance(consumed);
|
||||
}
|
||||
|
||||
private void ResetReader()
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
CurrentSpanIndex = 0;
|
||||
Consumed = 0;
|
||||
currentPosition = sequence.Start;
|
||||
nextPosition = currentPosition;
|
||||
|
||||
if (sequence.TryGet(ref nextPosition, out ReadOnlyMemory<T> memory))
|
||||
{
|
||||
moreData = true;
|
||||
|
||||
if (memory.Length == 0)
|
||||
{
|
||||
CurrentSpan = default;
|
||||
// No data in the first span, move to one with data
|
||||
GetNextSpan();
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentSpan = memory.Span;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No data in any spans and at end of sequence
|
||||
moreData = false;
|
||||
CurrentSpan = default;
|
||||
}
|
||||
}
|
||||
|
||||
private void GetNextSpan()
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
if (!sequence.IsSingleSegment)
|
||||
{
|
||||
SequencePosition previousNextPosition = nextPosition;
|
||||
while (sequence.TryGet(ref nextPosition, out ReadOnlyMemory<T> memory))
|
||||
{
|
||||
currentPosition = previousNextPosition;
|
||||
if (memory.Length > 0)
|
||||
{
|
||||
CurrentSpan = memory.Span;
|
||||
CurrentSpanIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentSpan = default;
|
||||
CurrentSpanIndex = 0;
|
||||
previousNextPosition = nextPosition;
|
||||
}
|
||||
}
|
||||
moreData = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Advance(long count)
|
||||
{
|
||||
const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000);
|
||||
if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count)
|
||||
{
|
||||
CurrentSpanIndex += (int)count;
|
||||
Consumed += count;
|
||||
}
|
||||
else if (usingSequence)
|
||||
{
|
||||
// Can't satisfy from the current span
|
||||
AdvanceToNextSpan(count);
|
||||
}
|
||||
else if (CurrentSpan.Length - CurrentSpanIndex == (int)count)
|
||||
{
|
||||
CurrentSpanIndex += (int)count;
|
||||
Consumed += count;
|
||||
moreData = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
}
|
||||
|
||||
private void AdvanceToNextSpan(long count)
|
||||
{
|
||||
Debug.Assert(usingSequence, "usingSequence");
|
||||
if (count < 0) throw new ArgumentOutOfRangeException(nameof(count));
|
||||
|
||||
Consumed += count;
|
||||
while (moreData)
|
||||
{
|
||||
int remaining = CurrentSpan.Length - CurrentSpanIndex;
|
||||
|
||||
if (remaining > count)
|
||||
{
|
||||
CurrentSpanIndex += (int)count;
|
||||
count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// As there may not be any further segments we need to
|
||||
// push the current index to the end of the span.
|
||||
CurrentSpanIndex += remaining;
|
||||
count -= remaining;
|
||||
Debug.Assert(count >= 0);
|
||||
|
||||
GetNextSpan();
|
||||
|
||||
if (count == 0) break;
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
{
|
||||
// Not enough data left- adjust for where we actually ended and throw
|
||||
Consumed -= count;
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool TryCopyTo(Span<T> destination)
|
||||
{
|
||||
// This API doesn't advance to facilitate conditional advancement based on the data returned.
|
||||
// We don't provide an advance option to allow easier utilizing of stack allocated destination spans.
|
||||
// (Because we can make this method readonly we can guarantee that we won't capture the span.)
|
||||
|
||||
ReadOnlySpan<T> firstSpan = UnreadSpan;
|
||||
if (firstSpan.Length >= destination.Length)
|
||||
{
|
||||
firstSpan.Slice(0, destination.Length).CopyTo(destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not enough in the current span to satisfy the request, fall through to the slow path
|
||||
return TryCopyMultisegment(destination);
|
||||
}
|
||||
|
||||
internal readonly bool TryCopyMultisegment(Span<T> destination)
|
||||
{
|
||||
// If we don't have enough to fill the requested buffer, return false
|
||||
if (Remaining < destination.Length)
|
||||
return false;
|
||||
|
||||
ReadOnlySpan<T> firstSpan = UnreadSpan;
|
||||
Debug.Assert(firstSpan.Length < destination.Length);
|
||||
firstSpan.CopyTo(destination);
|
||||
int copied = firstSpan.Length;
|
||||
|
||||
SequencePosition next = nextPosition;
|
||||
while (sequence.TryGet(ref next, out ReadOnlyMemory<T> nextSegment))
|
||||
if (nextSegment.Length > 0)
|
||||
{
|
||||
ReadOnlySpan<T> nextSpan = nextSegment.Span;
|
||||
int toCopy = Math.Min(nextSpan.Length, destination.Length - copied);
|
||||
nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied));
|
||||
copied += toCopy;
|
||||
if (copied >= destination.Length) break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
181
Projects/Server/Buffers/BufferReaderExtensions.cs
Normal file
181
Projects/Server/Buffers/BufferReaderExtensions.cs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// Copyright (c) Harry Pierson. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
public static class BufferReaderExtensions
|
||||
{
|
||||
private static unsafe bool TryRead<T>(ref this BufferReader<byte> reader, out T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> span = reader.UnreadSpan;
|
||||
if (span.Length < sizeof(T)) return TryReadMultisegment(ref reader, out value);
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
|
||||
reader.Advance(sizeof(T));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static unsafe bool TryReadMultisegment<T>(ref BufferReader<byte> reader, out T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
Debug.Assert(reader.UnreadSpan.Length < sizeof(T), "reader.UnreadSpan.Length < sizeof(T)");
|
||||
|
||||
// Not enough data in the current segment, try to peek for the data we need.
|
||||
T buffer = default;
|
||||
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
|
||||
|
||||
if (!reader.TryCopyTo(tempSpan))
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
|
||||
reader.Advance(sizeof(T));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryRead(ref this BufferReader<byte> reader, out sbyte value)
|
||||
{
|
||||
if (TryRead(ref reader, out byte byteValue))
|
||||
{
|
||||
value = unchecked((sbyte)byteValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out short value) =>
|
||||
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out short value) =>
|
||||
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out short value)
|
||||
{
|
||||
if (reader.TryRead(out value))
|
||||
{
|
||||
value = BinaryPrimitives.ReverseEndianness(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out ushort value)
|
||||
{
|
||||
if (TryReadLittleEndian(ref reader, out short signedvalue))
|
||||
{
|
||||
value = unchecked((ushort)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out ushort value)
|
||||
{
|
||||
if (TryReadBigEndian(ref reader, out short signedvalue))
|
||||
{
|
||||
value = unchecked((ushort)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out int value) =>
|
||||
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out int value) =>
|
||||
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out int value)
|
||||
{
|
||||
if (reader.TryRead(out value))
|
||||
{
|
||||
value = BinaryPrimitives.ReverseEndianness(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out uint value)
|
||||
{
|
||||
if (TryReadLittleEndian(ref reader, out int signedvalue))
|
||||
{
|
||||
value = unchecked((uint)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out uint value)
|
||||
{
|
||||
if (TryReadBigEndian(ref reader, out int signedvalue))
|
||||
{
|
||||
value = unchecked((uint)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out long value) =>
|
||||
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out long value) =>
|
||||
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
|
||||
|
||||
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out long value)
|
||||
{
|
||||
if (reader.TryRead(out value))
|
||||
{
|
||||
value = BinaryPrimitives.ReverseEndianness(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out ulong value)
|
||||
{
|
||||
if (TryReadLittleEndian(ref reader, out long signedvalue))
|
||||
{
|
||||
value = unchecked((ulong)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out ulong value)
|
||||
{
|
||||
if (TryReadBigEndian(ref reader, out long signedvalue))
|
||||
{
|
||||
value = unchecked((ulong)signedvalue);
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
Projects/Server/Buffers/BufferWriter.cs
Normal file
143
Projects/Server/Buffers/BufferWriter.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// A fast access struct that wraps <see cref="IBufferWriter{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of element to be written.</typeparam>
|
||||
internal ref struct BufferWriter<T> where T : IBufferWriter<byte>
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying <see cref="IBufferWriter{T}"/>.
|
||||
/// </summary>
|
||||
private T _output;
|
||||
|
||||
/// <summary>
|
||||
/// The result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)"/>, less any bytes already "consumed" with <see cref="Advance(int)"/>.
|
||||
/// Backing field for the <see cref="Span"/> property.
|
||||
/// </summary>
|
||||
private Span<byte> _span;
|
||||
|
||||
/// <summary>
|
||||
/// The number of uncommitted bytes (all the calls to <see cref="Advance(int)"/> since the last call to <see cref="Commit"/>).
|
||||
/// </summary>
|
||||
private int _buffered;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of bytes written with this writer.
|
||||
/// Backing field for the <see cref="BytesCommitted"/> property.
|
||||
/// </summary>
|
||||
private long _bytesCommitted;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BufferWriter{T}"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="output">The <see cref="IBufferWriter{T}"/> to be wrapped.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public BufferWriter(T output)
|
||||
{
|
||||
_buffered = 0;
|
||||
_bytesCommitted = 0;
|
||||
_output = output;
|
||||
_span = output.GetSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)"/>.
|
||||
/// </summary>
|
||||
public Span<byte> Span => _span;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of bytes written with this writer.
|
||||
/// </summary>
|
||||
public long BytesCommitted => _bytesCommitted;
|
||||
|
||||
/// <summary>
|
||||
/// Calls <see cref="IBufferWriter{T}.Advance(int)"/> on the underlying writer
|
||||
/// with the number of uncommitted bytes.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Commit()
|
||||
{
|
||||
var buffered = _buffered;
|
||||
if (buffered > 0)
|
||||
{
|
||||
_bytesCommitted += buffered;
|
||||
_buffered = 0;
|
||||
_output.Advance(buffered);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to indicate that part of the buffer has been written to.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bytes written to.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Advance(int count)
|
||||
{
|
||||
_buffered += count;
|
||||
_span = _span.Slice(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the caller's buffer into this writer and calls <see cref="Advance(int)"/> with the length of the source buffer.
|
||||
/// </summary>
|
||||
/// <param name="source">The buffer to copy in.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write(ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (_span.Length >= source.Length)
|
||||
{
|
||||
source.CopyTo(_span);
|
||||
Advance(source.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteMultiBuffer(source);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a new buffer if necessary to ensure that some given number of bytes can be written to a single buffer.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of bytes that must be allocated in a single buffer.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Ensure(int count = 1)
|
||||
{
|
||||
if (_span.Length < count) EnsureMore(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a fresh span to write to, with an optional minimum size.
|
||||
/// </summary>
|
||||
/// <param name="count">The minimum size for the next requested buffer.</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void EnsureMore(int count = 0)
|
||||
{
|
||||
if (_buffered > 0) Commit();
|
||||
|
||||
_span = _output.GetSpan(count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the caller's buffer into this writer, potentially across multiple buffers from the underlying writer.
|
||||
/// </summary>
|
||||
/// <param name="source">The buffer to copy into this writer.</param>
|
||||
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
|
||||
{
|
||||
while (source.Length > 0)
|
||||
{
|
||||
if (_span.Length == 0) EnsureMore();
|
||||
|
||||
var writable = Math.Min(source.Length, _span.Length);
|
||||
source.Slice(0, writable).CopyTo(_span);
|
||||
source = source.Slice(writable);
|
||||
Advance(writable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to allocate and distribute re-usable blocks of memory.
|
||||
/// </summary>
|
||||
public class DiagnosticMemoryPool : MemoryPool<byte>
|
||||
{
|
||||
private readonly MemoryPool<byte> _pool;
|
||||
|
||||
private readonly bool _allowLateReturn;
|
||||
|
||||
private readonly bool _rentTracking;
|
||||
|
||||
private readonly object _syncObj;
|
||||
|
||||
private readonly HashSet<DiagnosticPoolBlock> _blocks;
|
||||
|
||||
private readonly List<Exception> _blockAccessExceptions;
|
||||
|
||||
private readonly TaskCompletionSource<object> _allBlocksReturned;
|
||||
|
||||
private int _totalBlocks;
|
||||
|
||||
/// <summary>
|
||||
/// This default value passed in to Rent to use the default value for the pool.
|
||||
/// </summary>
|
||||
private const int AnySize = -1;
|
||||
|
||||
public DiagnosticMemoryPool(MemoryPool<byte> pool, bool allowLateReturn = false, bool rentTracking = false)
|
||||
{
|
||||
_pool = pool;
|
||||
_allowLateReturn = allowLateReturn;
|
||||
_rentTracking = rentTracking;
|
||||
_blocks = new HashSet<DiagnosticPoolBlock>();
|
||||
_syncObj = new object();
|
||||
_allBlocksReturned = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_blockAccessExceptions = new List<Exception>();
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public override IMemoryOwner<byte> Rent(int size = AnySize)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (IsDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool);
|
||||
|
||||
var diagnosticPoolBlock = new DiagnosticPoolBlock(this, _pool.Rent(size));
|
||||
if (_rentTracking) diagnosticPoolBlock.Track();
|
||||
_totalBlocks++;
|
||||
_blocks.Add(diagnosticPoolBlock);
|
||||
return diagnosticPoolBlock;
|
||||
}
|
||||
}
|
||||
|
||||
public override int MaxBufferSize => _pool.MaxBufferSize;
|
||||
|
||||
internal void Return(DiagnosticPoolBlock block)
|
||||
{
|
||||
bool returnedAllBlocks;
|
||||
lock (_syncObj)
|
||||
{
|
||||
_blocks.Remove(block);
|
||||
returnedAllBlocks = _blocks.Count == 0;
|
||||
}
|
||||
|
||||
if (IsDisposed)
|
||||
{
|
||||
if (!_allowLateReturn) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockReturnedToDisposedPool(block);
|
||||
|
||||
if (returnedAllBlocks) SetAllBlocksReturned();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void ReportException(Exception exception)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
_blockAccessExceptions.Add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_DoubleDispose();
|
||||
|
||||
bool allBlocksReturned = false;
|
||||
try
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
IsDisposed = true;
|
||||
allBlocksReturned = _blocks.Count == 0;
|
||||
if (!allBlocksReturned && !_allowLateReturn) MemoryPoolThrowHelper.ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray());
|
||||
|
||||
if (_blockAccessExceptions.Any()) throw CreateAccessExceptions();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (allBlocksReturned) SetAllBlocksReturned();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetAllBlocksReturned()
|
||||
{
|
||||
if (_blockAccessExceptions.Any())
|
||||
_allBlocksReturned.SetException(CreateAccessExceptions());
|
||||
else
|
||||
_allBlocksReturned.SetResult(null);
|
||||
}
|
||||
|
||||
private AggregateException CreateAccessExceptions() => new AggregateException("Exceptions occurred while accessing blocks", _blockAccessExceptions.ToArray());
|
||||
|
||||
public async Task WhenAllBlocksReturnedAsync(TimeSpan timeout)
|
||||
{
|
||||
var task = await Task.WhenAny(_allBlocksReturned.Task, Task.Delay(timeout));
|
||||
if (task != _allBlocksReturned.Task)
|
||||
MemoryPoolThrowHelper.ThrowInvalidOperationException_BlocksWereNotReturnedInTime(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray());
|
||||
|
||||
await task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace System.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
|
||||
/// individual blocks are then treated as independent array segments.
|
||||
/// </summary>
|
||||
public sealed class DiagnosticPoolBlock : MemoryManager<byte>
|
||||
{
|
||||
/// <summary>
|
||||
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
|
||||
/// </summary>
|
||||
private readonly DiagnosticMemoryPool _pool;
|
||||
|
||||
private readonly IMemoryOwner<byte> _memoryOwner;
|
||||
private MemoryHandle? _memoryHandle;
|
||||
private readonly Memory<byte> _memory;
|
||||
|
||||
private readonly object _syncObj = new object();
|
||||
private bool _isDisposed;
|
||||
private int _pinCount;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This object cannot be instantiated outside of the static Create method
|
||||
/// </summary>
|
||||
internal DiagnosticPoolBlock(DiagnosticMemoryPool pool, IMemoryOwner<byte> memoryOwner)
|
||||
{
|
||||
_pool = pool;
|
||||
_memoryOwner = memoryOwner;
|
||||
_memory = memoryOwner.Memory;
|
||||
}
|
||||
|
||||
public override Memory<byte> Memory
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||
|
||||
if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||
|
||||
return CreateMemory(_memory.Length);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_pool.ReportException(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (Volatile.Read(ref _pinCount) > 0) MemoryPoolThrowHelper.ThrowInvalidOperationException_ReturningPinnedBlock(this);
|
||||
|
||||
if (_isDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockDoubleDispose(this);
|
||||
|
||||
_memoryOwner.Dispose();
|
||||
|
||||
_pool.Return(this);
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_pool.ReportException(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override Span<byte> GetSpan()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||
|
||||
if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||
|
||||
return _memory.Span;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_pool.ReportException(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override MemoryHandle Pin(int byteOffset = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||
|
||||
if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||
|
||||
if (byteOffset < 0 || byteOffset > _memory.Length) MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException(_memory.Length, byteOffset);
|
||||
|
||||
_pinCount++;
|
||||
|
||||
_memoryHandle ??= _memory.Pin();
|
||||
|
||||
unsafe
|
||||
{
|
||||
return new MemoryHandle(((IntPtr)_memoryHandle.Value.Pointer + byteOffset).ToPointer(), default, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_pool.ReportException(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryGetArray(out ArraySegment<byte> segment)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
|
||||
|
||||
if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
|
||||
|
||||
return MemoryMarshal.TryGetArray(_memory, out segment);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_pool.ReportException(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Unpin()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (_pinCount == 0) MemoryPoolThrowHelper.ThrowInvalidOperationException_PinCountZero(this);
|
||||
|
||||
_pinCount--;
|
||||
|
||||
if (_pinCount == 0)
|
||||
{
|
||||
Debug.Assert(_memoryHandle.HasValue);
|
||||
_memoryHandle.Value.Dispose();
|
||||
_memoryHandle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_pool.ReportException(exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public StackTrace Leaser { get; set; }
|
||||
|
||||
public void Track()
|
||||
{
|
||||
Leaser = new StackTrace(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using System.Text;
|
|||
|
||||
namespace System.Buffers
|
||||
{
|
||||
public class MemoryPoolThrowHelper
|
||||
public static class MemoryPoolThrowHelper
|
||||
{
|
||||
public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset)
|
||||
{
|
||||
|
|
@ -17,60 +17,6 @@ namespace System.Buffers
|
|||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset) =>
|
||||
(uint)offset > (uint)sourceLength ? new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset)) : new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.length));
|
||||
public static void ThrowInvalidOperationException_PinCountZero(DiagnosticPoolBlock block)
|
||||
{
|
||||
throw new InvalidOperationException(GenerateMessage("Can't unpin, pin count is zero", block));
|
||||
}
|
||||
|
||||
public static void ThrowInvalidOperationException_ReturningPinnedBlock(DiagnosticPoolBlock block)
|
||||
{
|
||||
throw new InvalidOperationException(GenerateMessage("Disposing pinned block", block));
|
||||
}
|
||||
|
||||
public static void ThrowInvalidOperationException_DoubleDispose()
|
||||
{
|
||||
throw new InvalidOperationException("Object is being disposed twice");
|
||||
}
|
||||
|
||||
public static void ThrowInvalidOperationException_BlockDoubleDispose(DiagnosticPoolBlock block)
|
||||
{
|
||||
throw new InvalidOperationException("Block is being disposed twice");
|
||||
}
|
||||
|
||||
public static void ThrowInvalidOperationException_BlockReturnedToDisposedPool(DiagnosticPoolBlock block)
|
||||
{
|
||||
throw new InvalidOperationException(GenerateMessage("Block is being returned to disposed pool", block));
|
||||
}
|
||||
|
||||
public static void ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(DiagnosticPoolBlock block)
|
||||
{
|
||||
throw new InvalidOperationException(GenerateMessage("Block is backed by disposed slab", block));
|
||||
}
|
||||
|
||||
public static void ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(int returned, int total, DiagnosticPoolBlock[] blocks)
|
||||
{
|
||||
throw new InvalidOperationException(GenerateMessage($"Memory pool with active blocks is being disposed, {returned} of {total} returned", blocks));
|
||||
}
|
||||
|
||||
public static void ThrowInvalidOperationException_BlocksWereNotReturnedInTime(int returned, int total, DiagnosticPoolBlock[] blocks)
|
||||
{
|
||||
throw new InvalidOperationException(GenerateMessage($"Blocks were not returned in time, {returned} of {total} returned ", blocks));
|
||||
}
|
||||
|
||||
private static string GenerateMessage(string message, params DiagnosticPoolBlock[] blocks)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(message);
|
||||
foreach (var diagnosticPoolBlock in blocks)
|
||||
if (diagnosticPoolBlock.Leaser != null)
|
||||
{
|
||||
builder.AppendLine();
|
||||
|
||||
builder.AppendLine("Block leased from:");
|
||||
builder.AppendLine(diagnosticPoolBlock.Leaser.ToString());
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -95,6 +95,12 @@ namespace Server
|
|||
}
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<CommandEventArgs> Command;
|
||||
public static void InvokeCommand(CommandEventArgs e) => Command?.Invoke(e);
|
||||
}
|
||||
|
||||
public class CommandEntry : IComparable<CommandEntry>
|
||||
{
|
||||
public CommandEntry(string command, CommandEventHandler handler, AccessLevel accessLevel)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
52
Projects/Server/Events/AccountLoginEvent.cs
Normal file
52
Projects/Server/Events/AccountLoginEvent.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: AccountLoginEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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 Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class AccountLoginEventArgs : EventArgs
|
||||
{
|
||||
public AccountLoginEventArgs(NetState state, string username, string password)
|
||||
{
|
||||
State = state;
|
||||
Username = username;
|
||||
Password = password;
|
||||
}
|
||||
|
||||
public NetState State{ get; }
|
||||
|
||||
public string Username{ get; }
|
||||
|
||||
public string Password{ get; }
|
||||
|
||||
public bool Accepted{ get; set; }
|
||||
|
||||
public ALRReason RejectReason{ get; set; }
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<AccountLoginEventArgs> AccountLogin;
|
||||
public static void InvokeAccountLogin(AccountLoginEventArgs e) => AccountLogin?.Invoke(e);
|
||||
}
|
||||
}
|
||||
75
Projects/Server/Events/AggressiveActionEvent.cs
Normal file
75
Projects/Server/Events/AggressiveActionEvent.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: AggressiveActionEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Collections.Generic;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class AggressiveActionEventArgs : EventArgs
|
||||
{
|
||||
private static Queue<AggressiveActionEventArgs> m_Pool = new Queue<AggressiveActionEventArgs>();
|
||||
|
||||
private AggressiveActionEventArgs(Mobile aggressed, Mobile aggressor, bool criminal)
|
||||
{
|
||||
Aggressed = aggressed;
|
||||
Aggressor = aggressor;
|
||||
Criminal = criminal;
|
||||
}
|
||||
|
||||
public Mobile Aggressed{ get; private set; }
|
||||
|
||||
public Mobile Aggressor{ get; private set; }
|
||||
|
||||
public bool Criminal{ get; private set; }
|
||||
|
||||
public static AggressiveActionEventArgs Create(Mobile aggressed, Mobile aggressor, bool criminal)
|
||||
{
|
||||
AggressiveActionEventArgs args;
|
||||
|
||||
if (m_Pool.Count > 0)
|
||||
{
|
||||
args = m_Pool.Dequeue();
|
||||
|
||||
args.Aggressed = aggressed;
|
||||
args.Aggressor = aggressor;
|
||||
args.Criminal = criminal;
|
||||
}
|
||||
else
|
||||
{
|
||||
args = new AggressiveActionEventArgs(aggressed, aggressor, criminal);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
m_Pool.Enqueue(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<AggressiveActionEventArgs> AggressiveAction;
|
||||
public static void InvokeAggressiveAction(AggressiveActionEventArgs e) => AggressiveAction?.Invoke(e);
|
||||
}
|
||||
}
|
||||
110
Projects/Server/Events/CharacterCreatedEvent.cs
Normal file
110
Projects/Server/Events/CharacterCreatedEvent.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: CharacterCreatedEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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 Server.Accounting;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class CharacterCreatedEventArgs : EventArgs
|
||||
{
|
||||
public CharacterCreatedEventArgs(NetState state, IAccount a, string name, bool female, int hue, int str, int dex,
|
||||
int intel, CityInfo city, SkillNameValue[] skills, int shirtHue, int pantsHue, int hairID, int hairHue,
|
||||
int beardID, int beardHue, int profession, Race race)
|
||||
{
|
||||
State = state;
|
||||
Account = a;
|
||||
Name = name;
|
||||
Female = female;
|
||||
Hue = hue;
|
||||
Str = str;
|
||||
Dex = dex;
|
||||
Int = intel;
|
||||
City = city;
|
||||
Skills = skills;
|
||||
ShirtHue = shirtHue;
|
||||
PantsHue = pantsHue;
|
||||
HairID = hairID;
|
||||
HairHue = hairHue;
|
||||
BeardID = beardID;
|
||||
BeardHue = beardHue;
|
||||
Profession = profession;
|
||||
Race = race;
|
||||
}
|
||||
|
||||
public NetState State{ get; }
|
||||
|
||||
public IAccount Account{ get; }
|
||||
|
||||
public Mobile Mobile{ get; set; }
|
||||
|
||||
public string Name{ get; }
|
||||
|
||||
public bool Female{ get; }
|
||||
|
||||
public int Hue{ get; }
|
||||
|
||||
public int Str{ get; }
|
||||
|
||||
public int Dex{ get; }
|
||||
|
||||
public int Int{ get; }
|
||||
|
||||
public CityInfo City{ get; }
|
||||
|
||||
public SkillNameValue[] Skills{ get; }
|
||||
|
||||
public int ShirtHue{ get; }
|
||||
|
||||
public int PantsHue{ get; }
|
||||
|
||||
public int HairID{ get; }
|
||||
|
||||
public int HairHue{ get; }
|
||||
|
||||
public int BeardID{ get; }
|
||||
|
||||
public int BeardHue{ get; }
|
||||
|
||||
public int Profession{ get; set; }
|
||||
|
||||
public Race Race{ get; }
|
||||
}
|
||||
|
||||
public struct SkillNameValue
|
||||
{
|
||||
public SkillName Name{ get; }
|
||||
|
||||
public int Value{ get; }
|
||||
|
||||
public SkillNameValue(SkillName name, int value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class EventSink {
|
||||
public static event Action<CharacterCreatedEventArgs> CharacterCreated;
|
||||
public static void InvokeCharacterCreated(CharacterCreatedEventArgs e) => CharacterCreated?.Invoke(e);
|
||||
}
|
||||
}
|
||||
41
Projects/Server/Events/CreateGuildEvent.cs
Normal file
41
Projects/Server/Events/CreateGuildEvent.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: CreateGuildEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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 Server.Guilds;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class CreateGuildEventArgs : EventArgs
|
||||
{
|
||||
public CreateGuildEventArgs(uint id) => Id = id;
|
||||
|
||||
public uint Id{ get; set; }
|
||||
|
||||
public BaseGuild Guild{ get; set; }
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<CreateGuildEventArgs> CreateGuild;
|
||||
public static void InvokeCreateGuild(CreateGuildEventArgs e) => CreateGuild?.Invoke(e);
|
||||
}
|
||||
}
|
||||
150
Projects/Server/Events/EventSink.cs
Normal file
150
Projects/Server/Events/EventSink.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: EventSink.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Collections.Generic;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<Mobile> OpenDoorMacroUsed;
|
||||
public static void InvokeOpenDoorMacroUsed(Mobile m) => OpenDoorMacroUsed?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile> Login;
|
||||
public static void InvokeLogin(Mobile m) => Login?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile, int> HungerChanged;
|
||||
public static void InvokeHungerChanged(Mobile mobile, int oldValue) => HungerChanged?.Invoke(mobile, oldValue);
|
||||
|
||||
public static event Action Shutdown;
|
||||
public static void InvokeShutdown() => Shutdown?.Invoke();
|
||||
|
||||
public static event Action<Mobile> HelpRequest;
|
||||
public static void InvokeHelpRequest(Mobile m) => HelpRequest?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile> DisarmRequest;
|
||||
public static void InvokeDisarmRequest(Mobile m) => DisarmRequest?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile> StunRequest;
|
||||
public static void InvokeStunRequest(Mobile m) => StunRequest?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile, int> OpenSpellbookRequest;
|
||||
public static void InvokeOpenSpellbookRequest(Mobile m, int type) => OpenSpellbookRequest?.Invoke(m, type);
|
||||
|
||||
public static event Action<Mobile, int, Item> CastSpellRequest;
|
||||
public static void InvokeCastSpellRequest(Mobile m, int spellID, Item book) => CastSpellRequest?.Invoke(m, spellID, book);
|
||||
|
||||
public static event Action<Mobile, Item, Mobile> BandageTargetRequest;
|
||||
public static void InvokeBandageTargetRequest(Mobile m, Item bandage, Mobile target) => BandageTargetRequest?.Invoke(m, bandage, target);
|
||||
|
||||
public static event Action<Mobile, string> AnimateRequest;
|
||||
public static void InvokeAnimateRequest(Mobile m, string action) => AnimateRequest?.Invoke(m, action);
|
||||
|
||||
public static event Action<Mobile> Logout;
|
||||
public static void InvokeLogout(Mobile m) => Logout?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile> Connected;
|
||||
public static void InvokeConnected(Mobile m) => Connected?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile> Disconnected;
|
||||
public static void InvokeDisconnected(Mobile m) => Disconnected?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile, Mobile, string> RenameRequest;
|
||||
public static void InvokeRenameRequest(Mobile from, Mobile target, string name) => RenameRequest?.Invoke(from, target, name);
|
||||
|
||||
public static event Action<Mobile> PlayerDeath;
|
||||
public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile, Mobile> VirtueGumpRequest;
|
||||
public static void InvokeVirtueGumpRequest(Mobile beholder, Mobile beheld) => VirtueGumpRequest?.Invoke(beholder, beheld);
|
||||
|
||||
public static event Action<Mobile, Mobile, int> VirtueItemRequest;
|
||||
public static void InvokeVirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) => VirtueItemRequest?.Invoke(beholder, beheld, gumpID);
|
||||
|
||||
public static event Action<Mobile, int> VirtueMacroRequest;
|
||||
public static void InvokeVirtueMacroRequest(Mobile mobile, int virtueID) => VirtueMacroRequest?.Invoke(mobile, virtueID);
|
||||
|
||||
public static event Action<Mobile> ChatRequest;
|
||||
public static void InvokeChatRequest(Mobile m) => ChatRequest?.Invoke(m);
|
||||
|
||||
|
||||
public static event Action<Mobile, Mobile> PaperdollRequest;
|
||||
public static void InvokePaperdollRequest(Mobile beholder, Mobile beheld) => PaperdollRequest?.Invoke(beholder ,beheld);
|
||||
|
||||
public static event Action<Mobile, Mobile> ProfileRequest;
|
||||
public static void InvokeProfileRequest(Mobile beholder, Mobile beheld) => ProfileRequest?.Invoke(beholder, beheld);
|
||||
|
||||
public static event Action<Mobile, Mobile, string> ChangeProfileRequest;
|
||||
public static void InvokeChangeProfileRequest(Mobile beholder, Mobile beheld, string text) =>
|
||||
ChangeProfileRequest?.Invoke(beholder, beheld, text);
|
||||
|
||||
|
||||
public static event Action<NetState, int> DeleteRequest;
|
||||
public static void InvokeDeleteRequest(NetState state, int index) => DeleteRequest?.Invoke(state, index);
|
||||
|
||||
public static event Action WorldLoad;
|
||||
public static void InvokeWorldLoad() => WorldLoad?.Invoke();
|
||||
|
||||
public static event Action<bool> WorldSave;
|
||||
public static void InvokeWorldSave(bool sendMessage) => WorldSave?.Invoke(sendMessage);
|
||||
|
||||
public static event Action<Mobile, int> SetAbility;
|
||||
public static void InvokeSetAbility(Mobile mobile, int index) => SetAbility?.Invoke(mobile, index);
|
||||
|
||||
public static event Action ServerStarted;
|
||||
public static void InvokeServerStarted() => ServerStarted?.Invoke();
|
||||
|
||||
public static event Action<Mobile> GuildGumpRequest;
|
||||
public static void InvokeGuildGumpRequest(Mobile m) => GuildGumpRequest?.Invoke(m);
|
||||
|
||||
public static event Action<Mobile> QuestGumpRequest;
|
||||
public static void InvokeQuestGumpRequest(Mobile m) => QuestGumpRequest?.Invoke(m);
|
||||
|
||||
public static event Action<NetState, ClientVersion> ClientVersionReceived;
|
||||
public static void InvokeClientVersionReceived(NetState state, ClientVersion cv) => ClientVersionReceived?.Invoke(state, cv);
|
||||
|
||||
public static event Action<Mobile, List<Serial>> EquipMacro;
|
||||
public static void InvokeEquipMacro(Mobile m, List<Serial> list)
|
||||
{
|
||||
if (list?.Count > 0)
|
||||
EquipMacro?.Invoke(m, list);
|
||||
}
|
||||
|
||||
public static event Action<Mobile, List<Layer>> UnequipMacro;
|
||||
|
||||
public static void InvokeUnequipMacro(Mobile m, List<Layer> layers)
|
||||
{
|
||||
if (layers?.Count > 0)
|
||||
UnequipMacro?.Invoke(m, layers);
|
||||
}
|
||||
|
||||
public static event Action<Mobile, IEntity, int> TargetedSpell;
|
||||
public static void InvokeTargetedSpell(Mobile m, IEntity target, int spellId) => TargetedSpell?.Invoke(m, target, spellId);
|
||||
|
||||
public static event Action<Mobile, IEntity, int> TargetedSkillUse;
|
||||
public static void InvokeTargetedSkillUse(Mobile m, IEntity target, int skillId) => TargetedSkillUse?.Invoke(m, target, skillId);
|
||||
|
||||
public static event Action<Mobile, Item, short> TargetByResourceMacro;
|
||||
public static void InvokeTargetByResourceMacro(Mobile m, Item item, short resourceType) => TargetByResourceMacro?.Invoke(m, item, resourceType);
|
||||
}
|
||||
}
|
||||
45
Projects/Server/Events/FastwalkEvent.cs
Normal file
45
Projects/Server/Events/FastwalkEvent.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: FastWalkEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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 Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class FastWalkEventArgs : EventArgs
|
||||
{
|
||||
public FastWalkEventArgs(NetState state)
|
||||
{
|
||||
NetState = state;
|
||||
Blocked = false;
|
||||
}
|
||||
|
||||
public NetState NetState{ get; }
|
||||
|
||||
public bool Blocked{ get; set; }
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<FastWalkEventArgs> FastWalk;
|
||||
public static void InvokeFastWalk(FastWalkEventArgs e) => FastWalk?.Invoke(e);
|
||||
}
|
||||
}
|
||||
52
Projects/Server/Events/GameLoginEvent.cs
Normal file
52
Projects/Server/Events/GameLoginEvent.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: GameLoginEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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 Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class GameLoginEventArgs : EventArgs
|
||||
{
|
||||
public GameLoginEventArgs(NetState state, string un, string pw)
|
||||
{
|
||||
State = state;
|
||||
Username = un;
|
||||
Password = pw;
|
||||
}
|
||||
|
||||
public NetState State{ get; }
|
||||
|
||||
public string Username{ get; }
|
||||
|
||||
public string Password{ get; }
|
||||
|
||||
public bool Accepted{ get; set; }
|
||||
|
||||
public CityInfo[] CityInfo{ get; set; }
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<GameLoginEventArgs> GameLogin;
|
||||
public static void InvokeGameLogin(GameLoginEventArgs e) => GameLogin?.Invoke(e);
|
||||
}
|
||||
}
|
||||
74
Projects/Server/Events/MovementEvent.cs
Normal file
74
Projects/Server/Events/MovementEvent.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: MovementEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Collections.Generic;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class MovementEventArgs : EventArgs
|
||||
{
|
||||
private static Queue<MovementEventArgs> m_Pool = new Queue<MovementEventArgs>();
|
||||
|
||||
public MovementEventArgs(Mobile mobile, Direction dir)
|
||||
{
|
||||
Mobile = mobile;
|
||||
Direction = dir;
|
||||
}
|
||||
|
||||
public Mobile Mobile{ get; private set; }
|
||||
|
||||
public Direction Direction{ get; private set; }
|
||||
|
||||
public bool Blocked{ get; set; }
|
||||
|
||||
public static MovementEventArgs Create(Mobile mobile, Direction dir)
|
||||
{
|
||||
MovementEventArgs args;
|
||||
|
||||
if (m_Pool.Count > 0)
|
||||
{
|
||||
args = m_Pool.Dequeue();
|
||||
|
||||
args.Mobile = mobile;
|
||||
args.Direction = dir;
|
||||
args.Blocked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
args = new MovementEventArgs(mobile, dir);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
m_Pool.Enqueue(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<MovementEventArgs> Movement;
|
||||
public static void InvokeMovement(MovementEventArgs e) => Movement?.Invoke(e);
|
||||
}
|
||||
}
|
||||
40
Projects/Server/Events/ServerCrashedEvent.cs
Normal file
40
Projects/Server/Events/ServerCrashedEvent.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ServerCrashedEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class ServerCrashedEventArgs : EventArgs
|
||||
{
|
||||
public ServerCrashedEventArgs(Exception e) => Exception = e;
|
||||
|
||||
public Exception Exception{ get; }
|
||||
|
||||
public bool Close{ get; set; }
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<ServerCrashedEventArgs> ServerCrashed;
|
||||
public static void InvokeServerCrashed(ServerCrashedEventArgs e) => ServerCrashed?.Invoke(e);
|
||||
}
|
||||
}
|
||||
63
Projects/Server/Events/ServerListEvent.cs
Normal file
63
Projects/Server/Events/ServerListEvent.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ServerListEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Collections.Generic;
|
||||
using System.Net;
|
||||
using Server.Accounting;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class ServerListEventArgs : EventArgs
|
||||
{
|
||||
public ServerListEventArgs(NetState state, IAccount account)
|
||||
{
|
||||
State = state;
|
||||
Account = account;
|
||||
Servers = new List<ServerInfo>();
|
||||
}
|
||||
|
||||
public NetState State{ get; }
|
||||
|
||||
public IAccount Account{ get; }
|
||||
|
||||
public bool Rejected{ get; set; }
|
||||
|
||||
public List<ServerInfo> Servers{ get; }
|
||||
|
||||
public void AddServer(string name, IPEndPoint address)
|
||||
{
|
||||
AddServer(name, 0, TimeZoneInfo.Local, address);
|
||||
}
|
||||
|
||||
public void AddServer(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address)
|
||||
{
|
||||
Servers.Add(new ServerInfo(name, fullPercent, tz, address));
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<ServerListEventArgs> ServerList;
|
||||
public static void InvokeServerList(ServerListEventArgs e) => ServerList?.Invoke(e);
|
||||
}
|
||||
}
|
||||
45
Projects/Server/Events/SocketConnectionEvent.cs
Normal file
45
Projects/Server/Events/SocketConnectionEvent.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: SocketConnectionEvent.cs *
|
||||
* Created: 2020/04/11 - Updated: 2020/04/11 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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 Microsoft.AspNetCore.Connections;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class SocketConnectEventArgs : EventArgs
|
||||
{
|
||||
public SocketConnectEventArgs(ConnectionContext c)
|
||||
{
|
||||
Context = c;
|
||||
AllowConnection = true;
|
||||
}
|
||||
|
||||
public ConnectionContext Context{ get; }
|
||||
|
||||
public bool AllowConnection{ get; set; }
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<SocketConnectEventArgs> SocketConnect;
|
||||
public static void InvokeSocketConnect(SocketConnectEventArgs e) => SocketConnect?.Invoke(e);
|
||||
}
|
||||
}
|
||||
46
Projects/Server/Events/SpeechEvent.cs
Normal file
46
Projects/Server/Events/SpeechEvent.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class SpeechEventArgs : EventArgs
|
||||
{
|
||||
public SpeechEventArgs(Mobile mobile, string speech, MessageType type, int hue, int[] keywords)
|
||||
{
|
||||
Mobile = mobile;
|
||||
Speech = speech;
|
||||
Type = type;
|
||||
Hue = hue;
|
||||
Keywords = keywords;
|
||||
}
|
||||
|
||||
public Mobile Mobile{ get; }
|
||||
|
||||
public string Speech{ get; set; }
|
||||
|
||||
public MessageType Type{ get; }
|
||||
|
||||
public int Hue{ get; }
|
||||
|
||||
public int[] Keywords{ get; }
|
||||
|
||||
public bool Handled{ get; set; }
|
||||
|
||||
public bool Blocked{ get; set; }
|
||||
|
||||
public bool HasKeyword(int keyword)
|
||||
{
|
||||
for (int i = 0; i < Keywords.Length; ++i)
|
||||
if (Keywords[i] == keyword)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class EventSink
|
||||
{
|
||||
public static event Action<SpeechEventArgs> Speech;
|
||||
public static void InvokeSpeech(SpeechEventArgs e) => Speech?.Invoke(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -251,9 +251,9 @@ namespace Server
|
|||
|
||||
try
|
||||
{
|
||||
CrashedEventArgs args = new CrashedEventArgs(e.ExceptionObject as Exception);
|
||||
ServerCrashedEventArgs args = new ServerCrashedEventArgs(e.ExceptionObject as Exception);
|
||||
|
||||
EventSink.InvokeCrashed(args);
|
||||
EventSink.InvokeServerCrashed(args);
|
||||
|
||||
close = args.Close;
|
||||
}
|
||||
|
|
@ -318,7 +318,7 @@ namespace Server
|
|||
World.WaitForWriteCompletion();
|
||||
|
||||
if (!m_Crashed)
|
||||
EventSink.InvokeShutdown(new ShutdownEventArgs());
|
||||
EventSink.InvokeShutdown();
|
||||
|
||||
Timer.TimerThread.Set();
|
||||
|
||||
|
|
|
|||
|
|
@ -707,7 +707,7 @@ namespace Server
|
|||
{
|
||||
m_Hunger = value;
|
||||
|
||||
EventSink.InvokeHungerChanged(new HungerChangedEventArgs(this, oldValue));
|
||||
EventSink.InvokeHungerChanged(this, oldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1461,7 +1461,7 @@ namespace Server
|
|||
if (m_NetState == null)
|
||||
{
|
||||
OnDisconnected();
|
||||
EventSink.InvokeDisconnected(new DisconnectedEventArgs(this));
|
||||
EventSink.InvokeDisconnected(this);
|
||||
|
||||
// Disconnected, start the logout timer
|
||||
|
||||
|
|
@ -1476,7 +1476,7 @@ namespace Server
|
|||
else
|
||||
{
|
||||
OnConnected();
|
||||
EventSink.InvokeConnected(new ConnectedEventArgs(this));
|
||||
EventSink.InvokeConnected(this);
|
||||
|
||||
// Connected, stop the logout timer and if needed, move to the world
|
||||
|
||||
|
|
@ -1818,16 +1818,7 @@ namespace Server
|
|||
|
||||
public virtual bool KeepsItemsOnDeath => m_AccessLevel > AccessLevel.Player;
|
||||
|
||||
public bool HasTrade
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetState != null)
|
||||
return m_NetState.Trades.Count > 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool HasTrade => m_NetState?.Trades.Count > 0;
|
||||
|
||||
public bool NoMoveHS{ get; set; }
|
||||
|
||||
|
|
@ -4394,7 +4385,7 @@ namespace Server
|
|||
Stam = 0;
|
||||
Mana = 0;
|
||||
|
||||
EventSink.InvokePlayerDeath(new PlayerDeathEventArgs(this));
|
||||
EventSink.InvokePlayerDeath(this);
|
||||
|
||||
ProcessDeltaQueue();
|
||||
|
||||
|
|
@ -6456,10 +6447,10 @@ namespace Server
|
|||
return false;
|
||||
|
||||
return this == m || m.m_Map == m_Map &&
|
||||
(!m.Hidden || m_AccessLevel != AccessLevel.Player &&
|
||||
(m_AccessLevel >= m.AccessLevel || m_AccessLevel >= AccessLevel.Administrator)) &&
|
||||
(m.Alive || Core.SE && Skills.SpiritSpeak.Value >= 100.0 || !Alive ||
|
||||
m_AccessLevel > AccessLevel.Player || m.Warmode);
|
||||
(!m.Hidden || m_AccessLevel != AccessLevel.Player &&
|
||||
(m_AccessLevel >= m.AccessLevel || m_AccessLevel >= AccessLevel.Administrator)) &&
|
||||
(m.Alive || Core.SE && Skills.SpiritSpeak.Value >= 100.0 || !Alive ||
|
||||
m_AccessLevel > AccessLevel.Player || m.Warmode);
|
||||
}
|
||||
|
||||
public virtual bool CanBeRenamedBy(Mobile from) => from.AccessLevel >= AccessLevel.GameMaster && from.m_AccessLevel > m_AccessLevel;
|
||||
|
|
@ -7069,7 +7060,7 @@ namespace Server
|
|||
|
||||
public virtual void DisplayPaperdollTo(Mobile to)
|
||||
{
|
||||
EventSink.InvokePaperdollRequest(new PaperdollRequestEventArgs(to, this));
|
||||
EventSink.InvokePaperdollRequest(to, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -7548,7 +7539,7 @@ namespace Server
|
|||
{
|
||||
if (m_Mobile.m_Map != Map.Internal)
|
||||
{
|
||||
EventSink.InvokeLogout(new LogoutEventArgs(m_Mobile));
|
||||
EventSink.InvokeLogout(m_Mobile);
|
||||
|
||||
m_Mobile.LogoutLocation = m_Mobile.m_Location;
|
||||
m_Mobile.LogoutMap = m_Mobile.m_Map;
|
||||
|
|
@ -7725,8 +7716,8 @@ namespace Server
|
|||
m_Callback(from, "");
|
||||
else
|
||||
m_CancelCallback?.Invoke(@from, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Prompt BeginPrompt(PromptCallback callback, PromptCallback cancelCallback)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,91 +19,203 @@
|
|||
***************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public static class MultiData
|
||||
{
|
||||
private static readonly MultiComponentList[] m_Components;
|
||||
public static Dictionary<int, MultiComponentList> Components { get; } = new Dictionary<int, MultiComponentList>();
|
||||
|
||||
private static readonly FileStream m_Index;
|
||||
private static readonly FileStream m_Stream;
|
||||
private static readonly BinaryReader m_IndexReader;
|
||||
private static readonly BinaryReader m_StreamReader;
|
||||
|
||||
private static readonly bool UsingUOPFormat;
|
||||
|
||||
static MultiData()
|
||||
{
|
||||
string multiUOPPath = Core.FindDataFile("MultiCollection.uop");
|
||||
|
||||
if (File.Exists(multiUOPPath))
|
||||
{
|
||||
LoadUOP(multiUOPPath);
|
||||
UsingUOPFormat = true;
|
||||
return;
|
||||
}
|
||||
|
||||
string idxPath = Core.FindDataFile("multi.idx");
|
||||
string mulPath = Core.FindDataFile("multi.mul");
|
||||
|
||||
if (File.Exists(idxPath) && File.Exists(mulPath))
|
||||
{
|
||||
m_Index = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
m_IndexReader = new BinaryReader(m_Index);
|
||||
var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
m_IndexReader = new BinaryReader(idx);
|
||||
|
||||
m_Stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
m_StreamReader = new BinaryReader(m_Stream);
|
||||
|
||||
m_Components = new MultiComponentList[(int)(m_Index.Length / 12)];
|
||||
var stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
m_StreamReader = new BinaryReader(stream);
|
||||
|
||||
string vdPath = Core.FindDataFile("verdata.mul");
|
||||
|
||||
if (File.Exists(vdPath))
|
||||
if (!File.Exists(vdPath)) return;
|
||||
|
||||
using FileStream fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
BinaryReader bin = new BinaryReader(fs);
|
||||
|
||||
int count = bin.ReadInt32();
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
using FileStream fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
BinaryReader bin = new BinaryReader(fs);
|
||||
int file = bin.ReadInt32();
|
||||
int index = bin.ReadInt32();
|
||||
int lookup = bin.ReadInt32();
|
||||
int length = bin.ReadInt32();
|
||||
bin.ReadInt32(); // extra
|
||||
|
||||
int count = bin.ReadInt32();
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (file == 14 && index >= 0 && lookup >= 0 && length > 0)
|
||||
{
|
||||
int file = bin.ReadInt32();
|
||||
int index = bin.ReadInt32();
|
||||
int lookup = bin.ReadInt32();
|
||||
int length = bin.ReadInt32();
|
||||
int extra = bin.ReadInt32();
|
||||
bin.BaseStream.Seek(lookup, SeekOrigin.Begin);
|
||||
|
||||
if (file == 14 && index >= 0 && index < m_Components.Length && lookup >= 0 && length > 0)
|
||||
{
|
||||
bin.BaseStream.Seek(lookup, SeekOrigin.Begin);
|
||||
Components[index] = new MultiComponentList(bin, length / 12);
|
||||
|
||||
m_Components[index] = new MultiComponentList(bin, length / 12);
|
||||
|
||||
bin.BaseStream.Seek(24 + i * 20, SeekOrigin.Begin);
|
||||
}
|
||||
bin.BaseStream.Seek(24 + i * 20, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
bin.Close();
|
||||
}
|
||||
|
||||
bin.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Warning: Multi data files not found");
|
||||
|
||||
m_Components = new MultiComponentList[0];
|
||||
}
|
||||
}
|
||||
|
||||
public static MultiComponentList GetComponents(int multiID)
|
||||
{
|
||||
MultiComponentList mcl;
|
||||
|
||||
if (multiID >= 0 && multiID < m_Components.Length)
|
||||
{
|
||||
mcl = m_Components[multiID];
|
||||
multiID &= 0x3FFF;
|
||||
|
||||
if (mcl == null)
|
||||
m_Components[multiID] = mcl = Load(multiID);
|
||||
}
|
||||
if (Components.ContainsKey(multiID))
|
||||
mcl = Components[multiID];
|
||||
else if (!UsingUOPFormat)
|
||||
Components[multiID] = mcl = Load(multiID);
|
||||
else
|
||||
{
|
||||
mcl = MultiComponentList.Empty;
|
||||
}
|
||||
|
||||
return mcl;
|
||||
}
|
||||
|
||||
public static void LoadUOP(string path)
|
||||
{
|
||||
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
BinaryReader streamReader = new BinaryReader(stream);
|
||||
|
||||
// Head Information Start
|
||||
if (streamReader.ReadInt32() != 0x0050594D) // Not a UOP Files
|
||||
return;
|
||||
|
||||
if (streamReader.ReadInt32() > 5) // Bad Version
|
||||
return;
|
||||
|
||||
// Multi ID List Array Start
|
||||
UOPHash.BuildChunkIDs(out var chunkIds);
|
||||
// Multi ID List Array End
|
||||
|
||||
streamReader.ReadUInt32(); // format timestamp? 0xFD23EC43
|
||||
long startAddress = streamReader.ReadInt64();
|
||||
|
||||
streamReader.ReadInt32();
|
||||
streamReader.ReadInt32();
|
||||
|
||||
stream.Seek(startAddress, SeekOrigin.Begin); // Head Information End
|
||||
|
||||
long nextBlock;
|
||||
|
||||
do
|
||||
{
|
||||
int blockFileCount = streamReader.ReadInt32();
|
||||
nextBlock = streamReader.ReadInt64();
|
||||
|
||||
int index = 0;
|
||||
|
||||
do
|
||||
{
|
||||
long offset = streamReader.ReadInt64();
|
||||
|
||||
int headerSize = streamReader.ReadInt32(); // header length
|
||||
int compressedSize = streamReader.ReadInt32(); // compressed size
|
||||
int decompressedSize = streamReader.ReadInt32(); // decompressed size
|
||||
|
||||
ulong filehash = streamReader.ReadUInt64(); // filename hash (HashLittle2)
|
||||
streamReader.ReadUInt32();
|
||||
short compressionMethod = streamReader.ReadInt16(); // compression method (0 = none, 1 = zlib)
|
||||
|
||||
index++;
|
||||
|
||||
if (offset == 0 || decompressedSize == 0 || filehash == 0x126D1E99DDEDEE0A) // Exclude housing.bin
|
||||
continue;
|
||||
|
||||
chunkIds.TryGetValue(filehash, out var chunkID);
|
||||
|
||||
long position = stream.Position; // save current position
|
||||
|
||||
stream.Seek(offset + headerSize, SeekOrigin.Begin);
|
||||
|
||||
Span<byte> sourceData = new byte[compressedSize];
|
||||
|
||||
if (stream.Read(sourceData) != compressedSize)
|
||||
continue;
|
||||
|
||||
Span<byte> data;
|
||||
|
||||
if (compressionMethod == 1)
|
||||
{
|
||||
data = new byte[decompressedSize];
|
||||
Compression.Unpack(data, ref decompressedSize, sourceData, compressedSize);
|
||||
}
|
||||
else
|
||||
data = sourceData;
|
||||
|
||||
var tileList = new List<MultiTileEntry>();
|
||||
|
||||
// Skip the first 4 bytes
|
||||
BufferReader<byte> reader = new BufferReader<byte>(data);
|
||||
|
||||
reader.Advance(4); // ???
|
||||
reader.TryReadLittleEndian(out uint count);
|
||||
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
reader.TryReadLittleEndian(out ushort itemid);
|
||||
reader.TryReadLittleEndian(out short x);
|
||||
reader.TryReadLittleEndian(out short y);
|
||||
reader.TryReadLittleEndian(out short z);
|
||||
reader.TryReadLittleEndian(out ushort flagValue);
|
||||
|
||||
TileFlag tileFlag = flagValue switch
|
||||
{
|
||||
1 => TileFlag.None,
|
||||
257 => TileFlag.Generic,
|
||||
_ => TileFlag.Background // 0
|
||||
};
|
||||
|
||||
reader.TryReadLittleEndian(out uint clilocsCount);
|
||||
reader.Advance(clilocsCount * 4); // bypass binary block
|
||||
|
||||
tileList.Add(new MultiTileEntry(itemid, x, y, z, tileFlag));
|
||||
}
|
||||
|
||||
Components[chunkID] = new MultiComponentList(tileList);
|
||||
|
||||
stream.Seek(position, SeekOrigin.Begin); // back to position
|
||||
}
|
||||
while (index < blockFileCount);
|
||||
}
|
||||
while (stream.Seek(nextBlock, SeekOrigin.Begin) != 0);
|
||||
}
|
||||
|
||||
// TODO: Change this to read the file all during load time
|
||||
public static MultiComponentList Load(int multiID)
|
||||
{
|
||||
try
|
||||
|
|
@ -131,9 +243,9 @@ namespace Server
|
|||
{
|
||||
public ushort m_ItemID;
|
||||
public short m_OffsetX, m_OffsetY, m_OffsetZ;
|
||||
public int m_Flags;
|
||||
public TileFlag m_Flags;
|
||||
|
||||
public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, int flags)
|
||||
public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, TileFlag flags)
|
||||
{
|
||||
m_ItemID = itemID;
|
||||
m_OffsetX = xOffset;
|
||||
|
|
@ -205,7 +317,7 @@ namespace Server
|
|||
allTiles[i].m_OffsetX = reader.ReadShort();
|
||||
allTiles[i].m_OffsetY = reader.ReadShort();
|
||||
allTiles[i].m_OffsetZ = reader.ReadShort();
|
||||
allTiles[i].m_Flags = reader.ReadInt();
|
||||
allTiles[i].m_Flags = (TileFlag)reader.ReadInt();
|
||||
}
|
||||
else
|
||||
for (int i = 0; i < length; ++i)
|
||||
|
|
@ -214,7 +326,7 @@ namespace Server
|
|||
allTiles[i].m_OffsetX = reader.ReadShort();
|
||||
allTiles[i].m_OffsetY = reader.ReadShort();
|
||||
allTiles[i].m_OffsetZ = reader.ReadShort();
|
||||
allTiles[i].m_Flags = reader.ReadInt();
|
||||
allTiles[i].m_Flags = (TileFlag)reader.ReadInt();
|
||||
}
|
||||
|
||||
TileList[][] tiles = new TileList[Width][];
|
||||
|
|
@ -253,10 +365,11 @@ namespace Server
|
|||
allTiles[i].m_OffsetX = reader.ReadInt16();
|
||||
allTiles[i].m_OffsetY = reader.ReadInt16();
|
||||
allTiles[i].m_OffsetZ = reader.ReadInt16();
|
||||
allTiles[i].m_Flags = reader.ReadInt32();
|
||||
|
||||
if (PostHSFormat)
|
||||
reader.ReadInt32(); // ??
|
||||
allTiles[i].m_Flags = (TileFlag)reader.ReadUInt64();
|
||||
else
|
||||
allTiles[i].m_Flags = (TileFlag)reader.ReadUInt32();
|
||||
|
||||
MultiTileEntry e = allTiles[i];
|
||||
|
||||
|
|
@ -306,6 +419,63 @@ namespace Server
|
|||
Tiles[x][y] = tiles[x][y].ToArray();
|
||||
}
|
||||
|
||||
public MultiComponentList(List<MultiTileEntry> list)
|
||||
{
|
||||
var allTiles = List = new MultiTileEntry[list.Count];
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
allTiles[i].m_ItemID = list[i].m_ItemID;
|
||||
allTiles[i].m_OffsetX = list[i].m_OffsetX;
|
||||
allTiles[i].m_OffsetY = list[i].m_OffsetY;
|
||||
allTiles[i].m_OffsetZ = list[i].m_OffsetZ;
|
||||
|
||||
allTiles[i].m_Flags = list[i].m_Flags;
|
||||
|
||||
MultiTileEntry e = allTiles[i];
|
||||
|
||||
if (i == 0 || e.m_Flags != 0)
|
||||
{
|
||||
if (e.m_OffsetX < m_Min.m_X) m_Min.m_X = e.m_OffsetX;
|
||||
|
||||
if (e.m_OffsetY < m_Min.m_Y) m_Min.m_Y = e.m_OffsetY;
|
||||
|
||||
if (e.m_OffsetX > m_Max.m_X) m_Max.m_X = e.m_OffsetX;
|
||||
|
||||
if (e.m_OffsetY > m_Max.m_Y) m_Max.m_Y = e.m_OffsetY;
|
||||
}
|
||||
}
|
||||
|
||||
Center = new Point2D(-m_Min.m_X, -m_Min.m_Y);
|
||||
Width = (m_Max.m_X - m_Min.m_X) + 1;
|
||||
Height = (m_Max.m_Y - m_Min.m_Y) + 1;
|
||||
|
||||
var tiles = new TileList[Width][];
|
||||
Tiles = new StaticTile[Width][][];
|
||||
|
||||
for (int x = 0; x < Width; ++x)
|
||||
{
|
||||
tiles[x] = new TileList[Height];
|
||||
Tiles[x] = new StaticTile[Height][];
|
||||
|
||||
for (int y = 0; y < Height; ++y) tiles[x][y] = new TileList();
|
||||
}
|
||||
|
||||
for (int i = 0; i < allTiles.Length; ++i)
|
||||
if (i == 0 || allTiles[i].m_Flags != 0)
|
||||
{
|
||||
int xOffset = allTiles[i].m_OffsetX + Center.m_X;
|
||||
int yOffset = allTiles[i].m_OffsetY + Center.m_Y;
|
||||
int itemID = ((allTiles[i].m_ItemID & TileData.MaxItemValue) | 0x10000);
|
||||
|
||||
tiles[xOffset][yOffset].Add((ushort)itemID, (sbyte)allTiles[i].m_OffsetZ);
|
||||
}
|
||||
|
||||
for (int x = 0; x < Width; ++x)
|
||||
for (int y = 0; y < Height; ++y)
|
||||
Tiles[x][y] = tiles[x][y].ToArray();
|
||||
}
|
||||
|
||||
private MultiComponentList()
|
||||
{
|
||||
Tiles = new StaticTile[0][][];
|
||||
|
|
@ -368,7 +538,7 @@ namespace Server
|
|||
for (int i = 0; i < oldList.Length; ++i)
|
||||
newList[i] = oldList[i];
|
||||
|
||||
newList[oldList.Length] = new MultiTileEntry((ushort)itemID, (short)x, (short)y, (short)z, 1);
|
||||
newList[oldList.Length] = new MultiTileEntry((ushort)itemID, (short)x, (short)y, (short)z, TileFlag.Background);
|
||||
|
||||
List = newList;
|
||||
|
||||
|
|
@ -552,7 +722,7 @@ namespace Server
|
|||
if (vy > m_Max.m_Y)
|
||||
m_Max.m_Y = vy;
|
||||
|
||||
List[index++] = new MultiTileEntry((ushort)tile.ID, (short)vx, (short)vy, (short)tile.Z, 1);
|
||||
List[index++] = new MultiTileEntry((ushort)tile.ID, (short)vx, (short)vy, (short)tile.Z, TileFlag.Background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -578,8 +748,86 @@ namespace Server
|
|||
writer.Write(ent.m_OffsetX);
|
||||
writer.Write(ent.m_OffsetY);
|
||||
writer.Write(ent.m_OffsetZ);
|
||||
writer.Write(ent.m_Flags);
|
||||
writer.Write((int)ent.m_Flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UOPHash
|
||||
{
|
||||
public static void BuildChunkIDs(out Dictionary<ulong, int> chunkIds)
|
||||
{
|
||||
const int maxId = 0x10000;
|
||||
|
||||
chunkIds = new Dictionary<ulong, int>();
|
||||
|
||||
for (int i = 0; i < maxId; ++i)
|
||||
chunkIds[HashLittle2($"build/multicollection/{i:000000}.bin")] = i;
|
||||
}
|
||||
|
||||
private static ulong HashLittle2(string s)
|
||||
{
|
||||
int length = s.Length;
|
||||
|
||||
uint b, c;
|
||||
uint a = b = c = 0xDEADBEEF + (uint)length;
|
||||
|
||||
int k = 0;
|
||||
|
||||
while (length > 12)
|
||||
{
|
||||
a += s[k];
|
||||
a += (uint)s[k + 1] << 8;
|
||||
a += (uint)s[k + 2] << 16;
|
||||
a += (uint)s[k + 3] << 24;
|
||||
b += s[k + 4];
|
||||
b += (uint)s[k + 5] << 8;
|
||||
b += (uint)s[k + 6] << 16;
|
||||
b += (uint)s[k + 7] << 24;
|
||||
c += s[k + 8];
|
||||
c += (uint)s[k + 9] << 8;
|
||||
c += (uint)s[k + 10] << 16;
|
||||
c += (uint)s[k + 11] << 24;
|
||||
|
||||
a -= c; a ^= (c << 4) | (c >> 28); c += b;
|
||||
b -= a; b ^= (a << 6) | (a >> 26); a += c;
|
||||
c -= b; c ^= (b << 8) | (b >> 24); b += a;
|
||||
a -= c; a ^= (c << 16) | (c >> 16); c += b;
|
||||
b -= a; b ^= (a << 19) | (a >> 13); a += c;
|
||||
c -= b; c ^= (b << 4) | (b >> 28); b += a;
|
||||
|
||||
length -= 12;
|
||||
k += 12;
|
||||
}
|
||||
|
||||
if (length != 0)
|
||||
{
|
||||
switch (length)
|
||||
{
|
||||
case 12: c += (uint)s[k + 11] << 24; goto case 11;
|
||||
case 11: c += (uint)s[k + 10] << 16; goto case 10;
|
||||
case 10: c += (uint)s[k + 9] << 8; goto case 9;
|
||||
case 9: c += s[k + 8]; goto case 8;
|
||||
case 8: b += (uint)s[k + 7] << 24; goto case 7;
|
||||
case 7: b += (uint)s[k + 6] << 16; goto case 6;
|
||||
case 6: b += (uint)s[k + 5] << 8; goto case 5;
|
||||
case 5: b += s[k + 4]; goto case 4;
|
||||
case 4: a += (uint)s[k + 3] << 24; goto case 3;
|
||||
case 3: a += (uint)s[k + 2] << 16; goto case 2;
|
||||
case 2: a += (uint)s[k + 1] << 8; goto case 1;
|
||||
case 1: a += s[k]; break;
|
||||
}
|
||||
|
||||
c ^= b; c -= (b << 14) | (b >> 18);
|
||||
a ^= c; a -= (c << 11) | (c >> 21);
|
||||
b ^= a; b -= (a << 25) | (a >> 7);
|
||||
c ^= b; c -= (b << 16) | (b >> 16);
|
||||
a ^= c; a -= (c << 4) | (c >> 28);
|
||||
b ^= a; b -= (a << 14) | (a >> 18);
|
||||
c ^= b; c -= (b << 24) | (b >> 8);
|
||||
}
|
||||
|
||||
return ((ulong)b << 32) | c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
|
||||
/***************************************************************************
|
||||
* MessagePump.cs
|
||||
* -------------------
|
||||
* begin : May 1, 2002
|
||||
* copyright : (C) The RunUO Software Team
|
||||
* email : info@runuo.com
|
||||
*
|
||||
* $Id$
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
***************************************************************************/
|
||||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: MessagePumpService.cs *
|
||||
* Created: 2020/04/12 - Updated: 2020/04/12 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
|
|
|
|||
|
|
@ -285,7 +285,6 @@ namespace Server.Network
|
|||
|
||||
public static int MenuCap { get; set; } = 512;
|
||||
|
||||
|
||||
public void WriteConsole(string text)
|
||||
{
|
||||
Console.WriteLine("Client: {0}: {1}", this, text);
|
||||
|
|
@ -395,8 +394,8 @@ namespace Server.Network
|
|||
public IAccount Account { get; set; }
|
||||
|
||||
public override string ToString() => m_ToString;
|
||||
|
||||
public NetState(ConnectionContext connection)
|
||||
|
||||
{
|
||||
Connection = connection;
|
||||
Seeded = false;
|
||||
|
|
|
|||
|
|
@ -157,6 +157,8 @@ namespace Server.Network
|
|||
Register(0xD7, 0, true, EncodedCommand);
|
||||
Register(0xE1, 0, false, ClientType);
|
||||
Register(0xEF, 21, false, LoginServerSeed);
|
||||
Register(0xEC, 0, false, EquipMacro);
|
||||
Register(0xED, 0, false, UnequipMacro);
|
||||
Register(0xF4, 0, false, CrashReport);
|
||||
Register(0xF8, 106, false, CreateCharacter70160);
|
||||
Register(0xFB, 2, false, ShowPublicHouseContent);
|
||||
|
|
@ -179,6 +181,9 @@ namespace Server.Network
|
|||
RegisterExtended(0x1C, true, CastSpell);
|
||||
RegisterExtended(0x24, false, UnhandledBF);
|
||||
RegisterExtended(0x2C, true, BandageTarget);
|
||||
RegisterExtended(0x2D, true, TargetedSpell);
|
||||
RegisterExtended(0x2E, true, TargetedSkillUse);
|
||||
RegisterExtended(0x30, true, TargetByResourceMacro);
|
||||
RegisterExtended(0x32, true, ToggleFlying);
|
||||
|
||||
RegisterEncoded(0x19, true, SetAbility);
|
||||
|
|
@ -289,6 +294,8 @@ namespace Server.Network
|
|||
return -1;
|
||||
}
|
||||
|
||||
Console.WriteLine("Processing Packet... {0:X}", packetId);
|
||||
|
||||
if (!ns.Seeded)
|
||||
{
|
||||
if (packetId == 0xEF)
|
||||
|
|
@ -381,17 +388,17 @@ namespace Server.Network
|
|||
|
||||
public static void SetAbility(NetState state, IEntity e, EncodedReader reader)
|
||||
{
|
||||
EventSink.InvokeSetAbility(new SetAbilityEventArgs(state.Mobile, reader.ReadInt32()));
|
||||
EventSink.InvokeSetAbility(state.Mobile, reader.ReadInt32());
|
||||
}
|
||||
|
||||
public static void GuildGumpRequest(NetState state, IEntity e, EncodedReader reader)
|
||||
{
|
||||
EventSink.InvokeGuildGumpRequest(new GuildGumpRequestArgs(state.Mobile));
|
||||
EventSink.InvokeGuildGumpRequest(state.Mobile);
|
||||
}
|
||||
|
||||
public static void QuestGumpRequest(NetState state, IEntity e, EncodedReader reader)
|
||||
{
|
||||
EventSink.InvokeQuestGumpRequest(new QuestGumpRequestArgs(state.Mobile));
|
||||
EventSink.InvokeQuestGumpRequest(state.Mobile);
|
||||
}
|
||||
|
||||
public static void EncodedCommand(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -431,12 +438,12 @@ namespace Server.Network
|
|||
Mobile targ = World.FindMobile(pvSrc.ReadUInt32());
|
||||
|
||||
if (targ != null)
|
||||
EventSink.InvokeRenameRequest(new RenameRequestEventArgs(from, targ, pvSrc.ReadStringSafe()));
|
||||
EventSink.InvokeRenameRequest(from, targ, pvSrc.ReadStringSafe());
|
||||
}
|
||||
|
||||
public static void ChatRequest(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
EventSink.InvokeChatRequest(new ChatRequestEventArgs(state.Mobile));
|
||||
EventSink.InvokeChatRequest(state.Mobile);
|
||||
}
|
||||
|
||||
public static void SecureTrade(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -591,7 +598,7 @@ namespace Server.Network
|
|||
pvSrc.Seek(30, SeekOrigin.Current);
|
||||
int index = pvSrc.ReadInt32();
|
||||
|
||||
EventSink.InvokeDeleteRequest(new DeleteRequestEventArgs(state, index));
|
||||
EventSink.InvokeDeleteRequest(state, index);
|
||||
}
|
||||
|
||||
public static void DeathStatusResponse(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -697,7 +704,7 @@ namespace Server.Network
|
|||
{
|
||||
case 0xC7: // Animate
|
||||
{
|
||||
EventSink.InvokeAnimateRequest(new AnimateRequestEventArgs(m, command));
|
||||
EventSink.InvokeAnimateRequest(m, command);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -715,7 +722,7 @@ namespace Server.Network
|
|||
if (!int.TryParse(command, out int booktype))
|
||||
booktype = 1;
|
||||
|
||||
EventSink.InvokeOpenSpellbookRequest(new OpenSpellbookRequestEventArgs(m, booktype));
|
||||
EventSink.InvokeOpenSpellbookRequest(m, booktype);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -728,14 +735,14 @@ namespace Server.Network
|
|||
int spellID = Utility.ToInt32(split[0]) - 1;
|
||||
uint serial = split.Length > 1 ? Utility.ToUInt32(split[1]) : (uint)Serial.MinusOne;
|
||||
|
||||
EventSink.InvokeCastSpellRequest(new CastSpellRequestEventArgs(m, spellID, World.FindItem(serial)));
|
||||
EventSink.InvokeCastSpellRequest(m, spellID, World.FindItem(serial));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 0x58: // Open door
|
||||
{
|
||||
EventSink.InvokeOpenDoorMacroUsed(new OpenDoorMacroEventArgs(m));
|
||||
EventSink.InvokeOpenDoorMacroUsed(m);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -743,7 +750,7 @@ namespace Server.Network
|
|||
{
|
||||
int spellID = Utility.ToInt32(command) - 1;
|
||||
|
||||
EventSink.InvokeCastSpellRequest(new CastSpellRequestEventArgs(m, spellID, null));
|
||||
EventSink.InvokeCastSpellRequest(m, spellID, null);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -751,7 +758,7 @@ namespace Server.Network
|
|||
{
|
||||
int virtueID = Utility.ToInt32(command) - 1;
|
||||
|
||||
EventSink.InvokeVirtueMacroRequest(new VirtueMacroRequestEventArgs(m, virtueID));
|
||||
EventSink.InvokeVirtueMacroRequest(m, virtueID);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -863,7 +870,7 @@ namespace Server.Network
|
|||
{
|
||||
case 0x00: // display request
|
||||
{
|
||||
EventSink.InvokeProfileRequest(new ProfileRequestEventArgs(beholder, beheld));
|
||||
EventSink.InvokeProfileRequest(beholder, beheld);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -877,7 +884,7 @@ namespace Server.Network
|
|||
|
||||
string text = pvSrc.ReadUnicodeString(length);
|
||||
|
||||
EventSink.InvokeChangeProfileRequest(new ChangeProfileRequestEventArgs(beholder, beheld, text));
|
||||
EventSink.InvokeChangeProfileRequest(beholder, beheld, text);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -1011,7 +1018,7 @@ namespace Server.Network
|
|||
|
||||
public static void HelpRequest(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
EventSink.InvokeHelpRequest(new HelpRequestEventArgs(state.Mobile));
|
||||
EventSink.InvokeHelpRequest(state.Mobile);
|
||||
}
|
||||
|
||||
public static void TargetResponse(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -1216,14 +1223,14 @@ namespace Server.Network
|
|||
Mobile beheld = World.FindMobile(pvSrc.ReadUInt32());
|
||||
|
||||
if (beheld != null)
|
||||
EventSink.InvokeVirtueGumpRequest(new VirtueGumpRequestEventArgs(state.Mobile, beheld));
|
||||
EventSink.InvokeVirtueGumpRequest(state.Mobile, beheld);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mobile beheld = World.FindMobile(serial);
|
||||
|
||||
if (beheld != null)
|
||||
EventSink.InvokeVirtueItemRequest(new VirtueItemRequestEventArgs(state.Mobile, beheld, buttonID));
|
||||
EventSink.InvokeVirtueItemRequest(state.Mobile, beheld, buttonID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1513,7 +1520,7 @@ namespace Server.Network
|
|||
|
||||
int spellID = pvSrc.ReadInt16() - 1;
|
||||
|
||||
EventSink.InvokeCastSpellRequest(new CastSpellRequestEventArgs(from, spellID, spellbook));
|
||||
EventSink.InvokeCastSpellRequest(from, spellID, spellbook);
|
||||
}
|
||||
|
||||
public static void BandageTarget(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -1535,7 +1542,7 @@ namespace Server.Network
|
|||
if (target == null)
|
||||
return;
|
||||
|
||||
EventSink.InvokeBandageTargetRequest(new BandageTargetRequestEventArgs(from, bandage, target));
|
||||
EventSink.InvokeBandageTargetRequest(from, bandage, target);
|
||||
|
||||
from.NextActionTime = Core.TickCount + Mobile.ActionDelay;
|
||||
}
|
||||
|
|
@ -1691,12 +1698,12 @@ namespace Server.Network
|
|||
|
||||
public static void StunRequest(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
EventSink.InvokeStunRequest(new StunRequestEventArgs(state.Mobile));
|
||||
EventSink.InvokeStunRequest(state.Mobile);
|
||||
}
|
||||
|
||||
public static void DisarmRequest(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
EventSink.InvokeDisarmRequest(new DisarmRequestEventArgs(state.Mobile));
|
||||
EventSink.InvokeDisarmRequest(state.Mobile);
|
||||
}
|
||||
|
||||
public static void StatLockChange(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -1828,7 +1835,7 @@ namespace Server.Network
|
|||
{
|
||||
CV version = state.Version = new CV(pvSrc.ReadString());
|
||||
|
||||
EventSink.InvokeClientVersionReceived(new ClientVersionReceivedArgs(state, version));
|
||||
EventSink.InvokeClientVersionReceived(state, version);
|
||||
}
|
||||
|
||||
public static void ClientType(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -1838,7 +1845,7 @@ namespace Server.Network
|
|||
int type = pvSrc.ReadUInt16();
|
||||
CV version = state.Version = new CV(pvSrc.ReadString());
|
||||
|
||||
//EventSink.InvokeClientVersionReceived( new ClientVersionReceivedArgs( state, version ) );//todo
|
||||
EventSink.InvokeClientVersionReceived(state, version);
|
||||
}
|
||||
|
||||
public static void MobileQuery(NetState state, PacketReader pvSrc)
|
||||
|
|
@ -2025,7 +2032,7 @@ namespace Server.Network
|
|||
state.Send(SeasonChange.Instantiate(m.GetSeason(), true));
|
||||
state.Send(new MapChange(m));
|
||||
|
||||
EventSink.InvokeLogin(new LoginEventArgs(m));
|
||||
EventSink.InvokeLogin(m);
|
||||
|
||||
m.ClearFastwalkStack();
|
||||
}
|
||||
|
|
@ -2493,6 +2500,47 @@ namespace Server.Network
|
|||
state.Dispose();
|
||||
}
|
||||
|
||||
public static void EquipMacro(NetState ns, PacketReader pvSrc)
|
||||
{
|
||||
int count = pvSrc.ReadByte();
|
||||
List<Serial> serialList = new List<Serial>(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
serialList.Add(pvSrc.ReadUInt32());
|
||||
|
||||
EventSink.InvokeEquipMacro(ns.Mobile, serialList);
|
||||
}
|
||||
|
||||
public static void UnequipMacro(NetState ns, PacketReader pvSrc)
|
||||
{
|
||||
int count = pvSrc.ReadByte();
|
||||
List<Layer> layers = new List<Layer>(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
layers.Add((Layer)pvSrc.ReadUInt16());
|
||||
|
||||
EventSink.InvokeUnequipMacro(ns.Mobile, layers);
|
||||
}
|
||||
|
||||
public static void TargetedSpell(NetState ns, PacketReader pvSrc)
|
||||
{
|
||||
short spellId = (short)(pvSrc.ReadInt16() - 1); // zero based;
|
||||
|
||||
EventSink.InvokeTargetedSpell(ns.Mobile, World.FindEntity(pvSrc.ReadUInt32()), spellId);
|
||||
}
|
||||
|
||||
public static void TargetedSkillUse(NetState ns, PacketReader pvSrc)
|
||||
{
|
||||
short skillId = pvSrc.ReadInt16();
|
||||
|
||||
EventSink.InvokeTargetedSkillUse(ns.Mobile, World.FindEntity(pvSrc.ReadUInt32()), skillId);
|
||||
}
|
||||
|
||||
public static void TargetByResourceMacro(NetState ns, PacketReader pvSrc)
|
||||
{
|
||||
Serial serial = pvSrc.ReadUInt32();
|
||||
|
||||
if (serial.IsItem) EventSink.InvokeTargetByResourceMacro(ns.Mobile, World.FindItem(serial), pvSrc.ReadInt16());
|
||||
}
|
||||
|
||||
private class LoginTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_Mobile;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,24 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ServerConnectionHandler.cs *
|
||||
* Created: 2020/04/12 - Updated: 2020/04/12 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Pipelines;
|
||||
|
|
@ -1,7 +1,27 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: ServerStartup.cs *
|
||||
* Created: 2020/04/12 - Updated: 2020/04/12 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Network
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,24 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: TcpServer.cs *
|
||||
* Created: 2020/04/12 - Updated: 2020/04/12 *
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* 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.Collections.Generic;
|
||||
using System.Net;
|
||||
|
|
|
|||
|
|
@ -49,11 +49,7 @@ namespace Server
|
|||
{
|
||||
PermitBackgroundWrite = permitBackgroundWrite;
|
||||
|
||||
Task.WaitAll(new Task[3] {
|
||||
Task.Factory.StartNew(() => SaveMobiles()),
|
||||
Task.Factory.StartNew(() => SaveItems()),
|
||||
Task.Factory.StartNew(() => SaveGuilds())
|
||||
});
|
||||
Task.WaitAll(Task.Factory.StartNew(SaveMobiles), Task.Factory.StartNew(SaveItems), Task.Factory.StartNew(SaveGuilds));
|
||||
|
||||
if (permitBackgroundWrite && UseSequentialWriters
|
||||
) //If we're permitted to write in the background, but we don't anyways, then notify.
|
||||
|
|
@ -155,7 +151,10 @@ namespace Server
|
|||
foreach (Item item in items.Values)
|
||||
{
|
||||
if (item.Decays && item.Parent == null && item.Map != Map.Internal && item.LastMoved + item.DecayTime <= n)
|
||||
{
|
||||
Console.WriteLine($"Decay Item {item.Name ?? item.DefaultName} ({item.GetType().FullName})");
|
||||
_decayQueue.Enqueue(item);
|
||||
}
|
||||
|
||||
long start = bin.Position;
|
||||
|
||||
|
|
@ -217,7 +216,8 @@ namespace Server
|
|||
{
|
||||
Item item = _decayQueue.Dequeue();
|
||||
|
||||
if (item.OnDecay()) item.Delete();
|
||||
if (item.OnDecay())
|
||||
item.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,116 +183,7 @@ namespace Server
|
|||
{
|
||||
string filePath = Core.FindDataFile("tiledata.mul");
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
BinaryReader bin = new BinaryReader(fs);
|
||||
|
||||
if (fs.Length == 3188736)
|
||||
{
|
||||
// 7.0.9.0
|
||||
LandTable = new LandData[0x4000];
|
||||
|
||||
for (int i = 0; i < 0x4000; ++i)
|
||||
{
|
||||
if (i == 1 || i > 0 && (i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt64();
|
||||
bin.ReadInt16(); // skip 2 bytes -- textureID
|
||||
|
||||
LandTable[i] = new LandData(ReadNameString(bin), flags);
|
||||
}
|
||||
|
||||
ItemTable = new ItemData[0x10000];
|
||||
|
||||
for (int i = 0; i < 0x10000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt64();
|
||||
int weight = bin.ReadByte();
|
||||
int quality = bin.ReadByte();
|
||||
bin.ReadInt16();
|
||||
bin.ReadByte();
|
||||
int quantity = bin.ReadByte();
|
||||
bin.ReadInt32();
|
||||
bin.ReadByte();
|
||||
int value = bin.ReadByte();
|
||||
int height = bin.ReadByte();
|
||||
|
||||
ItemTable[i] = new ItemData(ReadNameString(bin), flags, weight, quality, quantity, value,
|
||||
height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LandTable = new LandData[0x4000];
|
||||
|
||||
for (int i = 0; i < 0x4000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt32();
|
||||
bin.ReadInt16(); // skip 2 bytes -- textureID
|
||||
|
||||
LandTable[i] = new LandData(ReadNameString(bin), flags);
|
||||
}
|
||||
|
||||
if (fs.Length == 1644544)
|
||||
{
|
||||
// 7.0.0.0
|
||||
ItemTable = new ItemData[0x8000];
|
||||
|
||||
for (int i = 0; i < 0x8000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt32();
|
||||
int weight = bin.ReadByte();
|
||||
int quality = bin.ReadByte();
|
||||
bin.ReadInt16();
|
||||
bin.ReadByte();
|
||||
int quantity = bin.ReadByte();
|
||||
bin.ReadInt32();
|
||||
bin.ReadByte();
|
||||
int value = bin.ReadByte();
|
||||
int height = bin.ReadByte();
|
||||
|
||||
ItemTable[i] = new ItemData(ReadNameString(bin), flags, weight, quality, quantity, value,
|
||||
height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemTable = new ItemData[0x4000];
|
||||
|
||||
for (int i = 0; i < 0x4000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt32();
|
||||
int weight = bin.ReadByte();
|
||||
int quality = bin.ReadByte();
|
||||
bin.ReadInt16();
|
||||
bin.ReadByte();
|
||||
int quantity = bin.ReadByte();
|
||||
bin.ReadInt32();
|
||||
bin.ReadByte();
|
||||
int value = bin.ReadByte();
|
||||
int height = bin.ReadByte();
|
||||
|
||||
ItemTable[i] = new ItemData(ReadNameString(bin), flags, weight, quality, quantity, value,
|
||||
height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaxLandValue = LandTable.Length - 1;
|
||||
MaxItemValue = ItemTable.Length - 1;
|
||||
}
|
||||
else
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("tiledata.mul was not found");
|
||||
|
|
@ -302,6 +193,111 @@ namespace Server
|
|||
|
||||
throw new Exception($"TileData: {filePath} not found");
|
||||
}
|
||||
|
||||
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
BinaryReader bin = new BinaryReader(fs);
|
||||
|
||||
if (fs.Length == 3188736)
|
||||
{
|
||||
// 7.0.9.0
|
||||
LandTable = new LandData[0x4000];
|
||||
|
||||
for (int i = 0; i < 0x4000; ++i)
|
||||
{
|
||||
if (i == 1 || i > 0 && (i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt64();
|
||||
bin.ReadInt16(); // skip 2 bytes -- textureID
|
||||
|
||||
LandTable[i] = new LandData(ReadNameString(bin), flags);
|
||||
}
|
||||
|
||||
ItemTable = new ItemData[0x10000];
|
||||
|
||||
for (int i = 0; i < 0x10000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt64();
|
||||
int weight = bin.ReadByte();
|
||||
int quality = bin.ReadByte();
|
||||
bin.ReadInt16();
|
||||
bin.ReadByte();
|
||||
int quantity = bin.ReadByte();
|
||||
bin.ReadInt32();
|
||||
bin.ReadByte();
|
||||
int value = bin.ReadByte();
|
||||
int height = bin.ReadByte();
|
||||
|
||||
ItemTable[i] = new ItemData(ReadNameString(bin), flags, weight, quality, quantity, value,
|
||||
height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LandTable = new LandData[0x4000];
|
||||
|
||||
for (int i = 0; i < 0x4000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt32();
|
||||
bin.ReadInt16(); // skip 2 bytes -- textureID
|
||||
|
||||
LandTable[i] = new LandData(ReadNameString(bin), flags);
|
||||
}
|
||||
|
||||
if (fs.Length == 1644544)
|
||||
{
|
||||
// 7.0.0.0
|
||||
ItemTable = new ItemData[0x8000];
|
||||
|
||||
for (int i = 0; i < 0x8000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt32();
|
||||
int weight = bin.ReadByte();
|
||||
int quality = bin.ReadByte();
|
||||
bin.ReadInt16();
|
||||
bin.ReadByte();
|
||||
int quantity = bin.ReadByte();
|
||||
bin.ReadInt32();
|
||||
bin.ReadByte();
|
||||
int value = bin.ReadByte();
|
||||
int height = bin.ReadByte();
|
||||
|
||||
ItemTable[i] = new ItemData(ReadNameString(bin), flags, weight, quality, quantity, value,
|
||||
height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemTable = new ItemData[0x4000];
|
||||
|
||||
for (int i = 0; i < 0x4000; ++i)
|
||||
{
|
||||
if ((i & 0x1F) == 0) bin.ReadInt32(); // header
|
||||
|
||||
TileFlag flags = (TileFlag)bin.ReadInt32();
|
||||
int weight = bin.ReadByte();
|
||||
int quality = bin.ReadByte();
|
||||
bin.ReadInt16();
|
||||
bin.ReadByte();
|
||||
int quantity = bin.ReadByte();
|
||||
bin.ReadInt32();
|
||||
bin.ReadByte();
|
||||
int value = bin.ReadByte();
|
||||
int height = bin.ReadByte();
|
||||
|
||||
ItemTable[i] = new ItemData(ReadNameString(bin), flags, weight, quality, quantity, value,
|
||||
height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaxLandValue = LandTable.Length - 1;
|
||||
MaxItemValue = ItemTable.Length - 1;
|
||||
}
|
||||
|
||||
public static LandData[] LandTable{ get; }
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ namespace Server
|
|||
private readonly int m_FileIndex;
|
||||
private readonly List<TileMatrix> m_FileShare = new List<TileMatrix>();
|
||||
|
||||
private readonly FileStream m_MapStream;
|
||||
|
||||
public FileStream IndexStream { get; }
|
||||
|
||||
public FileStream DataStream { get; }
|
||||
|
||||
private readonly BinaryReader m_IndexReader;
|
||||
|
||||
private readonly LandTile[] m_InvalidLandBlock;
|
||||
private readonly int[][] m_LandPatches;
|
||||
private readonly LandTile[][][] m_LandTiles;
|
||||
|
|
@ -53,7 +61,6 @@ namespace Server
|
|||
private StaticTile[] m_TileBuffer = new StaticTile[128];
|
||||
|
||||
private readonly TileList m_TilesList = new TileList();
|
||||
// private int m_Width, m_Height;
|
||||
|
||||
public TileMatrix(Map owner, int fileIndex, int mapID, int width, int height)
|
||||
{
|
||||
|
|
@ -78,8 +85,6 @@ namespace Server
|
|||
}
|
||||
|
||||
m_FileIndex = fileIndex;
|
||||
// m_Width = width;
|
||||
// m_Height = height;
|
||||
BlockWidth = width >> 3;
|
||||
BlockHeight = height >> 3;
|
||||
|
||||
|
|
@ -87,21 +92,19 @@ namespace Server
|
|||
|
||||
if (fileIndex != 0x7F)
|
||||
{
|
||||
string mapPath = Core.FindDataFile("map{0}.mul", fileIndex);
|
||||
string mapPath = Core.FindDataFile("map{0}LegacyMUL.uop", fileIndex);
|
||||
|
||||
if (File.Exists(mapPath))
|
||||
{
|
||||
MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
m_MapIndex = new UOPIndex(m_MapStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapPath = Core.FindDataFile("map{0}LegacyMUL.uop", fileIndex);
|
||||
mapPath = Core.FindDataFile("map{0}.mul", fileIndex);
|
||||
|
||||
if (File.Exists(mapPath))
|
||||
{
|
||||
MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
m_MapIndex = new UOPIndex(MapStream);
|
||||
}
|
||||
m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
}
|
||||
|
||||
string indexPath = Core.FindDataFile("staidx{0}.mul", fileIndex);
|
||||
|
|
@ -109,7 +112,7 @@ namespace Server
|
|||
if (File.Exists(indexPath))
|
||||
{
|
||||
IndexStream = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
IndexReader = new BinaryReader(IndexStream);
|
||||
m_IndexReader = new BinaryReader(IndexStream);
|
||||
}
|
||||
|
||||
string staticsPath = Core.FindDataFile("statics{0}.mul", fileIndex);
|
||||
|
|
@ -138,51 +141,12 @@ namespace Server
|
|||
Patch = new TileMatrixPatch(this, mapID);
|
||||
}
|
||||
|
||||
/*public Map Owner
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Owner;
|
||||
}
|
||||
}*/
|
||||
|
||||
public TileMatrixPatch Patch{ get; }
|
||||
|
||||
public int BlockWidth{ get; }
|
||||
|
||||
public int BlockHeight{ get; }
|
||||
|
||||
/*public int Width
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Width;
|
||||
}
|
||||
}
|
||||
|
||||
public int Height
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Height;
|
||||
}
|
||||
}*/
|
||||
|
||||
public FileStream MapStream{ get; set; }
|
||||
|
||||
/*public bool MapUOPPacked
|
||||
{
|
||||
get{ return ( m_MapIndex != null ); }
|
||||
}*/
|
||||
|
||||
public FileStream IndexStream{ get; set; }
|
||||
|
||||
public FileStream DataStream{ get; set; }
|
||||
|
||||
public BinaryReader IndexReader{ get; set; }
|
||||
|
||||
public bool Exists => MapStream != null && IndexStream != null && DataStream != null;
|
||||
|
||||
public StaticTile[][][] EmptyStaticBlock{ get; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
|
|
@ -292,7 +256,7 @@ namespace Server
|
|||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
public LandTile[] GetLandBlock(int x, int y)
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || MapStream == null)
|
||||
if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || m_MapStream == null)
|
||||
return m_InvalidLandBlock;
|
||||
|
||||
m_LandTiles[x] ??= new LandTile[BlockHeight][];
|
||||
|
|
@ -339,10 +303,10 @@ namespace Server
|
|||
{
|
||||
try
|
||||
{
|
||||
IndexReader.BaseStream.Seek((x * BlockHeight + y) * 12, SeekOrigin.Begin);
|
||||
m_IndexReader.BaseStream.Seek((x * BlockHeight + y) * 12, SeekOrigin.Begin);
|
||||
|
||||
int lookup = IndexReader.ReadInt32();
|
||||
int length = IndexReader.ReadInt32();
|
||||
int lookup = m_IndexReader.ReadInt32();
|
||||
int length = m_IndexReader.ReadInt32();
|
||||
|
||||
if (lookup < 0 || length <= 0)
|
||||
return EmptyStaticBlock;
|
||||
|
|
@ -423,13 +387,13 @@ namespace Server
|
|||
if (m_MapIndex != null)
|
||||
offset = m_MapIndex.Lookup(offset);
|
||||
|
||||
MapStream.Seek(offset, SeekOrigin.Begin);
|
||||
m_MapStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
LandTile[] tiles = new LandTile[64];
|
||||
|
||||
fixed (LandTile* pTiles = tiles)
|
||||
{
|
||||
NativeReader.Read(MapStream.SafeFileHandle.DangerousGetHandle(), pTiles, 192);
|
||||
NativeReader.Read(m_MapStream.SafeFileHandle.DangerousGetHandle(), pTiles, 192);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
|
|
@ -448,14 +412,10 @@ namespace Server
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_MapIndex != null)
|
||||
m_MapIndex.Close();
|
||||
else
|
||||
MapStream?.Close();
|
||||
|
||||
m_MapIndex?.Close();
|
||||
m_MapStream?.Close();
|
||||
DataStream?.Close();
|
||||
|
||||
IndexReader?.Close();
|
||||
m_IndexReader?.Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -670,16 +630,8 @@ namespace Server
|
|||
{
|
||||
public static readonly IComparer<UOPEntry> Instance = new OffsetComparer();
|
||||
|
||||
public int Compare(UOPEntry x, UOPEntry y)
|
||||
{
|
||||
if (x == null)
|
||||
return y == null ? 0 : 1;
|
||||
|
||||
if (y == null)
|
||||
return -1;
|
||||
|
||||
return x.m_Offset.CompareTo(y.m_Offset);
|
||||
}
|
||||
public int Compare(UOPEntry x, UOPEntry y) =>
|
||||
x == null ? y == null ? 0 : 1 : y == null ? -1 : x.m_Offset.CompareTo(y.m_Offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
|
|
|
|||
|
|
@ -645,7 +645,7 @@ namespace Server
|
|||
|
||||
try
|
||||
{
|
||||
EventSink.InvokeWorldSave(new WorldSaveEventArgs(message));
|
||||
EventSink.InvokeWorldSave(message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue