Compare commits

..

No commits in common. "master" and "1.0.0" have entirely different histories.

40 changed files with 218 additions and 1001 deletions

View file

@ -1,6 +1,6 @@
# Server Commands
This list was automatically generated on `2026.03.25 14:52:06 UTC` using server version `1.0.0`.
This list was automatically generated on `2026.03.09 15:06:03 UTC` using server version `1.0.0`.
To see an up to date list of all commands, type !commands in the server console or the in-game chat. When invoking a command from in-game your account has to meet the user level requirement for the command.
@ -211,17 +211,16 @@ Region management commands.
## Server
Server management commands.
| Command | Description | User Level | Invoker Type |
| --------------------------------- | ------------------------------------------------- | ---------- | ------------- |
| !server broadcast | Broadcasts a notification to all players. | Admin | Any |
| !server reloadaddg | Reloads the Add G page. | Admin | ServerConsole |
| !server reloadcatalog | Reloads MTX store catalog. | Admin | ServerConsole |
| !server reloaddashboard | Reloads the web dashboard. | Admin | ServerConsole |
| !server reloadlivetuning | Reloads live tuning settings. | Admin | ServerConsole |
| !server reloadplayernameblacklist | Reloads the player name blacklist. | Admin | ServerConsole |
| !server shutdown | Shuts the server down. | Admin | Any |
| !server status | Prints server status. | Any | Any |
| !server whitelist | Enables or disables account whitelist for logins. | Admin | ServerConsole |
| Command | Description | User Level | Invoker Type |
| --------------------------------- | ----------------------------------------- | ---------- | ------------- |
| !server broadcast | Broadcasts a notification to all players. | Admin | Any |
| !server reloadaddg | Reloads the Add G page. | Admin | ServerConsole |
| !server reloadcatalog | Reloads MTX store catalog. | Admin | ServerConsole |
| !server reloaddashboard | Reloads the web dashboard. | Admin | ServerConsole |
| !server reloadlivetuning | Reloads live tuning settings. | Admin | ServerConsole |
| !server reloadplayernameblacklist | Reloads the player name blacklist. | Admin | ServerConsole |
| !server shutdown | Shuts the server down. | Admin | Any |
| !server status | Prints server status. | Any | Any |
## Store
Commands for interacting with the in-game store.

View file

@ -1,59 +1,58 @@
using System.Collections;
using System.Runtime.CompilerServices;
namespace MHServerEmu.Core.Collections
{
public class InvasiveList<T>
{
private readonly Iterator[] _iterators;
private readonly Stack<Iterator> _iteratorPool;
private Iterator _reusableIterator;
private int _numIterators;
public int Id { get; private set; }
public T Head { get; private set; }
public T Head { get; set; }
public T Tail { get; private set; }
public int Count { get; private set; }
public bool IsEmpty { get => Head == null; }
private Iterator[] _iterators;
private int _numIterators;
private int _maxIterators;
public InvasiveList(int maxIterators, int id = 0)
public InvasiveList(int maxIterators)
{
_iterators = new Iterator[maxIterators];
if (maxIterators > 1)
_iteratorPool = new();
_maxIterators = maxIterators;
_iterators = new Iterator[_maxIterators];
}
public InvasiveList(int maxIterators, int id)
{
_maxIterators = maxIterators;
_iterators = new Iterator[_maxIterators];
Id = id;
}
public IEnumerator<T> GetEnumerator()
public IEnumerable<T> Iterate()
{
Iterator iterator;
var iterator = new Iterator(this);
if (_iteratorPool != null)
try
{
if (_iteratorPool.TryPop(out iterator) == false)
iterator = new(this);
while (iterator.End() == false)
{
var element = iterator.Current;
iterator.MoveNext();
yield return element;
}
}
else
finally
{
_reusableIterator ??= new(this);
iterator = _reusableIterator;
UnregisterIterator(iterator);
}
iterator.Initialize();
return iterator;
}
public bool IsEmpty() => Head == null;
public void Remove(T element)
{
if (element == null || Contains(element) == false) return;
ref var node = ref GetInvasiveListNode(element, Id);
if (Unsafe.IsNullRef(ref node)) return;
var node = GetInvasiveListNode(element, Id);
if (node == null) return;
for (int i = 0; i < _numIterators; i++)
{
@ -69,16 +68,16 @@ namespace MHServerEmu.Core.Collections
if (node.Next != null)
{
T nextElement = node.Next;
ref var nextNode = ref GetInvasiveListNode(nextElement, Id);
if (Unsafe.IsNullRef(ref nextNode) == false)
var nextNode = GetInvasiveListNode(nextElement, Id);
if (nextNode != null)
nextNode.Prev = node.Prev;
}
if (node.Prev != null)
{
T prevElement = node.Prev;
ref var prevNode = ref GetInvasiveListNode(prevElement, Id);
if (Unsafe.IsNullRef(ref prevNode) == false)
var prevNode = GetInvasiveListNode(prevElement, Id);
if (prevNode != null)
prevNode.Next = node.Next;
}
@ -94,11 +93,11 @@ namespace MHServerEmu.Core.Collections
if (oldElement == null || Contains(oldElement) == false) return;
if (element == null || Contains(element)) return;
ref var node = ref GetInvasiveListNode(element, Id);
if (Unsafe.IsNullRef(ref node)) return;
var node = GetInvasiveListNode(element, Id);
if (node == null) return;
ref var oldNode = ref GetInvasiveListNode(oldElement, Id);
if (Unsafe.IsNullRef(ref oldNode)) return;
var oldNode = GetInvasiveListNode(oldElement, Id);
if (oldNode == null) return;
var oldPrev = oldNode.Prev;
oldNode.Prev = element;
@ -107,8 +106,8 @@ namespace MHServerEmu.Core.Collections
if (oldPrev != null)
{
ref var oldPrevNode = ref GetInvasiveListNode(oldPrev, Id);
if (Unsafe.IsNullRef(ref oldPrevNode)) return;
var oldPrevNode = GetInvasiveListNode(oldPrev, Id);
if (oldPrevNode == null) return;
oldPrevNode.Next = element;
}
else
@ -121,14 +120,14 @@ namespace MHServerEmu.Core.Collections
{
if (element == null || Contains(element)) return;
ref var node = ref GetInvasiveListNode(element, Id);
if (Unsafe.IsNullRef(ref node)) return;
var node = GetInvasiveListNode(element, Id);
if (node == null) return;
node.Prev = Tail;
if (Tail != null)
{
ref var tailNode = ref GetInvasiveListNode(Tail, Id);
if (Unsafe.IsNullRef(ref tailNode)) return;
var tailNode = GetInvasiveListNode(Tail, Id);
if (tailNode == null) return;
tailNode.Next = element;
}
else
@ -138,23 +137,20 @@ namespace MHServerEmu.Core.Collections
Count++;
}
public virtual ref InvasiveListNode<T> GetInvasiveListNode(T element, int listId)
{
return ref Unsafe.NullRef<InvasiveListNode<T>>();
}
public virtual InvasiveListNode<T> GetInvasiveListNode(T element, int listId) => null;
public bool Contains(T element)
{
if (element == null) return false;
ref var node = ref GetInvasiveListNode(element, Id);
if (Unsafe.IsNullRef(ref node)) return false;
var node = GetInvasiveListNode(element, Id);
if (node == null) return false;
return node.Next != null || node.Prev != null || element.Equals(Head);
}
private void RegisterIterator(Iterator iterator)
{
if (_numIterators >= _iterators.Length)
throw new InvalidOperationException($"Too many iterators '{_iterators.Length}' for invasive list");
if (_numIterators >= _maxIterators)
throw new InvalidOperationException($"Too many iterators '{_maxIterators}' for invasive list");
_iterators[_numIterators++] = iterator;
}
@ -169,75 +165,71 @@ namespace MHServerEmu.Core.Collections
_iterators[_numIterators - 1] = null;
_numIterators--;
// pool iterator instance for reuse
iterator.Reset();
_iteratorPool?.Push(iterator);
return;
}
throw new InvalidOperationException("Iterator not found in iterator collection of invasive list!");
}
public sealed class Iterator : IEnumerator<T>
public class Iterator : IEnumerator<T>
{
private readonly InvasiveList<T> _list;
private bool _start = true;
public T Current { get; private set; }
object IEnumerator.Current { get => Current; }
public bool SkipNext { get; set; } = false;
private InvasiveList<T> _list;
public bool SkipNext { get; set; }
public Iterator(InvasiveList<T> invasiveList)
{
_list = invasiveList;
}
public void Initialize()
{
Current = _list.Head;
SkipNext = false;
_list.RegisterIterator(this);
}
public void Dispose()
{
_list.UnregisterIterator(this);
}
public void Reset()
{
_start = true;
Current = default;
SkipNext = false;
}
public T Current { get; private set; }
object IEnumerator.Current => Current;
public void Dispose() { }
public void Reset() { }
public bool MoveNext()
{
if (_start)
{
Current = _list.Head;
_start = false;
}
else
{
if (SkipNext)
SkipNext = false;
else if (Current != null)
Current = _list.GetInvasiveListNode(Current, _list.Id).Next;
}
if (SkipNext) SkipNext = false;
else if (Current != null)
Current = _list.GetInvasiveListNode(Current, _list.Id).Next;
return Current != null;
return true;
}
public bool End() => Current == null;
}
}
public struct InvasiveListNode<T>
public class InvasiveListNode<T>
{
public T Next;
public T Prev;
public T Next { get; set; }
public T Prev { get; set; }
public void Clear() => Next = Prev = default;
}
public class InvasiveListNodeCollection<T>
{
private readonly InvasiveListNode<T>[] _nodes;
private int _numLists;
public InvasiveListNodeCollection(int numLists)
{
_numLists = numLists;
_nodes = new InvasiveListNode<T>[_numLists];
for (int i = 0; i < _numLists; i++)
_nodes[i] = new();
}
public InvasiveListNode<T> GetInvasiveListNode(int listIndex)
{
if (listIndex >= 0 && listIndex < _numLists)
return _nodes[listIndex];
else
return null;
}
}
}

View file

@ -461,7 +461,7 @@ namespace MHServerEmu.Core.Network
/// <summary>
/// [Game -> PlayerManager] Relays a match region request command from a client.
/// </summary>
public readonly struct MatchRegionRequestQueueCommand(ulong playerDbId, ulong regionProtoId, ulong difficultyTierProtoId, ulong metaStateProtoId, RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId, int teamSizeOverride)
public readonly struct MatchRegionRequestQueueCommand(ulong playerDbId, ulong regionProtoId, ulong difficultyTierProtoId, ulong metaStateProtoId, RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId)
: IGameServiceMessage
{
public readonly ulong PlayerDbId = playerDbId;
@ -471,7 +471,6 @@ namespace MHServerEmu.Core.Network
public readonly RegionRequestQueueCommandVar Command = command;
public readonly ulong RegionRequestGroupId = regionRequestGroupId;
public readonly ulong TargetPlayerDbId = targetPlayerDbId;
public readonly int TeamSizeOverride = teamSizeOverride;
}
// MatchQueueUpdate is based on PlayerMgrToGameServer.proto from 1.53
@ -831,12 +830,6 @@ namespace MHServerEmu.Core.Network
public readonly int ResultCode = resultCode;
}
public readonly struct SetWhitelistEnabled(bool enable)
: IGameServiceMessage
{
public readonly bool Enable = enable;
}
#endregion
}
}

View file

@ -1,5 +1,4 @@
using Google.ProtocolBuffers;
using MHServerEmu.Core.Serialization;
namespace MHServerEmu.Core.Network
{
@ -44,15 +43,5 @@ namespace MHServerEmu.Core.Network
stream.WriteRawVarint32((uint)Message.SerializedSize);
Message.WriteTo(stream);
}
/// <summary>
/// Writes this <see cref="MessagePackageOut"/> to the provided <see cref="ICodedOutputStreamEx"/>.
/// </summary>
public void WriteTo(ICodedOutputStreamEx stream)
{
stream.WriteRawVarint32(Id);
stream.WriteRawVarint32((uint)Message.SerializedSize);
Message.WriteTo(stream);
}
}
}

View file

@ -1,9 +1,10 @@
using System.Collections;
using System.Buffers;
using System.Collections;
using Google.ProtocolBuffers;
using MHServerEmu.Core.Helpers;
using MHServerEmu.Core.Logging;
using MHServerEmu.Core.Memory;
using MHServerEmu.Core.Network.Tcp;
using MHServerEmu.Core.Serialization;
namespace MHServerEmu.Core.Network
{
@ -16,6 +17,7 @@ namespace MHServerEmu.Core.Network
// Packets apparently go as high as 2800+ messages based on logs, so we presize pooled lists to 4096 to fit that and extra.
private static readonly ConcurrentPool<List<MessagePackageOut>> MessageListPool = new(4096, static () => new(4096));
private static readonly ArrayPool<byte> BufferPool = ArrayPool<byte>.Create();
private readonly List<MessagePackageOut> _outboundMessageList = null;
@ -135,10 +137,15 @@ namespace MHServerEmu.Core.Network
if (_outboundMessageList.Count == 0)
return Logger.WarnReturn(false, "SerializeData(): Data packet contains no messages");
using RecyclableCodedOutputStream cos = RecyclableCodedOutputStream.CreateInstance(stream);
// Use pooled buffers for coded output streams with reflection hackery, see ProtobufHelper for more info.
byte[] buffer = BufferPool.Rent(4096);
CodedOutputStream cos = ProtobufHelper.CodedOutputStreamEx.CreateInstance(stream, buffer);
foreach (MessagePackageOut messagePackage in _outboundMessageList)
messagePackage.WriteTo(cos);
cos.Flush();
BufferPool.Return(buffer);
return true;
}

View file

@ -173,7 +173,7 @@ namespace MHServerEmu.Core.Serialization
{
ReadBuffer = new byte[4096];
WriteBuffer = new byte[32]; // We flush after every value, so we can use very small buffer sizes for output (default is 4096).
SharedAutoBuffer = new(65536);
SharedAutoBuffer = new(1024);
}
}

View file

@ -1,18 +0,0 @@
using Google.ProtocolBuffers;
namespace MHServerEmu.Core.Serialization
{
/// <summary>
/// Extended version of <see cref="ICodedOutputStream"/> that exposes additional low level writing functionality.
/// </summary>
public interface ICodedOutputStreamEx : ICodedOutputStream
{
void WriteRawVarint32(uint value);
void WriteRawVarint64(ulong value);
void WriteRawByte(byte value);
void WriteRawBytes(byte[] value);
}
}

View file

@ -1,459 +0,0 @@
using System.Collections;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Google.ProtocolBuffers;
using Google.ProtocolBuffers.Descriptors;
using MHServerEmu.Core.Helpers;
namespace MHServerEmu.Core.Serialization
{
/// <summary>
/// A more memory efficient version of <see cref="CodedOutputStream"/>.
/// </summary>
public sealed class RecyclableCodedOutputStream : ICodedOutputStreamEx, IDisposable
{
private static readonly ConcurrentBag<RecyclableCodedOutputStream> Instances = new();
private readonly byte[] _primaryBuffer = new byte[CodedOutputStream.DefaultBufferSize];
private readonly byte[] _floatBuffer = new byte[sizeof(float)];
private CodedOutputStream _cos;
private RecyclableCodedOutputStream() { }
private void Initialize(Stream stream)
{
_cos = ProtobufHelper.CodedOutputStreamEx.CreateInstance(stream, _primaryBuffer);
}
public static RecyclableCodedOutputStream CreateInstance(Stream stream)
{
if (Instances.TryTake(out RecyclableCodedOutputStream cos) == false)
cos = new();
cos.Initialize(stream);
return cos;
}
#region IDisposable
public void Dispose()
{
if (_cos != null)
{
_cos.Flush();
_cos = null;
}
Instances.Add(this);
}
#endregion
#region ICodedOutputStream
// For most of this we just pass everything to the default implementation.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Flush()
{
_cos.Flush();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteArray(FieldType fieldType, int fieldNumber, string fieldName, IEnumerable list)
{
_cos.WriteArray(fieldType, fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBool(int fieldNumber, string fieldName, bool value)
{
_cos.WriteBool(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBoolArray(int fieldNumber, string fieldName, IEnumerable<bool> list)
{
_cos.WriteBoolArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBytes(int fieldNumber, string fieldName, ByteString value)
{
_cos.WriteBytes(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBytesArray(int fieldNumber, string fieldName, IEnumerable<ByteString> list)
{
_cos.WriteBytesArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteDouble(int fieldNumber, string fieldName, double value)
{
_cos.WriteDouble(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteDoubleArray(int fieldNumber, string fieldName, IEnumerable<double> list)
{
_cos.WriteDoubleArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteEnum(int fieldNumber, string fieldName, int value, object rawValue)
{
_cos.WriteEnum(fieldNumber, fieldName, value, rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteEnumArray<T>(int fieldNumber, string fieldName, IEnumerable<T> list) where T : struct, IComparable, IFormattable
{
_cos.WriteEnumArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteField(FieldType fieldType, int fieldNumber, string fieldName, object value)
{
_cos.WriteField(fieldType, fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFixed32(int fieldNumber, string fieldName, uint value)
{
_cos.WriteFixed32(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFixed32Array(int fieldNumber, string fieldName, IEnumerable<uint> list)
{
_cos.WriteFixed32Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFixed64(int fieldNumber, string fieldName, ulong value)
{
_cos.WriteFixed64(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFixed64Array(int fieldNumber, string fieldName, IEnumerable<ulong> list)
{
_cos.WriteFixed64Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloat(int fieldNumber, string fieldName, float value)
{
//_cos.WriteFloat(fieldNumber, fieldName, value);
_cos.WriteTag(fieldNumber, WireFormat.WireType.Fixed32);
MemoryMarshal.Cast<byte, float>(_floatBuffer)[0] = value;
_cos.WriteRawBytes(_floatBuffer, 0, 4);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteFloatArray(int fieldNumber, string fieldName, IEnumerable<float> list)
{
_cos.WriteFloatArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteGroup(int fieldNumber, string fieldName, IMessageLite value)
{
_cos.WriteGroup(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteGroupArray<T>(int fieldNumber, string fieldName, IEnumerable<T> list) where T : IMessageLite
{
_cos.WriteGroupArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt32(int fieldNumber, string fieldName, int value)
{
_cos.WriteInt32(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt32Array(int fieldNumber, string fieldName, IEnumerable<int> list)
{
_cos.WriteInt32Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt64(int fieldNumber, string fieldName, long value)
{
_cos.WriteInt64(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteInt64Array(int fieldNumber, string fieldName, IEnumerable<long> list)
{
_cos.WriteInt64Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteMessage(int fieldNumber, string fieldName, IMessageLite value)
{
_cos.WriteMessage(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteMessageArray<T>(int fieldNumber, string fieldName, IEnumerable<T> list) where T : IMessageLite
{
_cos.WriteMessageArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteMessageEnd()
{
_cos.Flush();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteMessageSetExtension(int fieldNumber, string fieldName, IMessageLite value)
{
_cos.WriteMessageSetExtension(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteMessageSetExtension(int fieldNumber, string fieldName, ByteString value)
{
_cos.WriteMessageSetExtension(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteMessageStart()
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedArray(FieldType fieldType, int fieldNumber, string fieldName, IEnumerable list)
{
_cos.WritePackedArray(fieldType, fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedBoolArray(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<bool> list)
{
_cos.WritePackedBoolArray(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedDoubleArray(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<double> list)
{
_cos.WritePackedDoubleArray(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedEnumArray<T>(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<T> list) where T : struct, IComparable, IFormattable
{
_cos.WritePackedEnumArray(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedFixed32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<uint> list)
{
_cos.WritePackedFixed32Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedFixed64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<ulong> list)
{
_cos.WritePackedFixed64Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedFloatArray(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<float> list)
{
_cos.WritePackedFloatArray(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedInt32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<int> list)
{
_cos.WritePackedInt32Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedInt64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<long> list)
{
_cos.WritePackedInt64Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedSFixed32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<int> list)
{
_cos.WritePackedSFixed32Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedSFixed64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<long> list)
{
_cos.WritePackedSFixed64Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedSInt32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<int> list)
{
_cos.WritePackedSInt32Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedSInt64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<long> list)
{
_cos.WritePackedSInt64Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedUInt32Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<uint> list)
{
_cos.WritePackedUInt32Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WritePackedUInt64Array(int fieldNumber, string fieldName, int calculatedSize, IEnumerable<ulong> list)
{
_cos.WritePackedUInt64Array(fieldNumber, fieldName, calculatedSize, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSFixed32(int fieldNumber, string fieldName, int value)
{
_cos.WriteSFixed32(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSFixed32Array(int fieldNumber, string fieldName, IEnumerable<int> list)
{
_cos.WriteSFixed32Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSFixed64(int fieldNumber, string fieldName, long value)
{
_cos.WriteSFixed64(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSFixed64Array(int fieldNumber, string fieldName, IEnumerable<long> list)
{
_cos.WriteSFixed64Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSInt32(int fieldNumber, string fieldName, int value)
{
_cos.WriteSInt32(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSInt32Array(int fieldNumber, string fieldName, IEnumerable<int> list)
{
_cos.WriteSInt32Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSInt64(int fieldNumber, string fieldName, long value)
{
_cos.WriteSInt64(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSInt64Array(int fieldNumber, string fieldName, IEnumerable<long> list)
{
_cos.WriteSInt64Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteString(int fieldNumber, string fieldName, string value)
{
_cos.WriteString(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteStringArray(int fieldNumber, string fieldName, IEnumerable<string> list)
{
_cos.WriteStringArray(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUInt32(int fieldNumber, string fieldName, uint value)
{
_cos.WriteUInt32(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUInt32Array(int fieldNumber, string fieldName, IEnumerable<uint> list)
{
_cos.WriteUInt32Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUInt64(int fieldNumber, string fieldName, ulong value)
{
_cos.WriteUInt64(fieldNumber, fieldName, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUInt64Array(int fieldNumber, string fieldName, IEnumerable<ulong> list)
{
_cos.WriteUInt64Array(fieldNumber, fieldName, list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUnknownBytes(int fieldNumber, ByteString value)
{
_cos.WriteUnknownBytes(fieldNumber, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUnknownField(int fieldNumber, WireFormat.WireType wireType, ulong value)
{
_cos.WriteUnknownField(fieldNumber, wireType, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete]
public void WriteUnknownGroup(int fieldNumber, IMessageLite value)
{
_cos.WriteUnknownGroup(fieldNumber, value);
}
#endregion
#region ICodedOutputStreamEx
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteRawVarint32(uint value)
{
_cos.WriteRawVarint32(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteRawVarint64(ulong value)
{
_cos.WriteRawVarint64(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteRawByte(byte value)
{
_cos.WriteRawByte(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteRawBytes(byte[] value)
{
_cos.WriteRawBytes(value);
}
#endregion
}
}

View file

@ -22,7 +22,6 @@ namespace MHServerEmu.DatabaseAccess.Models
IsPasswordExpired = 1 << 2,
// 1 << 3 was previously used in 0.x for the Linux compatibility mode, it should not be set in any 1.x+ databases.
IsWhitelisted = 1 << 4,
BypassLoginQueue = 1 << 5,
}
/// <summary>

View file

@ -68,8 +68,6 @@ namespace MHServerEmu.Games.Achievements
AchievementProgressMap.Clear();
_scoreCached = false;
AchievementProgressMap.EnsureCapacity((int)achievementCount);
for (uint i = 0; i < achievementCount; i++)
{
uint achievementId = 0;

View file

@ -4716,6 +4716,9 @@ namespace MHServerEmu.Games.Entities.Avatars
if (IsInWorld == false)
return 0f;
if (Game.CustomGameOptions.DisableMissionXPBonuses)
return 1f;
TuningPrototype tuningProto = tuningTable.Prototype;
if (tuningProto == null) return Logger.WarnReturn(0f, "GetMissionXPMultiplier(): tuningProto == null");
@ -4728,10 +4731,7 @@ namespace MHServerEmu.Games.Entities.Avatars
float multiplier = pctXPFromLevelDeltaCurve.GetAt(level - CharacterLevel);
multiplier *= tuningProto.PctXPMultiplier;
multiplier *= playerXPByDifficultyIndex.GetAt(tuningTable.DifficultyIndex);
if (Game.CustomGameOptions.DisableMissionXPBonuses == false)
multiplier *= GetAvatarXPMultiplier();
multiplier *= GetAvatarXPMultiplier();
multiplier *= GetPartyXPMultiplier(tuningProto);
multiplier *= GetLiveTuningXPMultiplier();
return multiplier;

View file

@ -106,7 +106,7 @@ namespace MHServerEmu.Games.Entities
private static readonly Logger Logger = LogManager.CreateLogger();
private InlineArray3<InvasiveListNode<Entity>> _entityListNodes;
private readonly InvasiveListNodeCollection<Entity> _entityListNodes = new(3);
private readonly EventGroup _pendingEvents = new();
@ -1307,15 +1307,9 @@ namespace MHServerEmu.Games.Entities
};
}
public ref InvasiveListNode<Entity> GetInvasiveListNode(int listId)
public InvasiveListNode<Entity> GetInvasiveListNode(int listId)
{
// InvasiveListNodeCollection is inlined into this function because of CS8170.
const int NumLists = 3;
if (listId >= 0 && listId < NumLists)
return ref _entityListNodes[listId];
else
return ref Unsafe.NullRef<InvasiveListNode<Entity>>();
return _entityListNodes.GetInvasiveListNode(listId);
}
#endregion

View file

@ -25,13 +25,13 @@ namespace MHServerEmu.Games.Entities
{
Simulated = 0,
Locomotion = 1,
All = 2, // unused?
All = 3,
}
public sealed class EntityInvasiveCollection : InvasiveList<Entity>
public class EntityInvasiveCollection : InvasiveList<Entity>
{
public EntityInvasiveCollection(EntityCollection collectionType, int maxIterators = 8) : base(maxIterators, (int)collectionType) { }
public override ref InvasiveListNode<Entity> GetInvasiveListNode(Entity element, int listId) => ref element.GetInvasiveListNode(listId);
public override InvasiveListNode<Entity> GetInvasiveListNode(Entity element, int listId) => element.GetInvasiveListNode(listId);
}
public readonly struct DestroyEntityEvent(Entity entity) : IGameEventData
@ -449,7 +449,7 @@ namespace MHServerEmu.Games.Entities
public void LocomoteEntities()
{
foreach (var entity in LocomotionEntities)
foreach (var entity in LocomotionEntities.Iterate())
if (entity is WorldEntity worldEntity)
worldEntity?.Locomotor.Locomote();
}
@ -586,7 +586,7 @@ namespace MHServerEmu.Games.Entities
IsAIEnabled = enable;
if (enable)
foreach (var entity in SimulatedEntities)
foreach (var entity in SimulatedEntities.Iterate())
if (entity is Agent agent) agent.AIController?.SetIsEnabled(true);
foreach (var entity in _entityDict.Values)

View file

@ -49,8 +49,6 @@ namespace MHServerEmu.Games.Entities
private int _activePowerTargetCount;
private bool _killSelf;
private Picker<ulong> _targetPicker; // Reusable picker for AppliesIntervalPowers hotspots, remove this if we implement picker pooling.
public Hotspot(Game game) : base(game)
{
SetFlag(EntityFlags.IsHotspot, true);
@ -69,9 +67,6 @@ namespace MHServerEmu.Games.Entities
if (hotspotProto.DirectApplyToMissilesData?.EvalPropertiesToApply != null || hotspotProto.Negatable)
SetFlag(EntityFlags.IsCollidableHotspot, true);
if (hotspotProto.IntervalPowersRandomTarget)
_targetPicker = new(Game.Random);
return true;
}
@ -801,7 +796,7 @@ namespace MHServerEmu.Games.Entities
if (hotspotProto.IntervalPowersRandomTarget)
{
Picker<ulong> picker = _targetPicker;
Picker<ulong> picker = new(Game.Random);
var hasLOS = TriBool.Undefined;
ulong prevTargetId = InvalidId;

View file

@ -15,24 +15,31 @@ namespace MHServerEmu.Games.Entities.Physics
}
}
public sealed class ForceSystemMemberList : InvasiveList<ForceSystemMember>
public class ForceSystemMemberList : InvasiveList<ForceSystemMember>
{
public ForceSystemMemberList(int maxIterators = 1) : base(maxIterators) { }
public override ref InvasiveListNode<ForceSystemMember> GetInvasiveListNode(ForceSystemMember element, int listId) => ref element.InvasiveListNode;
public override InvasiveListNode<ForceSystemMember> GetInvasiveListNode(ForceSystemMember element, int listId) => element.InvasiveListNode;
}
public class ForceSystemMember
{
private InvasiveListNode<ForceSystemMember> _invasiveListNode;
public ulong EntityId { get; set; }
public Vector3 Position { get; set; }
public Vector3 Direction { get; set; }
public float Time { get; set; }
public float Speed { get; set; }
public float Acceleration { get; set; }
public ref InvasiveListNode<ForceSystemMember> InvasiveListNode { get => ref _invasiveListNode; }
public InvasiveListNode<ForceSystemMember> InvasiveListNode { get; private set; }
public ForceSystemMember() { }
public ForceSystemMember()
{
EntityId = 0;
Position = Vector3.Zero;
Direction = Vector3.Zero;
Time = 0.0f;
Speed = 0.0f;
Acceleration = 0.0f;
InvasiveListNode = new();
}
}
}

View file

@ -196,7 +196,7 @@ namespace MHServerEmu.Games.Entities.Physics
EntityManager entityManager = _game.EntityManager;
foreach (var member in forceSystem.Members)
foreach (var member in forceSystem.Members.Iterate())
{
if (member == null) continue;
bool active = false;
@ -684,7 +684,7 @@ namespace MHServerEmu.Games.Entities.Physics
float distanceSq = Vector3.DistanceSquared(epicenter, member.Position);
var pendingMembers = pendingForce.Members;
foreach (var pendingMember in pendingMembers)
foreach (var pendingMember in pendingMembers.Iterate())
if (distanceSq > Vector3.DistanceSquared(epicenter, pendingMember.Position))
{
pendingForce.Members.InsertBefore(member, pendingMember);

View file

@ -2872,7 +2872,7 @@ namespace MHServerEmu.Games.Entities
}
public bool SendRegionRequestQueueCommandToPlayerManager(PrototypeId regionRef, PrototypeId difficultyTierRef,
RegionRequestQueueCommandVar command, ulong groupId = 0, ulong targetPlayerDbId = 0, int teamSizeOverride = -1)
RegionRequestQueueCommandVar command, ulong groupId = 0, ulong targetPlayerDbId = 0)
{
ulong playerDbId = DatabaseUniqueId;
ulong regionProtoId = (ulong)regionRef;
@ -2904,7 +2904,7 @@ namespace MHServerEmu.Games.Entities
break;
}
ServiceMessage.MatchRegionRequestQueueCommand message = new(playerDbId, regionProtoId, difficultyTierProtoId, metaStateProtoId, command, groupId, targetPlayerDbId, teamSizeOverride);
ServiceMessage.MatchRegionRequestQueueCommand message = new(playerDbId, regionProtoId, difficultyTierProtoId, metaStateProtoId, command, groupId, targetPlayerDbId);
ServerManager.Instance.SendMessageToService(GameServiceType.PlayerManager, message);
return true;
@ -3029,12 +3029,11 @@ namespace MHServerEmu.Games.Entities
public bool DiscoverMapPosition(Vector3 position, bool syncWithParty = true)
{
Region region = CurrentAvatar?.Region;
if (region == null)
return false;
var region = CurrentAvatar?.Region;
if (region == null) return Logger.WarnReturn(false, "UpdateMapDiscovery(): region == null");
MapDiscoveryData mapDiscoveryData = GetMapDiscoveryDataForEntity(CurrentAvatar);
if (mapDiscoveryData == null) return Logger.WarnReturn(false, "DiscoverMapPosition(): mapDiscoveryData == null");
if (mapDiscoveryData == null) return Logger.WarnReturn(false, "UpdateDiscoveryMap(): mapDiscoveryData == null");
bool reveal = mapDiscoveryData.RevealPosition(this, position);

View file

@ -15,10 +15,7 @@ namespace MHServerEmu.Games.Events
private static readonly Logger Logger = LogManager.CreateLogger();
#if DEBUG
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
#endif
private readonly ScheduledEventPool _eventPool = new();
private readonly TimeSpan _quantumSize;
@ -198,16 +195,12 @@ namespace MHServerEmu.Games.Events
@event.EventGroupNode.Remove();
@event.InvalidatePointers();
#if DEBUG
TimeSpan referenceTime = _stopwatch.Elapsed;
@event.OnTriggered();
TimeSpan triggerTime = _stopwatch.Elapsed - referenceTime;
if (triggerTime >= _quantumSize)
Logger.Warn($"{@event.GetType().Name} took {(_stopwatch.Elapsed - referenceTime).TotalMilliseconds} ms");
#else
@event.OnTriggered();
#endif
if (++numEvents > MaxEventsPerUpdate)
throw new Exception($"Infinite loop detected in EventScheduler.");

View file

@ -55,7 +55,7 @@ namespace MHServerEmu.Games.Navi
public void Release()
{
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
RemoveTriangle(triangle);
_lastTriangle = null;
@ -126,7 +126,7 @@ namespace MHServerEmu.Games.Navi
using var collinearEdgesHandle = ListPool<NaviEdge>.Instance.Get(out List<NaviEdge> collinearEdges);
using NaviSerialCheck naviSerialCheck = new(this);
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
foreach (var edge in triangle.Edges)
{
if (edge.Triangles[0] == null || edge.Triangles[1] == null) continue;
@ -925,7 +925,7 @@ namespace MHServerEmu.Games.Navi
{
StringBuilder hashes = new();
int id = 0;
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
hashes.AppendLine($"[{id++}] {triangle.ToHashString()}");
FileHelper.SaveTextFileToRoot(fileName, hashes.ToString());
}
@ -934,7 +934,7 @@ namespace MHServerEmu.Games.Navi
{
StringBuilder hashes = new();
int id = 0;
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
hashes.AppendLine($"[{id++}] {triangle.ToHashString2()}");
FileHelper.SaveTextFileToRoot(fileName, hashes.ToString());
}
@ -943,12 +943,12 @@ namespace MHServerEmu.Games.Navi
{
NaviSvgHelper svg = new(this);
Stack<NaviPoint> influences = new();
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
foreach (var edge in triangle.Edges)
foreach (var point in edge.Points)
if (point.InfluenceRadius > 0 && influences.Contains(point) == false)
influences.Push(point);
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
svg.AddTriangle(triangle);
foreach (var point in influences)
svg.AddCircle(point.Pos, point.InfluenceRadius);
@ -962,7 +962,7 @@ namespace MHServerEmu.Games.Navi
// Vertices
int newId = 1;
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
if (triangle.PathingFlags.HasFlag(filterFlags))
foreach (var edge in triangle.Edges)
foreach (var point in edge.Points)
@ -975,7 +975,7 @@ namespace MHServerEmu.Games.Navi
}
// Faces
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
if (triangle.PathingFlags.HasFlag(filterFlags))
{
var p0 = triangle.PointCW(0);

View file

@ -345,7 +345,7 @@ namespace MHServerEmu.Games.Navi
private void ReverseMarkupMesh()
{
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
for (int edgeIndex = 0; edgeIndex < 3; edgeIndex++)
{
NaviEdge edge = triangle.Edges[edgeIndex];
@ -367,7 +367,7 @@ namespace MHServerEmu.Games.Navi
private void ClearMarkup()
{
foreach (var triangle in TriangleList)
foreach (var triangle in TriangleList.Iterate())
{
triangle.ClearFlag(NaviTriangleFlags.Markup);
triangle.PathingFlags = PathFlags.None;

View file

@ -11,22 +11,21 @@ namespace MHServerEmu.Games.Navi
Markup = 1 << 1,
}
public sealed class TriangleList : InvasiveList<NaviTriangle>
public class TriangleList : InvasiveList<NaviTriangle>
{
public TriangleList(int maxIterators = 1) : base(maxIterators) { }
public override ref InvasiveListNode<NaviTriangle> GetInvasiveListNode(NaviTriangle element, int listId) => ref element.InvasiveListNode;
public override InvasiveListNode<NaviTriangle> GetInvasiveListNode(NaviTriangle element, int listId) => element.InvasiveListNode;
}
public class NaviTriangle
{
private InlineArray3<NaviEdge> _edges;
private InvasiveListNode<NaviTriangle> _invasiveListNode;
public ref InlineArray3<NaviEdge> Edges { get => ref _edges; }
public byte EdgeSideFlags { get; private set; }
public NaviTriangleFlags Flags { get; private set; }
public PathFlags PathingFlags { get; set; }
public ref InvasiveListNode<NaviTriangle> InvasiveListNode { get => ref _invasiveListNode; }
public InvasiveListNode<NaviTriangle> InvasiveListNode { get; private set; }
public ContentFlagCounts ContentFlagCounts; // ContentFlagCounts needs to be a field for Clear() calls
@ -35,6 +34,7 @@ namespace MHServerEmu.Games.Navi
Edges[0] = e0;
Edges[1] = e1;
Edges[2] = e2;
InvasiveListNode = new();
UpdateEdgeSideFlags();
Attach();
}

View file

@ -1,9 +1,7 @@
using Gazillion;
using MHServerEmu.Core.Memory;
using MHServerEmu.Core.Serialization;
using MHServerEmu.DatabaseAccess.Models;
using MHServerEmu.Games.Entities;
using MHServerEmu.Games.Entities.Avatars;
using MHServerEmu.Games.GameData;
using MHServerEmu.Games.GameData.Prototypes;
using MHServerEmu.Games.Properties;
@ -56,25 +54,7 @@ namespace MHServerEmu.Games.Network
{
List<(ulong, ulong)> propertyList = migrationData.GetOrCreatePropertyList(entity.DatabaseUniqueId);
propertyList.Clear();
// HACK: Ugly property hack, remove this when we figure out an efficient way to migrate runtime-only conditions.
PropertyEnum propertyToIgnore = PropertyEnum.Invalid;
if (entity is Avatar)
{
switch ((AvatarPrototypeId)entity.PrototypeDataRef)
{
case AvatarPrototypeId.AntMan:
propertyToIgnore = PropertyEnum.SecondaryResource;
break;
case AvatarPrototypeId.HumanTorch:
propertyToIgnore = PropertyEnum.Endurance;
break;
}
}
entity.Properties.GetPropertiesForMigration(propertyList, propertyToIgnore);
entity.Properties.GetPropertiesForMigration(propertyList);
}
private static void RestoreProperties(MigrationData migrationData, Entity entity)

View file

@ -259,18 +259,9 @@ namespace MHServerEmu.Games.Network
using (Archive archive = new(ArchiveSerializeType.Database))
{
DBPlayer dbPlayer = _dbAccount.Player;
Span<byte> oldArchiveData = dbPlayer.ArchiveData ?? Span<byte>.Empty;
// NOTE: Use Transfer() and NOT Player.Serialize() to make sure we pack the size of the player
Serializer.Transfer(archive, Player);
Span<byte> newArchiveData = archive.AsSpan();
// No point in doing a SequenceEqual check here, it's always different in practice.
if (newArchiveData.Length == oldArchiveData.Length)
newArchiveData.CopyTo(oldArchiveData);
else
dbPlayer.ArchiveData = newArchiveData.ToArray();
_dbAccount.Player.ArchiveData = archive.AccessAutoBuffer().ToArray();
}
// Save last town as a separate database field to be able to access it without deserializing the player entity

View file

@ -110,7 +110,6 @@ namespace MHServerEmu.Games.Populations
int level = area.GetCharacterLevel(entityProto);
settingsProperties[PropertyEnum.CharacterLevel] = level;
settingsProperties[PropertyEnum.CombatLevel] = level;
settingsProperties[PropertyEnum.DifficultyTier] = region.DifficultyTierRef;
if (Group != null)
{
settingsProperties[PropertyEnum.SpawnGroupId] = Group.Id;

View file

@ -216,10 +216,6 @@ namespace MHServerEmu.Games.Powers
if (PowerPrototype is not MovementPowerPrototype movementPowerProto || movementPowerProto.ConstantMoveTime == false)
Properties.CopyProperty(power.Properties, PropertyEnum.MovementSpeedOverride);
// Difficulty tier for summons (e.g. Axis raid sentinels)
if (PowerPrototype is SummonPowerPrototype)
Properties.CopyProperty(powerOwner.Properties, PropertyEnum.DifficultyTier);
// Snapshot properties from triggering power results
// TODO: Do we need full power results here? We should be able to get away with just the properties

View file

@ -844,7 +844,7 @@ namespace MHServerEmu.Games.Properties
return success;
}
public void GetPropertiesForMigration(List<(ulong, ulong)> propertyList, PropertyEnum propertyToIgnore = PropertyEnum.Invalid)
public void GetPropertiesForMigration(List<(ulong, ulong)> propertyList)
{
PropertyEnum prevProperty = PropertyEnum.Invalid;
PropertyInfoPrototype propInfoProto = null;
@ -860,9 +860,9 @@ namespace MHServerEmu.Games.Properties
prevProperty = propertyEnum;
}
// HACK: Do not migrate some properties because we don't migrate conditions, which can cause some heroes to get stuck in bad state (e.g. Human Torch, Ant-Man).
// HACK: Do not migrate mana because we don't migrate conditions, which can cause some heroes to get stuck in bad state (e.g. Human Torch).
// This can be removed if we ever start migrating runtime-only conditions.
if (propertyEnum == propertyToIgnore)
if (propertyEnum == PropertyEnum.Endurance)
continue;
// Migrate properties that are not saved to the database, but are supposed to be replicated for transfer

View file

@ -214,7 +214,7 @@ namespace MHServerEmu.Games.Regions.MatchQueues
/// Handles a <see cref="RegionRequestQueueCommandVar"/> request from a client.
/// </summary>
public bool TryRegionRequestCommand(PrototypeId regionRef, PrototypeId difficultyTierRef,
ulong groupId, RegionRequestQueueCommandVar command, int teamSizeOverride = -1)
ulong groupId, RegionRequestQueueCommandVar command)
{
if (regionRef == PrototypeId.Invalid) return Logger.WarnReturn(false, "TryRegionRequestCommand(): regionRef == PrototypeId.Invalid");
@ -272,7 +272,7 @@ namespace MHServerEmu.Games.Regions.MatchQueues
if (command == RegionRequestQueueCommandVar.eRRQC_AddToQueueBypass && regionProto.AllowsQueueBypass == false)
return false;
_owner.SendRegionRequestQueueCommandToPlayerManager(regionRef, difficultyTierRef, command, groupId, 0, teamSizeOverride);
_owner.SendRegionRequestQueueCommandToPlayerManager(regionRef, difficultyTierRef, command, groupId);
return true;
}

View file

@ -33,8 +33,6 @@ namespace MHServerEmu.PlayerManagement.Auth
private CooldownTimer _updateTimer = new(TimeSpan.FromMilliseconds(1000));
public bool WhitelistEnabled { get; private set; }
public int PendingSessionCount { get => _pendingSessionDict.Count; }
public int ActiveSessionCount { get => _activeSessionDict.Count; }
@ -44,16 +42,6 @@ namespace MHServerEmu.PlayerManagement.Auth
public SessionManager(PlayerManagerService playerManager)
{
_playerManager = playerManager;
WhitelistEnabled = playerManager.Config.UseWhitelist;
}
public void SetWhitelistEnabled(bool enable)
{
if (WhitelistEnabled == enable)
return;
WhitelistEnabled = enable;
Logger.Info($"Whitelist {(enable ? "enabled" : "disabled")}");
}
public void Update()
@ -104,7 +92,7 @@ namespace MHServerEmu.PlayerManagement.Auth
}
// Verify credentials
AuthStatusCode statusCode = AccountManager.TryGetAccountByLoginDataPB(loginDataPB, WhitelistEnabled, out DBAccount account);
AuthStatusCode statusCode = AccountManager.TryGetAccountByLoginDataPB(loginDataPB, _playerManager.Config.UseWhitelist, out DBAccount account);
if (statusCode != AuthStatusCode.Success)
return statusCode;

View file

@ -27,13 +27,11 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
QueueParams = queueParams;
int[] teamLimits = Queue.Prototype.TeamLimits;
if (teamLimits.HasValue())
{
for (int i = 0; i < teamLimits.Length; i++)
{
int teamLimit = (queueParams.TeamSizeOverride > 0) ? queueParams.TeamSizeOverride : teamLimits[i];
MatchTeam team = new(i, teamLimit);
MatchTeam team = new(i, teamLimits[i]);
_teams.Add(team);
}
}

View file

@ -22,7 +22,7 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
}
public void HandleCommand(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef,
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId, int teamSizeOverride = -1)
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId)
{
Logger.Trace($"HandleCommand(): command=[{command}], region=[{regionRef.GetNameFormatted()}], player=[{_player}]");
@ -31,7 +31,7 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
case RegionRequestQueueCommandVar.eRRQC_AddToQueueSolo:
case RegionRequestQueueCommandVar.eRRQC_AddToQueueParty:
case RegionRequestQueueCommandVar.eRRQC_AddToQueueBypass:
OnAddToQueue(regionRef, difficultyTierRef, metaStateRef, command, teamSizeOverride);
OnAddToQueue(regionRef, difficultyTierRef, metaStateRef, command);
break;
case RegionRequestQueueCommandVar.eRRQC_RemoveFromQueue:
@ -53,8 +53,7 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
}
}
private bool OnAddToQueue(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef,
RegionRequestQueueCommandVar command, int teamSizeOverride = -1)
private bool OnAddToQueue(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef, RegionRequestQueueCommandVar command)
{
RegionRequestQueue queue = PlayerManagerService.Instance.RegionRequestQueueManager.GetRegionRequestQueue(regionRef);
if (queue == null)
@ -75,7 +74,7 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
// Create region request group
MasterParty party = command == RegionRequestQueueCommandVar.eRRQC_AddToQueueParty ? _player.CurrentParty : null;
RegionRequestQueueParams queueParams = new(difficultyTierRef, metaStateRef, command == RegionRequestQueueCommandVar.eRRQC_AddToQueueBypass, teamSizeOverride);
RegionRequestQueueParams queueParams = new(difficultyTierRef, metaStateRef, command == RegionRequestQueueCommandVar.eRRQC_AddToQueueBypass);
RegionRequestGroup group = RegionRequestGroup.Create(queue, queueParams, _player, party);
if (group == null)

View file

@ -10,24 +10,22 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
public readonly PrototypeId DifficultyTierRef;
public readonly PrototypeId MetaStateRef;
public readonly bool IsBypass;
public readonly int TeamSizeOverride;
public RegionRequestQueueParams(PrototypeId difficultyTierRef, PrototypeId metaStateRef, bool isBypass, int teamSizeOverride)
public RegionRequestQueueParams(PrototypeId difficultyTierRef, PrototypeId metaStateRef, bool isBypass)
{
DifficultyTierRef = difficultyTierRef;
MetaStateRef = metaStateRef;
IsBypass = isBypass;
TeamSizeOverride = teamSizeOverride;
}
public override string ToString()
{
return $"difficulty={DifficultyTierRef.GetNameFormatted()}, metaState={MetaStateRef.GetNameFormatted()}, isBypass={IsBypass}, teamSizeOverride={TeamSizeOverride}";
return $"difficulty={DifficultyTierRef.GetNameFormatted()}, metaState={MetaStateRef.GetNameFormatted()}, isBypass={IsBypass}";
}
public override int GetHashCode()
{
return HashCode.Combine(DifficultyTierRef, MetaStateRef, IsBypass, TeamSizeOverride);
return HashCode.Combine(DifficultyTierRef, MetaStateRef, IsBypass);
}
public override bool Equals(object obj)
@ -42,8 +40,7 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
{
return DifficultyTierRef == other.DifficultyTierRef &&
MetaStateRef == other.MetaStateRef &&
IsBypass == other.IsBypass &&
TeamSizeOverride == other.TeamSizeOverride;
IsBypass == other.IsBypass;
}
public static bool operator ==(RegionRequestQueueParams left, RegionRequestQueueParams right)

View file

@ -137,10 +137,6 @@ namespace MHServerEmu.PlayerManagement.Network
OnAccountOperationRequest(accountOperationRequest);
break;
case ServiceMessage.SetWhitelistEnabled setWhitelistEnabled:
OnSetWhitelistEnabled(setWhitelistEnabled);
break;
default:
Logger.Warn($"ReceiveServiceMessage(): Unhandled service message type {message.GetType().Name}");
break;
@ -429,10 +425,9 @@ namespace MHServerEmu.PlayerManagement.Network
RegionRequestQueueCommandVar command = matchRegionRequestQueueCommand.Command;
ulong regionRequestGroupId = matchRegionRequestQueueCommand.RegionRequestGroupId;
ulong targetPlayerDbId = matchRegionRequestQueueCommand.TargetPlayerDbId;
int teamSizeOverride = matchRegionRequestQueueCommand.TeamSizeOverride;
PlayerHandle player = _playerManager.ClientManager.GetPlayer(playerDbId);
player?.ReceiveRegionRequestQueueCommand(regionRef, difficultyTierRef, metaStateRef, command, regionRequestGroupId, targetPlayerDbId, teamSizeOverride);
player?.ReceiveRegionRequestQueueCommand(regionRef, difficultyTierRef, metaStateRef, command, regionRequestGroupId, targetPlayerDbId);
return true;
}
@ -632,13 +627,6 @@ namespace MHServerEmu.PlayerManagement.Network
return true;
}
private bool OnSetWhitelistEnabled(in ServiceMessage.SetWhitelistEnabled setWhitelistEnabled)
{
_playerManager.SessionManager.SetWhitelistEnabled(setWhitelistEnabled.Enable);
return true;
}
#endregion
}
}

View file

@ -14,7 +14,7 @@ namespace MHServerEmu.PlayerManagement
public bool ShowNewsOnLogin { get; private set; } = false;
public string NewsUrl { get; private set; } = "http://localhost/";
public int ServerCapacity { get; private set; } = 0;
public int MaxLoginQueueClients { get; private set; } = 8192;
public int MaxLoginQueueClients { get; private set; } = 10000;
public bool EnableTownPlayerLimit { get; private set; } = false;
}
}

View file

@ -49,8 +49,6 @@ namespace MHServerEmu.PlayerManagement
/// </summary>
public PlayerManagerService()
{
Config = ConfigManager.Instance.GetConfig<PlayerManagerConfig>();
_serviceMailbox = new(this);
SessionManager = new(this);
@ -64,6 +62,8 @@ namespace MHServerEmu.PlayerManagement
RegionRequestQueueManager = new(this);
EventScheduler = new();
Config = ConfigManager.Instance.GetConfig<PlayerManagerConfig>();
}
#region IGameService Implementation

View file

@ -1,7 +1,6 @@
using Gazillion;
using Google.ProtocolBuffers;
using MHServerEmu.Core.Collections;
using MHServerEmu.Core.Extensions;
using MHServerEmu.Core.Logging;
using MHServerEmu.Core.Network;
using MHServerEmu.Core.System.Time;
@ -16,55 +15,24 @@ namespace MHServerEmu.PlayerManagement.Players
private static readonly Logger Logger = LogManager.CreateLogger();
private static readonly TimeSpan PendingClientTimeout = TimeSpan.FromSeconds(15);
private static readonly TimeSpan StatusUpdateInterval = TimeSpan.FromSeconds(10);
private static readonly TimeSpan ReconnectPermissionDuration = TimeSpan.FromMinutes(10);
private static readonly SessionEncryptionChanged SessionEncryptionChangedMessage = SessionEncryptionChanged.CreateBuilder()
.SetRandomNumberIndex(0)
.SetEncryptedRandomNumber(ByteString.Empty)
.Build();
private readonly DoubleBufferQueue<IFrontendClient> _newClientQueue = new();
private readonly LinkedList<IFrontendClient> _defaultQueue = new();
private readonly LinkedList<IFrontendClient> _reconnectQueue = new();
private readonly Queue<IFrontendClient> _highPriorityQueue = new();
private readonly Stack<LinkedListNode<IFrontendClient>> _queueNodes;
private readonly Queue<IFrontendClient> _loginQueue = new();
private readonly Queue<IFrontendClient> _highPriorityLoginQueue = new();
// Pending clients are clients that have successfully passed the login queue
private readonly Dictionary<IFrontendClient, TimeSpan> _pendingClients = new();
private readonly PlayerManagerService _playerManager;
private readonly LoginQueueStatus.Builder _statusBuilder = LoginQueueStatus.CreateBuilder();
private readonly Dictionary<IFrontendClient, TimeSpan> _statusUpdateTimes;
private readonly Dictionary<ulong, TimeSpan> _reconnectPermissions = new();
private CooldownTimer _reconnectPermissionPurgeTimer = new(TimeSpan.FromMinutes(1));
public int PlayersInLine { get => _defaultQueue.Count + _reconnectQueue.Count; }
public LoginQueueManager(PlayerManagerService playerManager)
{
_playerManager = playerManager;
int maxQueueClients = playerManager.Config.MaxLoginQueueClients;
_queueNodes = new(maxQueueClients);
for (int i = 0; i < maxQueueClients; i++)
{
LinkedListNode<IFrontendClient> node = new(null);
_queueNodes.Push(node);
}
_statusUpdateTimes = new(maxQueueClients);
}
public void Update()
{
TimeOutPendingClients();
PurgeReconnectPermissions();
AcceptNewClients();
ProcessLoginQueue();
}
@ -104,29 +72,13 @@ namespace MHServerEmu.PlayerManagement.Players
}
}
private void PurgeReconnectPermissions()
{
if (_reconnectPermissionPurgeTimer.Check() == false)
return;
TimeSpan now = Clock.UnixTime;
foreach (var kvp in _reconnectPermissions)
{
TimeSpan duration = now - kvp.Value;
if (duration >= ReconnectPermissionDuration)
{
_reconnectPermissions.Remove(kvp.Key);
Logger.Info($"Reconnect permission expired for account 0x{kvp.Key:X}");
}
}
}
/// <summary>
/// Accepts asynchronously added clients to the login queue.
/// </summary>
private void AcceptNewClients()
{
int maxLoginQueueClients = _playerManager.Config.MaxLoginQueueClients;
_newClientQueue.Swap();
while (_newClientQueue.CurrentCount > 0)
@ -146,30 +98,22 @@ namespace MHServerEmu.PlayerManagement.Players
// High priority queue always ignores server capacity
if (IsClientHighPriority(client))
{
_highPriorityQueue.Enqueue(client);
_highPriorityLoginQueue.Enqueue(client);
}
else
{
if (_queueNodes.TryPop(out LinkedListNode<IFrontendClient> queueNode) == false)
if (_loginQueue.Count >= maxLoginQueueClients)
{
Logger.Warn($"AcceptNewClients(): Unable to accept client [{client}], the queue already has {PlayersInLine} clients, which is the maximum number allowed by the current server configuration");
Logger.Warn($"AcceptNewClients(): Unable to accept client [{client}], the queue already has {maxLoginQueueClients} clients, which is the maximum number allowed by the current server configuration");
client.Disconnect();
RemoveClientSession(client);
continue;
}
LinkedList<IFrontendClient> queueToUse = _defaultQueue;
if (_reconnectPermissions.Remove(client.DbId))
{
queueToUse = _reconnectQueue;
Logger.Info($"Consumed reconnect permission for client [{client}]");
}
queueNode.Value = client;
queueToUse.AddLast(queueNode);
_loginQueue.Enqueue(client);
}
Logger.Info($"Accepted client [{client}] into the login queue");
}
}
@ -183,40 +127,37 @@ namespace MHServerEmu.PlayerManagement.Players
int availableCapacity = totalCapacity - _playerManager.ClientManager.PlayerCount - _pendingClients.Count;
// Let clients from the high priority queue in first ignoring capacity
while (_highPriorityQueue.Count > 0)
while (_highPriorityLoginQueue.Count > 0)
{
IFrontendClient client = _highPriorityQueue.Dequeue();
ProcessQueuedClient(client, ref availableCapacity, false);
IFrontendClient client = _highPriorityLoginQueue.Dequeue();
ProcessQueuedClient(client, ref availableCapacity);
}
// Let clients from the reconnect and default queues, check available capacity if enabled
ProcessQueue(_reconnectQueue, totalCapacity, ref availableCapacity, false);
ProcessQueue(_defaultQueue, totalCapacity, ref availableCapacity, true);
// Update status of remaining players
int playersInLine = PlayersInLine;
if (playersInLine > 0)
// Let clients from the normal login queue, check available capacity if enabled
while (_loginQueue.Count > 0 && (totalCapacity <= 0 || availableCapacity > 0))
{
_statusBuilder.SetNumberOfPlayersInLine((ulong)playersInLine);
TimeSpan now = Clock.UnixTime;
ulong nextPlaceInLine = 1;
UpdateQueueStatus(_reconnectQueue, ref nextPlaceInLine, now);
UpdateQueueStatus(_defaultQueue, ref nextPlaceInLine, now);
IFrontendClient client = _loginQueue.Dequeue();
ProcessQueuedClient(client, ref availableCapacity);
}
// Send status updates to remaining players
int playersInLine = _loginQueue.Count;
if (playersInLine == 0)
return;
LoginQueueStatus.Builder statusBuilder = LoginQueueStatus.CreateBuilder()
.SetNumberOfPlayersInLine((ulong)playersInLine);
ulong placeInLine = 1;
foreach (IFrontendClient client in _loginQueue)
client.SendMessage(MuxChannel, statusBuilder.SetPlaceInLine(placeInLine++).Build());
}
private static bool IsClientHighPriority(IFrontendClient client)
{
DBAccount account = ((IDBAccountOwner)client).Account;
// Users with elevated privileges (moderators / admins) have high priority by default.
if (account.UserLevel > AccountUserLevel.User)
return true;
// Accounts can be manually flagged to have high priority.
if (account.Flags.HasFlag(AccountFlags.BypassLoginQueue))
// Users with elevated privileges (moderators / admins) have high priority
if (((IDBAccountOwner)client).Account.UserLevel > AccountUserLevel.User)
return true;
// Add more cases as needed
@ -224,27 +165,8 @@ namespace MHServerEmu.PlayerManagement.Players
return false;
}
private void ProcessQueue(LinkedList<IFrontendClient> queue, int totalCapacity, ref int availableCapacity, bool allowReconnect)
private bool ProcessQueuedClient(IFrontendClient client, ref int availableCapacity)
{
while (queue.Count > 0 && (totalCapacity <= 0 || availableCapacity > 0))
{
LinkedListNode<IFrontendClient> queueNode = queue.First;
IFrontendClient client = queueNode.Value;
RemoveQueueNode(queueNode);
ProcessQueuedClient(client, ref availableCapacity, allowReconnect);
}
}
private bool ProcessQueuedClient(IFrontendClient client, ref int availableCapacity, bool allowReconnect)
{
// Allow all clients that pass the default queue to reconnect because of the client-side afk timer bug.
if (allowReconnect)
{
_reconnectPermissions[client.DbId] = Clock.UnixTime;
Logger.Info($"Added reconnect permission for client [{client}]");
}
if (client.IsConnected == false)
{
Logger.Warn($"ProcessQueuedClient(): Client [{client}] disconnected while waiting in the login queue");
@ -256,8 +178,10 @@ namespace MHServerEmu.PlayerManagement.Players
// However, if a malicious user modifies their client, it may try to skip ahead, so we need to verify this.
_pendingClients.Add(client, Clock.UnixTime);
// Gazillion never finished implementing encryption, so the SessionEncryptionChanged message is just a dummy we can cache and reuse.
client.SendMessage(MuxChannel, SessionEncryptionChangedMessage);
client.SendMessage(MuxChannel, SessionEncryptionChanged.CreateBuilder()
.SetRandomNumberIndex(0)
.SetEncryptedRandomNumber(ByteString.Empty)
.Build());
Logger.Info($"Client [{client}] passed the login queue");
@ -266,47 +190,6 @@ namespace MHServerEmu.PlayerManagement.Players
return true;
}
private void UpdateQueueStatus(LinkedList<IFrontendClient> queue, ref ulong nextPlaceInLine, TimeSpan now)
{
LinkedListNode<IFrontendClient> current = queue.First;
while (current != null)
{
IFrontendClient client = current.Value;
if (client.IsConnected == false)
{
Logger.Warn($"UpdateQueueStatus(): Client [{client}] disconnected while waiting in the login queue");
LinkedListNode<IFrontendClient> prev = current;
current = current.Next;
RemoveQueueNode(prev);
RemoveClientSession(client);
continue;
}
ulong placeInLine = nextPlaceInLine++;
ref TimeSpan lastUpdateTime = ref _statusUpdateTimes.GetValueRefOrAddDefault(client);
if ((now - lastUpdateTime) >= StatusUpdateInterval)
{
lastUpdateTime = now;
LoginQueueStatus status = _statusBuilder.SetPlaceInLine(placeInLine).Build();
client.SendMessage(MuxChannel, status);
}
current = current.Next;
}
}
private void RemoveQueueNode(LinkedListNode<IFrontendClient> node)
{
_statusUpdateTimes.Remove(node.Value);
node.Remove();
_queueNodes.Push(node);
}
private void RemoveClientSession(IFrontendClient client)
{
ulong sessionId = client.Session.Id;

View file

@ -814,9 +814,9 @@ namespace MHServerEmu.PlayerManagement.Players
}
public void ReceiveRegionRequestQueueCommand(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef,
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId, int teamSizeOverride)
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId)
{
_regionRequestQueueCommandHandler.HandleCommand(regionRef, difficultyTierRef, metaStateRef, command, regionRequestGroupId, targetPlayerDbId, teamSizeOverride);
_regionRequestQueueCommandHandler.HandleCommand(regionRef, difficultyTierRef, metaStateRef, command, regionRequestGroupId, targetPlayerDbId);
}
public void AddToChatRoom(ChatRoomTypes roomType, ulong roomId)

View file

@ -71,7 +71,7 @@ namespace MHServerEmu.Commands.Implementations
LootManager lootGenerator = playerConnection.Game.LootManager;
for (int i = 0; i < count; i++)
lootGenerator.GiveItem(itemProtoRef, LootContext.CashShop, player);
lootGenerator.GiveItem(itemProtoRef, LootContext.Drop, player);
Logger.Debug($"GiveItem(): {itemProtoRef.GetName()}[{count}] to {player}");
return string.Empty;

View file

@ -1,67 +0,0 @@
using Gazillion;
using MHServerEmu.Commands.Attributes;
using MHServerEmu.Core.Network;
using MHServerEmu.DatabaseAccess.Models;
using MHServerEmu.Games.GameData;
using MHServerEmu.Games.Network;
namespace MHServerEmu.Commands.Implementations
{
[CommandGroup("pvp")]
[CommandGroupDescription("Commands related to PvP matchmaking.")]
[CommandGroupUserLevel(AccountUserLevel.User)]
public class PvPCommands : CommandGroup
{
private static readonly PrototypeId RegionRef =
GameDatabase.GetPrototypeRefByName("Metagame/DefenderPvP/Regions/PvPDefenderTier5Region.prototype");
private static readonly PrototypeId DifficultyRef =
GameDatabase.GetPrototypeRefByName("Difficulty/Tiers/Tier1Normal.prototype");
private string JoinQueue(int size, NetClient client)
{
PlayerConnection playerConnection = (PlayerConnection)client;
var player = playerConnection.Player;
if (player == null) return "Player not found.";
bool isInParty = player.Party != null && player.Party.NumMembers > 1;
var command = isInParty
? RegionRequestQueueCommandVar.eRRQC_AddToQueueParty
: RegionRequestQueueCommandVar.eRRQC_AddToQueueSolo;
int limit = (size == 5) ? -1 : size;
player.MatchQueueStatus.TryRegionRequestCommand(RegionRef, DifficultyRef, 0, command, limit);
return $"Queued for {size}v{size} PvP! ({(isInParty ? "Party" : "Solo")})";
}
[Command("1v1")]
[CommandDescription("Join 1v1 PvP queue.")]
[CommandUsage("pvp 1v1")]
[CommandInvokerType(CommandInvokerType.Client)]
public string Queue1v1(string[] @params, NetClient client) => JoinQueue(1, client);
[Command("2v2")]
[CommandDescription("Join 2v2 PvP queue.")]
[CommandUsage("pvp 2v2")]
[CommandInvokerType(CommandInvokerType.Client)]
public string Queue2v2(string[] @params, NetClient client) => JoinQueue(2, client);
[Command("3v3")]
[CommandDescription("Join 3v3 PvP queue.")]
[CommandUsage("pvp 3v3")]
[CommandInvokerType(CommandInvokerType.Client)]
public string Queue3v3(string[] @params, NetClient client) => JoinQueue(3, client);
[Command("4v4")]
[CommandDescription("Join 4v4 PvP queue.")]
[CommandUsage("pvp 4v4")]
[CommandInvokerType(CommandInvokerType.Client)]
public string Queue4v4(string[] @params, NetClient client) => JoinQueue(4, client);
[Command("5v5")]
[CommandDescription("Join 5v5 PvP queue.")]
[CommandUsage("pvp 5v5")]
[CommandInvokerType(CommandInvokerType.Client)]
public string Queue5v5(string[] @params, NetClient client) => JoinQueue(5, client);
}
}

View file

@ -105,29 +105,6 @@ namespace MHServerEmu.Commands.Implementations
return string.Empty;
}
[Command("whitelist")]
[CommandDescription("Enables or disables account whitelist for logins.")]
[CommandParamCount(1)]
[CommandUserLevel(AccountUserLevel.Admin)]
[CommandInvokerType(CommandInvokerType.ServerConsole)]
public string Whitelist(string[] @params, NetClient client)
{
bool enable;
if (bool.TryParse(@params[0], out enable) == false)
{
if (int.TryParse(@params[0], out int value) == false)
return "Invalid parameter. Please use boolean or integer values.";
enable = value != 0;
}
ServiceMessage.SetWhitelistEnabled message = new(enable);
ServerManager.Instance.SendMessageToService(GameServiceType.PlayerManager, message);
return $"Sent a request to {(enable ? "enable" : "disable")} whitelist.";
}
[Command("shutdown")]
[CommandDescription("Shuts the server down.")]
[CommandUsage("server shutdown")]

View file

@ -87,7 +87,7 @@ UseWhitelist=false
ShowNewsOnLogin=false
NewsUrl=http://localhost/news/index.html
ServerCapacity=0
MaxLoginQueueClients=8192
MaxLoginQueueClients=10000
EnableTownPlayerLimit=false
[SQLiteDBManager]