Compare commits
32 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ed41d43a8 | ||
|
|
8595b65421 | ||
|
|
75c78bec4c | ||
|
|
fb56fe7e50 | ||
|
|
34b059fc96 | ||
|
|
088590eca2 | ||
|
|
b3e9fed249 | ||
|
|
4ee8419132 | ||
|
|
5c9b4b7aff | ||
|
|
74e39fb00a | ||
|
|
346965116f | ||
|
|
a3551c8ab8 | ||
|
|
c38018316b | ||
|
|
5acbeb2602 | ||
|
|
32ec15fd1a | ||
|
|
e0508fb29c | ||
|
|
cc241e6325 | ||
|
|
b93e433227 | ||
|
|
c29d8634ad | ||
|
|
66233d2fbe | ||
|
|
877adaf80d | ||
|
|
8740e60e35 | ||
|
|
08a875b69f | ||
|
|
c62be10413 | ||
|
|
a934f07f3a | ||
|
|
03839a2699 | ||
|
|
be411b2684 | ||
|
|
fc83aa151b | ||
|
|
1c75103166 | ||
|
|
7f3837583d | ||
|
|
85cd903586 | ||
|
|
641c35258f |
40 changed files with 1001 additions and 218 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Server Commands
|
||||
|
||||
This list was automatically generated on `2026.03.09 15:06:03 UTC` using server version `1.0.0`.
|
||||
This list was automatically generated on `2026.03.25 14:52:06 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,16 +211,17 @@ 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 |
|
||||
| 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 |
|
||||
|
||||
## Store
|
||||
Commands for interacting with the in-game store.
|
||||
|
|
|
|||
|
|
@ -1,58 +1,59 @@
|
|||
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; set; }
|
||||
public T Head { get; private set; }
|
||||
public T Tail { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
|
||||
private Iterator[] _iterators;
|
||||
private int _numIterators;
|
||||
private int _maxIterators;
|
||||
public bool IsEmpty { get => Head == null; }
|
||||
|
||||
public InvasiveList(int maxIterators)
|
||||
public InvasiveList(int maxIterators, int id = 0)
|
||||
{
|
||||
_maxIterators = maxIterators;
|
||||
_iterators = new Iterator[_maxIterators];
|
||||
}
|
||||
_iterators = new Iterator[maxIterators];
|
||||
|
||||
if (maxIterators > 1)
|
||||
_iteratorPool = new();
|
||||
|
||||
public InvasiveList(int maxIterators, int id)
|
||||
{
|
||||
_maxIterators = maxIterators;
|
||||
_iterators = new Iterator[_maxIterators];
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public IEnumerable<T> Iterate()
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
var iterator = new Iterator(this);
|
||||
Iterator iterator;
|
||||
|
||||
try
|
||||
if (_iteratorPool != null)
|
||||
{
|
||||
while (iterator.End() == false)
|
||||
{
|
||||
var element = iterator.Current;
|
||||
iterator.MoveNext();
|
||||
yield return element;
|
||||
}
|
||||
if (_iteratorPool.TryPop(out iterator) == false)
|
||||
iterator = new(this);
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
UnregisterIterator(iterator);
|
||||
_reusableIterator ??= new(this);
|
||||
iterator = _reusableIterator;
|
||||
}
|
||||
|
||||
iterator.Initialize();
|
||||
return iterator;
|
||||
}
|
||||
|
||||
public bool IsEmpty() => Head == null;
|
||||
|
||||
public void Remove(T element)
|
||||
{
|
||||
if (element == null || Contains(element) == false) return;
|
||||
|
||||
var node = GetInvasiveListNode(element, Id);
|
||||
if (node == null) return;
|
||||
ref var node = ref GetInvasiveListNode(element, Id);
|
||||
if (Unsafe.IsNullRef(ref node)) return;
|
||||
|
||||
for (int i = 0; i < _numIterators; i++)
|
||||
{
|
||||
|
|
@ -68,16 +69,16 @@ namespace MHServerEmu.Core.Collections
|
|||
if (node.Next != null)
|
||||
{
|
||||
T nextElement = node.Next;
|
||||
var nextNode = GetInvasiveListNode(nextElement, Id);
|
||||
if (nextNode != null)
|
||||
ref var nextNode = ref GetInvasiveListNode(nextElement, Id);
|
||||
if (Unsafe.IsNullRef(ref nextNode) == false)
|
||||
nextNode.Prev = node.Prev;
|
||||
}
|
||||
|
||||
if (node.Prev != null)
|
||||
{
|
||||
T prevElement = node.Prev;
|
||||
var prevNode = GetInvasiveListNode(prevElement, Id);
|
||||
if (prevNode != null)
|
||||
ref var prevNode = ref GetInvasiveListNode(prevElement, Id);
|
||||
if (Unsafe.IsNullRef(ref prevNode) == false)
|
||||
prevNode.Next = node.Next;
|
||||
}
|
||||
|
||||
|
|
@ -93,11 +94,11 @@ namespace MHServerEmu.Core.Collections
|
|||
if (oldElement == null || Contains(oldElement) == false) return;
|
||||
if (element == null || Contains(element)) return;
|
||||
|
||||
var node = GetInvasiveListNode(element, Id);
|
||||
if (node == null) return;
|
||||
ref var node = ref GetInvasiveListNode(element, Id);
|
||||
if (Unsafe.IsNullRef(ref node)) return;
|
||||
|
||||
var oldNode = GetInvasiveListNode(oldElement, Id);
|
||||
if (oldNode == null) return;
|
||||
ref var oldNode = ref GetInvasiveListNode(oldElement, Id);
|
||||
if (Unsafe.IsNullRef(ref oldNode)) return;
|
||||
|
||||
var oldPrev = oldNode.Prev;
|
||||
oldNode.Prev = element;
|
||||
|
|
@ -106,8 +107,8 @@ namespace MHServerEmu.Core.Collections
|
|||
|
||||
if (oldPrev != null)
|
||||
{
|
||||
var oldPrevNode = GetInvasiveListNode(oldPrev, Id);
|
||||
if (oldPrevNode == null) return;
|
||||
ref var oldPrevNode = ref GetInvasiveListNode(oldPrev, Id);
|
||||
if (Unsafe.IsNullRef(ref oldPrevNode)) return;
|
||||
oldPrevNode.Next = element;
|
||||
}
|
||||
else
|
||||
|
|
@ -120,14 +121,14 @@ namespace MHServerEmu.Core.Collections
|
|||
{
|
||||
if (element == null || Contains(element)) return;
|
||||
|
||||
var node = GetInvasiveListNode(element, Id);
|
||||
if (node == null) return;
|
||||
ref var node = ref GetInvasiveListNode(element, Id);
|
||||
if (Unsafe.IsNullRef(ref node)) return;
|
||||
|
||||
node.Prev = Tail;
|
||||
if (Tail != null)
|
||||
{
|
||||
var tailNode = GetInvasiveListNode(Tail, Id);
|
||||
if (tailNode == null) return;
|
||||
ref var tailNode = ref GetInvasiveListNode(Tail, Id);
|
||||
if (Unsafe.IsNullRef(ref tailNode)) return;
|
||||
tailNode.Next = element;
|
||||
}
|
||||
else
|
||||
|
|
@ -137,20 +138,23 @@ namespace MHServerEmu.Core.Collections
|
|||
Count++;
|
||||
}
|
||||
|
||||
public virtual InvasiveListNode<T> GetInvasiveListNode(T element, int listId) => null;
|
||||
public virtual ref InvasiveListNode<T> GetInvasiveListNode(T element, int listId)
|
||||
{
|
||||
return ref Unsafe.NullRef<InvasiveListNode<T>>();
|
||||
}
|
||||
|
||||
public bool Contains(T element)
|
||||
{
|
||||
if (element == null) return false;
|
||||
var node = GetInvasiveListNode(element, Id);
|
||||
if (node == null) return false;
|
||||
ref var node = ref GetInvasiveListNode(element, Id);
|
||||
if (Unsafe.IsNullRef(ref node)) return false;
|
||||
return node.Next != null || node.Prev != null || element.Equals(Head);
|
||||
}
|
||||
|
||||
private void RegisterIterator(Iterator iterator)
|
||||
{
|
||||
if (_numIterators >= _maxIterators)
|
||||
throw new InvalidOperationException($"Too many iterators '{_maxIterators}' for invasive list");
|
||||
if (_numIterators >= _iterators.Length)
|
||||
throw new InvalidOperationException($"Too many iterators '{_iterators.Length}' for invasive list");
|
||||
|
||||
_iterators[_numIterators++] = iterator;
|
||||
}
|
||||
|
|
@ -165,71 +169,75 @@ 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 class Iterator : IEnumerator<T>
|
||||
public sealed class Iterator : IEnumerator<T>
|
||||
{
|
||||
private InvasiveList<T> _list;
|
||||
public bool SkipNext { get; set; }
|
||||
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;
|
||||
|
||||
public Iterator(InvasiveList<T> invasiveList)
|
||||
{
|
||||
_list = invasiveList;
|
||||
Current = _list.Head;
|
||||
SkipNext = false;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_list.RegisterIterator(this);
|
||||
}
|
||||
|
||||
public T Current { get; private set; }
|
||||
object IEnumerator.Current => Current;
|
||||
public void Dispose() { }
|
||||
public void Reset() { }
|
||||
public void Dispose()
|
||||
{
|
||||
_list.UnregisterIterator(this);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_start = true;
|
||||
Current = default;
|
||||
SkipNext = false;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (SkipNext) SkipNext = false;
|
||||
else if (Current != null)
|
||||
Current = _list.GetInvasiveListNode(Current, _list.Id).Next;
|
||||
if (_start)
|
||||
{
|
||||
Current = _list.Head;
|
||||
_start = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SkipNext)
|
||||
SkipNext = false;
|
||||
else if (Current != null)
|
||||
Current = _list.GetInvasiveListNode(Current, _list.Id).Next;
|
||||
}
|
||||
|
||||
return true;
|
||||
return Current != null;
|
||||
}
|
||||
|
||||
public bool End() => Current == null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class InvasiveListNode<T>
|
||||
public struct InvasiveListNode<T>
|
||||
{
|
||||
public T Next { get; set; }
|
||||
public T Prev { get; set; }
|
||||
public T Next;
|
||||
public T Prev;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
public readonly struct MatchRegionRequestQueueCommand(ulong playerDbId, ulong regionProtoId, ulong difficultyTierProtoId, ulong metaStateProtoId, RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId, int teamSizeOverride)
|
||||
: IGameServiceMessage
|
||||
{
|
||||
public readonly ulong PlayerDbId = playerDbId;
|
||||
|
|
@ -471,6 +471,7 @@ 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
|
||||
|
|
@ -830,6 +831,12 @@ namespace MHServerEmu.Core.Network
|
|||
public readonly int ResultCode = resultCode;
|
||||
}
|
||||
|
||||
public readonly struct SetWhitelistEnabled(bool enable)
|
||||
: IGameServiceMessage
|
||||
{
|
||||
public readonly bool Enable = enable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Google.ProtocolBuffers;
|
||||
using MHServerEmu.Core.Serialization;
|
||||
|
||||
namespace MHServerEmu.Core.Network
|
||||
{
|
||||
|
|
@ -43,5 +44,15 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
using System.Buffers;
|
||||
using System.Collections;
|
||||
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
|
||||
{
|
||||
|
|
@ -17,7 +16,6 @@ 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;
|
||||
|
||||
|
|
@ -137,15 +135,10 @@ namespace MHServerEmu.Core.Network
|
|||
if (_outboundMessageList.Count == 0)
|
||||
return Logger.WarnReturn(false, "SerializeData(): Data packet contains no messages");
|
||||
|
||||
// Use pooled buffers for coded output streams with reflection hackery, see ProtobufHelper for more info.
|
||||
byte[] buffer = BufferPool.Rent(4096);
|
||||
using RecyclableCodedOutputStream cos = RecyclableCodedOutputStream.CreateInstance(stream);
|
||||
|
||||
CodedOutputStream cos = ProtobufHelper.CodedOutputStreamEx.CreateInstance(stream, buffer);
|
||||
foreach (MessagePackageOut messagePackage in _outboundMessageList)
|
||||
messagePackage.WriteTo(cos);
|
||||
cos.Flush();
|
||||
|
||||
BufferPool.Return(buffer);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(1024);
|
||||
SharedAutoBuffer = new(65536);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
18
src/MHServerEmu.Core/Serialization/ICodedOutputStreamEx.cs
Normal file
18
src/MHServerEmu.Core/Serialization/ICodedOutputStreamEx.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,459 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ 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>
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ namespace MHServerEmu.Games.Achievements
|
|||
AchievementProgressMap.Clear();
|
||||
_scoreCached = false;
|
||||
|
||||
AchievementProgressMap.EnsureCapacity((int)achievementCount);
|
||||
|
||||
for (uint i = 0; i < achievementCount; i++)
|
||||
{
|
||||
uint achievementId = 0;
|
||||
|
|
|
|||
|
|
@ -4716,9 +4716,6 @@ 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");
|
||||
|
||||
|
|
@ -4731,7 +4728,10 @@ namespace MHServerEmu.Games.Entities.Avatars
|
|||
float multiplier = pctXPFromLevelDeltaCurve.GetAt(level - CharacterLevel);
|
||||
multiplier *= tuningProto.PctXPMultiplier;
|
||||
multiplier *= playerXPByDifficultyIndex.GetAt(tuningTable.DifficultyIndex);
|
||||
multiplier *= GetAvatarXPMultiplier();
|
||||
|
||||
if (Game.CustomGameOptions.DisableMissionXPBonuses == false)
|
||||
multiplier *= GetAvatarXPMultiplier();
|
||||
|
||||
multiplier *= GetPartyXPMultiplier(tuningProto);
|
||||
multiplier *= GetLiveTuningXPMultiplier();
|
||||
return multiplier;
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ namespace MHServerEmu.Games.Entities
|
|||
|
||||
private static readonly Logger Logger = LogManager.CreateLogger();
|
||||
|
||||
private readonly InvasiveListNodeCollection<Entity> _entityListNodes = new(3);
|
||||
private InlineArray3<InvasiveListNode<Entity>> _entityListNodes;
|
||||
|
||||
private readonly EventGroup _pendingEvents = new();
|
||||
|
||||
|
|
@ -1307,9 +1307,15 @@ namespace MHServerEmu.Games.Entities
|
|||
};
|
||||
}
|
||||
|
||||
public InvasiveListNode<Entity> GetInvasiveListNode(int listId)
|
||||
public ref InvasiveListNode<Entity> GetInvasiveListNode(int listId)
|
||||
{
|
||||
return _entityListNodes.GetInvasiveListNode(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>>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ namespace MHServerEmu.Games.Entities
|
|||
{
|
||||
Simulated = 0,
|
||||
Locomotion = 1,
|
||||
All = 3,
|
||||
All = 2, // unused?
|
||||
}
|
||||
|
||||
public class EntityInvasiveCollection : InvasiveList<Entity>
|
||||
public sealed class EntityInvasiveCollection : InvasiveList<Entity>
|
||||
{
|
||||
public EntityInvasiveCollection(EntityCollection collectionType, int maxIterators = 8) : base(maxIterators, (int)collectionType) { }
|
||||
public override InvasiveListNode<Entity> GetInvasiveListNode(Entity element, int listId) => element.GetInvasiveListNode(listId);
|
||||
public override ref InvasiveListNode<Entity> GetInvasiveListNode(Entity element, int listId) => ref 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.Iterate())
|
||||
foreach (var entity in LocomotionEntities)
|
||||
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.Iterate())
|
||||
foreach (var entity in SimulatedEntities)
|
||||
if (entity is Agent agent) agent.AIController?.SetIsEnabled(true);
|
||||
|
||||
foreach (var entity in _entityDict.Values)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ 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);
|
||||
|
|
@ -67,6 +69,9 @@ namespace MHServerEmu.Games.Entities
|
|||
if (hotspotProto.DirectApplyToMissilesData?.EvalPropertiesToApply != null || hotspotProto.Negatable)
|
||||
SetFlag(EntityFlags.IsCollidableHotspot, true);
|
||||
|
||||
if (hotspotProto.IntervalPowersRandomTarget)
|
||||
_targetPicker = new(Game.Random);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -796,7 +801,7 @@ namespace MHServerEmu.Games.Entities
|
|||
|
||||
if (hotspotProto.IntervalPowersRandomTarget)
|
||||
{
|
||||
Picker<ulong> picker = new(Game.Random);
|
||||
Picker<ulong> picker = _targetPicker;
|
||||
var hasLOS = TriBool.Undefined;
|
||||
ulong prevTargetId = InvalidId;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,31 +15,24 @@ namespace MHServerEmu.Games.Entities.Physics
|
|||
}
|
||||
}
|
||||
|
||||
public class ForceSystemMemberList : InvasiveList<ForceSystemMember>
|
||||
public sealed class ForceSystemMemberList : InvasiveList<ForceSystemMember>
|
||||
{
|
||||
public ForceSystemMemberList(int maxIterators = 1) : base(maxIterators) { }
|
||||
public override InvasiveListNode<ForceSystemMember> GetInvasiveListNode(ForceSystemMember element, int listId) => element.InvasiveListNode;
|
||||
public override ref InvasiveListNode<ForceSystemMember> GetInvasiveListNode(ForceSystemMember element, int listId) => ref 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 InvasiveListNode<ForceSystemMember> InvasiveListNode { get; private set; }
|
||||
public ref InvasiveListNode<ForceSystemMember> InvasiveListNode { get => ref _invasiveListNode; }
|
||||
|
||||
public ForceSystemMember()
|
||||
{
|
||||
EntityId = 0;
|
||||
Position = Vector3.Zero;
|
||||
Direction = Vector3.Zero;
|
||||
Time = 0.0f;
|
||||
Speed = 0.0f;
|
||||
Acceleration = 0.0f;
|
||||
InvasiveListNode = new();
|
||||
}
|
||||
public ForceSystemMember() { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ namespace MHServerEmu.Games.Entities.Physics
|
|||
|
||||
EntityManager entityManager = _game.EntityManager;
|
||||
|
||||
foreach (var member in forceSystem.Members.Iterate())
|
||||
foreach (var member in forceSystem.Members)
|
||||
{
|
||||
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.Iterate())
|
||||
foreach (var pendingMember in pendingMembers)
|
||||
if (distanceSq > Vector3.DistanceSquared(epicenter, pendingMember.Position))
|
||||
{
|
||||
pendingForce.Members.InsertBefore(member, pendingMember);
|
||||
|
|
|
|||
|
|
@ -2872,7 +2872,7 @@ namespace MHServerEmu.Games.Entities
|
|||
}
|
||||
|
||||
public bool SendRegionRequestQueueCommandToPlayerManager(PrototypeId regionRef, PrototypeId difficultyTierRef,
|
||||
RegionRequestQueueCommandVar command, ulong groupId = 0, ulong targetPlayerDbId = 0)
|
||||
RegionRequestQueueCommandVar command, ulong groupId = 0, ulong targetPlayerDbId = 0, int teamSizeOverride = -1)
|
||||
{
|
||||
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);
|
||||
ServiceMessage.MatchRegionRequestQueueCommand message = new(playerDbId, regionProtoId, difficultyTierProtoId, metaStateProtoId, command, groupId, targetPlayerDbId, teamSizeOverride);
|
||||
ServerManager.Instance.SendMessageToService(GameServiceType.PlayerManager, message);
|
||||
|
||||
return true;
|
||||
|
|
@ -3029,11 +3029,12 @@ namespace MHServerEmu.Games.Entities
|
|||
|
||||
public bool DiscoverMapPosition(Vector3 position, bool syncWithParty = true)
|
||||
{
|
||||
var region = CurrentAvatar?.Region;
|
||||
if (region == null) return Logger.WarnReturn(false, "UpdateMapDiscovery(): region == null");
|
||||
Region region = CurrentAvatar?.Region;
|
||||
if (region == null)
|
||||
return false;
|
||||
|
||||
MapDiscoveryData mapDiscoveryData = GetMapDiscoveryDataForEntity(CurrentAvatar);
|
||||
if (mapDiscoveryData == null) return Logger.WarnReturn(false, "UpdateDiscoveryMap(): mapDiscoveryData == null");
|
||||
if (mapDiscoveryData == null) return Logger.WarnReturn(false, "DiscoverMapPosition(): mapDiscoveryData == null");
|
||||
|
||||
bool reveal = mapDiscoveryData.RevealPosition(this, position);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ 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;
|
||||
|
|
@ -195,12 +198,16 @@ 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.");
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace MHServerEmu.Games.Navi
|
|||
|
||||
public void Release()
|
||||
{
|
||||
foreach (var triangle in TriangleList.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
if (triangle.PathingFlags.HasFlag(filterFlags))
|
||||
{
|
||||
var p0 = triangle.PointCW(0);
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ namespace MHServerEmu.Games.Navi
|
|||
|
||||
private void ReverseMarkupMesh()
|
||||
{
|
||||
foreach (var triangle in TriangleList.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
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.Iterate())
|
||||
foreach (var triangle in TriangleList)
|
||||
{
|
||||
triangle.ClearFlag(NaviTriangleFlags.Markup);
|
||||
triangle.PathingFlags = PathFlags.None;
|
||||
|
|
|
|||
|
|
@ -11,21 +11,22 @@ namespace MHServerEmu.Games.Navi
|
|||
Markup = 1 << 1,
|
||||
}
|
||||
|
||||
public class TriangleList : InvasiveList<NaviTriangle>
|
||||
public sealed class TriangleList : InvasiveList<NaviTriangle>
|
||||
{
|
||||
public TriangleList(int maxIterators = 1) : base(maxIterators) { }
|
||||
public override InvasiveListNode<NaviTriangle> GetInvasiveListNode(NaviTriangle element, int listId) => element.InvasiveListNode;
|
||||
public override ref InvasiveListNode<NaviTriangle> GetInvasiveListNode(NaviTriangle element, int listId) => ref 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 InvasiveListNode<NaviTriangle> InvasiveListNode { get; private set; }
|
||||
public ref InvasiveListNode<NaviTriangle> InvasiveListNode { get => ref _invasiveListNode; }
|
||||
|
||||
public ContentFlagCounts ContentFlagCounts; // ContentFlagCounts needs to be a field for Clear() calls
|
||||
|
||||
|
|
@ -34,7 +35,6 @@ namespace MHServerEmu.Games.Navi
|
|||
Edges[0] = e0;
|
||||
Edges[1] = e1;
|
||||
Edges[2] = e2;
|
||||
InvasiveListNode = new();
|
||||
UpdateEdgeSideFlags();
|
||||
Attach();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
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;
|
||||
|
|
@ -54,7 +56,25 @@ namespace MHServerEmu.Games.Network
|
|||
{
|
||||
List<(ulong, ulong)> propertyList = migrationData.GetOrCreatePropertyList(entity.DatabaseUniqueId);
|
||||
propertyList.Clear();
|
||||
entity.Properties.GetPropertiesForMigration(propertyList);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
private static void RestoreProperties(MigrationData migrationData, Entity entity)
|
||||
|
|
|
|||
|
|
@ -259,9 +259,18 @@ 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);
|
||||
_dbAccount.Player.ArchiveData = archive.AccessAutoBuffer().ToArray();
|
||||
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();
|
||||
}
|
||||
|
||||
// Save last town as a separate database field to be able to access it without deserializing the player entity
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ 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;
|
||||
|
|
|
|||
|
|
@ -216,6 +216,10 @@ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -844,7 +844,7 @@ namespace MHServerEmu.Games.Properties
|
|||
return success;
|
||||
}
|
||||
|
||||
public void GetPropertiesForMigration(List<(ulong, ulong)> propertyList)
|
||||
public void GetPropertiesForMigration(List<(ulong, ulong)> propertyList, PropertyEnum propertyToIgnore = PropertyEnum.Invalid)
|
||||
{
|
||||
PropertyEnum prevProperty = PropertyEnum.Invalid;
|
||||
PropertyInfoPrototype propInfoProto = null;
|
||||
|
|
@ -860,9 +860,9 @@ namespace MHServerEmu.Games.Properties
|
|||
prevProperty = propertyEnum;
|
||||
}
|
||||
|
||||
// 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).
|
||||
// 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).
|
||||
// This can be removed if we ever start migrating runtime-only conditions.
|
||||
if (propertyEnum == PropertyEnum.Endurance)
|
||||
if (propertyEnum == propertyToIgnore)
|
||||
continue;
|
||||
|
||||
// Migrate properties that are not saved to the database, but are supposed to be replicated for transfer
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
ulong groupId, RegionRequestQueueCommandVar command, int teamSizeOverride = -1)
|
||||
{
|
||||
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);
|
||||
_owner.SendRegionRequestQueueCommandToPlayerManager(regionRef, difficultyTierRef, command, groupId, 0, teamSizeOverride);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ 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; }
|
||||
|
||||
|
|
@ -42,6 +44,16 @@ 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()
|
||||
|
|
@ -92,7 +104,7 @@ namespace MHServerEmu.PlayerManagement.Auth
|
|||
}
|
||||
|
||||
// Verify credentials
|
||||
AuthStatusCode statusCode = AccountManager.TryGetAccountByLoginDataPB(loginDataPB, _playerManager.Config.UseWhitelist, out DBAccount account);
|
||||
AuthStatusCode statusCode = AccountManager.TryGetAccountByLoginDataPB(loginDataPB, WhitelistEnabled, out DBAccount account);
|
||||
|
||||
if (statusCode != AuthStatusCode.Success)
|
||||
return statusCode;
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
|
|||
QueueParams = queueParams;
|
||||
|
||||
int[] teamLimits = Queue.Prototype.TeamLimits;
|
||||
|
||||
if (teamLimits.HasValue())
|
||||
{
|
||||
for (int i = 0; i < teamLimits.Length; i++)
|
||||
{
|
||||
MatchTeam team = new(i, teamLimits[i]);
|
||||
int teamLimit = (queueParams.TeamSizeOverride > 0) ? queueParams.TeamSizeOverride : teamLimits[i];
|
||||
MatchTeam team = new(i, teamLimit);
|
||||
_teams.Add(team);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
|
|||
}
|
||||
|
||||
public void HandleCommand(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef,
|
||||
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId)
|
||||
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId, int teamSizeOverride = -1)
|
||||
{
|
||||
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);
|
||||
OnAddToQueue(regionRef, difficultyTierRef, metaStateRef, command, teamSizeOverride);
|
||||
break;
|
||||
|
||||
case RegionRequestQueueCommandVar.eRRQC_RemoveFromQueue:
|
||||
|
|
@ -53,7 +53,8 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
|
|||
}
|
||||
}
|
||||
|
||||
private bool OnAddToQueue(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef, RegionRequestQueueCommandVar command)
|
||||
private bool OnAddToQueue(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef,
|
||||
RegionRequestQueueCommandVar command, int teamSizeOverride = -1)
|
||||
{
|
||||
RegionRequestQueue queue = PlayerManagerService.Instance.RegionRequestQueueManager.GetRegionRequestQueue(regionRef);
|
||||
if (queue == null)
|
||||
|
|
@ -74,7 +75,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);
|
||||
RegionRequestQueueParams queueParams = new(difficultyTierRef, metaStateRef, command == RegionRequestQueueCommandVar.eRRQC_AddToQueueBypass, teamSizeOverride);
|
||||
RegionRequestGroup group = RegionRequestGroup.Create(queue, queueParams, _player, party);
|
||||
|
||||
if (group == null)
|
||||
|
|
|
|||
|
|
@ -10,22 +10,24 @@ 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)
|
||||
public RegionRequestQueueParams(PrototypeId difficultyTierRef, PrototypeId metaStateRef, bool isBypass, int teamSizeOverride)
|
||||
{
|
||||
DifficultyTierRef = difficultyTierRef;
|
||||
MetaStateRef = metaStateRef;
|
||||
IsBypass = isBypass;
|
||||
TeamSizeOverride = teamSizeOverride;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"difficulty={DifficultyTierRef.GetNameFormatted()}, metaState={MetaStateRef.GetNameFormatted()}, isBypass={IsBypass}";
|
||||
return $"difficulty={DifficultyTierRef.GetNameFormatted()}, metaState={MetaStateRef.GetNameFormatted()}, isBypass={IsBypass}, teamSizeOverride={TeamSizeOverride}";
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(DifficultyTierRef, MetaStateRef, IsBypass);
|
||||
return HashCode.Combine(DifficultyTierRef, MetaStateRef, IsBypass, TeamSizeOverride);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
|
|
@ -40,7 +42,8 @@ namespace MHServerEmu.PlayerManagement.Matchmaking
|
|||
{
|
||||
return DifficultyTierRef == other.DifficultyTierRef &&
|
||||
MetaStateRef == other.MetaStateRef &&
|
||||
IsBypass == other.IsBypass;
|
||||
IsBypass == other.IsBypass &&
|
||||
TeamSizeOverride == other.TeamSizeOverride;
|
||||
}
|
||||
|
||||
public static bool operator ==(RegionRequestQueueParams left, RegionRequestQueueParams right)
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ 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;
|
||||
|
|
@ -425,9 +429,10 @@ 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);
|
||||
player?.ReceiveRegionRequestQueueCommand(regionRef, difficultyTierRef, metaStateRef, command, regionRequestGroupId, targetPlayerDbId, teamSizeOverride);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -627,6 +632,13 @@ namespace MHServerEmu.PlayerManagement.Network
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool OnSetWhitelistEnabled(in ServiceMessage.SetWhitelistEnabled setWhitelistEnabled)
|
||||
{
|
||||
_playerManager.SessionManager.SetWhitelistEnabled(setWhitelistEnabled.Enable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; } = 10000;
|
||||
public int MaxLoginQueueClients { get; private set; } = 8192;
|
||||
public bool EnableTownPlayerLimit { get; private set; } = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ namespace MHServerEmu.PlayerManagement
|
|||
/// </summary>
|
||||
public PlayerManagerService()
|
||||
{
|
||||
Config = ConfigManager.Instance.GetConfig<PlayerManagerConfig>();
|
||||
|
||||
_serviceMailbox = new(this);
|
||||
|
||||
SessionManager = new(this);
|
||||
|
|
@ -62,8 +64,6 @@ namespace MHServerEmu.PlayerManagement
|
|||
RegionRequestQueueManager = new(this);
|
||||
|
||||
EventScheduler = new();
|
||||
|
||||
Config = ConfigManager.Instance.GetConfig<PlayerManagerConfig>();
|
||||
}
|
||||
|
||||
#region IGameService Implementation
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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;
|
||||
|
|
@ -15,24 +16,55 @@ 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 Queue<IFrontendClient> _loginQueue = new();
|
||||
private readonly Queue<IFrontendClient> _highPriorityLoginQueue = 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;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
|
@ -72,13 +104,29 @@ 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)
|
||||
|
|
@ -98,21 +146,29 @@ namespace MHServerEmu.PlayerManagement.Players
|
|||
// High priority queue always ignores server capacity
|
||||
if (IsClientHighPriority(client))
|
||||
{
|
||||
_highPriorityLoginQueue.Enqueue(client);
|
||||
_highPriorityQueue.Enqueue(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_loginQueue.Count >= maxLoginQueueClients)
|
||||
if (_queueNodes.TryPop(out LinkedListNode<IFrontendClient> queueNode) == false)
|
||||
{
|
||||
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");
|
||||
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");
|
||||
client.Disconnect();
|
||||
RemoveClientSession(client);
|
||||
continue;
|
||||
}
|
||||
|
||||
_loginQueue.Enqueue(client);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
Logger.Info($"Accepted client [{client}] into the login queue");
|
||||
}
|
||||
|
|
@ -127,37 +183,40 @@ namespace MHServerEmu.PlayerManagement.Players
|
|||
int availableCapacity = totalCapacity - _playerManager.ClientManager.PlayerCount - _pendingClients.Count;
|
||||
|
||||
// Let clients from the high priority queue in first ignoring capacity
|
||||
while (_highPriorityLoginQueue.Count > 0)
|
||||
while (_highPriorityQueue.Count > 0)
|
||||
{
|
||||
IFrontendClient client = _highPriorityLoginQueue.Dequeue();
|
||||
ProcessQueuedClient(client, ref availableCapacity);
|
||||
IFrontendClient client = _highPriorityQueue.Dequeue();
|
||||
ProcessQueuedClient(client, ref availableCapacity, false);
|
||||
}
|
||||
|
||||
// Let clients from the normal login queue, check available capacity if enabled
|
||||
while (_loginQueue.Count > 0 && (totalCapacity <= 0 || availableCapacity > 0))
|
||||
// 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)
|
||||
{
|
||||
IFrontendClient client = _loginQueue.Dequeue();
|
||||
ProcessQueuedClient(client, ref availableCapacity);
|
||||
_statusBuilder.SetNumberOfPlayersInLine((ulong)playersInLine);
|
||||
|
||||
TimeSpan now = Clock.UnixTime;
|
||||
ulong nextPlaceInLine = 1;
|
||||
|
||||
UpdateQueueStatus(_reconnectQueue, ref nextPlaceInLine, now);
|
||||
UpdateQueueStatus(_defaultQueue, ref nextPlaceInLine, now);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Users with elevated privileges (moderators / admins) have high priority
|
||||
if (((IDBAccountOwner)client).Account.UserLevel > AccountUserLevel.User)
|
||||
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))
|
||||
return true;
|
||||
|
||||
// Add more cases as needed
|
||||
|
|
@ -165,8 +224,27 @@ namespace MHServerEmu.PlayerManagement.Players
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool ProcessQueuedClient(IFrontendClient client, ref int availableCapacity)
|
||||
private void ProcessQueue(LinkedList<IFrontendClient> queue, int totalCapacity, ref int availableCapacity, bool allowReconnect)
|
||||
{
|
||||
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");
|
||||
|
|
@ -178,10 +256,8 @@ 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);
|
||||
|
||||
client.SendMessage(MuxChannel, SessionEncryptionChanged.CreateBuilder()
|
||||
.SetRandomNumberIndex(0)
|
||||
.SetEncryptedRandomNumber(ByteString.Empty)
|
||||
.Build());
|
||||
// Gazillion never finished implementing encryption, so the SessionEncryptionChanged message is just a dummy we can cache and reuse.
|
||||
client.SendMessage(MuxChannel, SessionEncryptionChangedMessage);
|
||||
|
||||
Logger.Info($"Client [{client}] passed the login queue");
|
||||
|
||||
|
|
@ -190,6 +266,47 @@ 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;
|
||||
|
|
|
|||
|
|
@ -814,9 +814,9 @@ namespace MHServerEmu.PlayerManagement.Players
|
|||
}
|
||||
|
||||
public void ReceiveRegionRequestQueueCommand(PrototypeId regionRef, PrototypeId difficultyTierRef, PrototypeId metaStateRef,
|
||||
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId)
|
||||
RegionRequestQueueCommandVar command, ulong regionRequestGroupId, ulong targetPlayerDbId, int teamSizeOverride)
|
||||
{
|
||||
_regionRequestQueueCommandHandler.HandleCommand(regionRef, difficultyTierRef, metaStateRef, command, regionRequestGroupId, targetPlayerDbId);
|
||||
_regionRequestQueueCommandHandler.HandleCommand(regionRef, difficultyTierRef, metaStateRef, command, regionRequestGroupId, targetPlayerDbId, teamSizeOverride);
|
||||
}
|
||||
|
||||
public void AddToChatRoom(ChatRoomTypes roomType, ulong roomId)
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ namespace MHServerEmu.Commands.Implementations
|
|||
LootManager lootGenerator = playerConnection.Game.LootManager;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
lootGenerator.GiveItem(itemProtoRef, LootContext.Drop, player);
|
||||
lootGenerator.GiveItem(itemProtoRef, LootContext.CashShop, player);
|
||||
Logger.Debug($"GiveItem(): {itemProtoRef.GetName()}[{count}] to {player}");
|
||||
|
||||
return string.Empty;
|
||||
|
|
|
|||
67
src/MHServerEmu/Commands/Implementations/PvPCommands.cs
Normal file
67
src/MHServerEmu/Commands/Implementations/PvPCommands.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -105,6 +105,29 @@ 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")]
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ UseWhitelist=false
|
|||
ShowNewsOnLogin=false
|
||||
NewsUrl=http://localhost/news/index.html
|
||||
ServerCapacity=0
|
||||
MaxLoginQueueClients=10000
|
||||
MaxLoginQueueClients=8192
|
||||
EnableTownPlayerLimit=false
|
||||
|
||||
[SQLiteDBManager]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue