mirror of
https://github.com/RaidMax/IW4M-Admin.git
synced 2025-06-10 15:20:48 -05:00
implement new eventing system
This commit is contained in:
12
SharedLibraryCore/Events/CoreEvent.cs
Normal file
12
SharedLibraryCore/Events/CoreEvent.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace SharedLibraryCore.Events;
|
||||
|
||||
public abstract class CoreEvent
|
||||
{
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
public Guid? CorrelationId { get; init; }
|
||||
public object Source { get; init; }
|
||||
public DateTimeOffset CreatedAt { get; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset? ProcessedAt { get; set; }
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Dtos;
|
||||
|
||||
namespace SharedLibraryCore.Events
|
||||
{
|
||||
public class EventApi
|
||||
{
|
||||
private const int MaxEvents = 25;
|
||||
private static readonly ConcurrentQueue<EventInfo> RecentEvents = new ConcurrentQueue<EventInfo>();
|
||||
|
||||
public static IEnumerable<EventInfo> GetEvents(bool shouldConsume)
|
||||
{
|
||||
var eventList = RecentEvents.ToArray();
|
||||
|
||||
// clear queue if events should be consumed
|
||||
if (shouldConsume)
|
||||
{
|
||||
RecentEvents.Clear();
|
||||
}
|
||||
|
||||
return eventList;
|
||||
}
|
||||
|
||||
public static void OnGameEvent(GameEvent gameEvent)
|
||||
{
|
||||
var E = gameEvent;
|
||||
// don't want to clog up the api with unknown events
|
||||
if (E.Type == GameEvent.EventType.Unknown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var apiEvent = new EventInfo
|
||||
{
|
||||
ExtraInfo = E.Extra?.ToString() ?? E.Data,
|
||||
GameInfo = new EntityInfo
|
||||
{
|
||||
Name = E.Owner.GameName.ToString(),
|
||||
Id = (int)E.Owner.GameName
|
||||
},
|
||||
OwnerEntity = new EntityInfo
|
||||
{
|
||||
Name = E.Owner.Hostname,
|
||||
Id = E.Owner.EndPoint
|
||||
},
|
||||
OriginEntity = E.Origin == null
|
||||
? null
|
||||
: new EntityInfo
|
||||
{
|
||||
Id = E.Origin.ClientId,
|
||||
Name = E.Origin.Name
|
||||
},
|
||||
TargetEntity = E.Target == null
|
||||
? null
|
||||
: new EntityInfo
|
||||
{
|
||||
Id = E.Target.ClientId,
|
||||
Name = E.Target.Name
|
||||
},
|
||||
EventType = new EntityInfo
|
||||
{
|
||||
Id = (int)E.Type,
|
||||
Name = E.Type.ToString()
|
||||
},
|
||||
EventTime = E.Time
|
||||
};
|
||||
|
||||
// add the new event to the list
|
||||
AddNewEvent(apiEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds event to the list and removes first added if reached max capacity
|
||||
/// </summary>
|
||||
/// <param name="info">EventInfo to add</param>
|
||||
private static void AddNewEvent(EventInfo info)
|
||||
{
|
||||
// remove the first added event
|
||||
if (RecentEvents.Count >= MaxEvents)
|
||||
{
|
||||
RecentEvents.TryDequeue(out _);
|
||||
}
|
||||
|
||||
RecentEvents.Enqueue(info);
|
||||
}
|
||||
}
|
||||
}
|
44
SharedLibraryCore/Events/EventExtensions.cs
Normal file
44
SharedLibraryCore/Events/EventExtensions.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Events;
|
||||
|
||||
public static class EventExtensions
|
||||
{
|
||||
public static Task InvokeAsync<TEventType>(this Func<TEventType, CancellationToken, Task> function,
|
||||
TEventType eventArgType, CancellationToken token)
|
||||
{
|
||||
if (function is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return Task.WhenAll(function.GetInvocationList().Cast<Func<TEventType, CancellationToken, Task>>()
|
||||
.Select(x => RunHandler(x, eventArgType, token)));
|
||||
}
|
||||
|
||||
private static async Task RunHandler<TEventType>(Func<TEventType, CancellationToken, Task> handler,
|
||||
TEventType eventArgType, CancellationToken token)
|
||||
{
|
||||
if (token == CancellationToken.None)
|
||||
{
|
||||
// special case to allow tasks like request after delay to run longer
|
||||
await handler(eventArgType, token);
|
||||
}
|
||||
|
||||
using var timeoutToken = new CancellationTokenSource(Utilities.DefaultCommandTimeout);
|
||||
using var tokenSource =
|
||||
CancellationTokenSource.CreateLinkedTokenSource(token, timeoutToken.Token);
|
||||
|
||||
try
|
||||
{
|
||||
await handler(eventArgType, tokenSource.Token);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
@ -4,5 +4,5 @@ namespace SharedLibraryCore.Events.Game;
|
||||
|
||||
public abstract class GameEventV2 : GameEvent
|
||||
{
|
||||
public IGameServer Server { get; init; }
|
||||
public IGameServer Server => Owner;
|
||||
}
|
||||
|
@ -5,10 +5,11 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Events;
|
||||
|
||||
namespace SharedLibraryCore
|
||||
{
|
||||
public class GameEvent
|
||||
public class GameEvent : CoreEvent
|
||||
{
|
||||
public enum EventFailReason
|
||||
{
|
||||
@ -133,6 +134,8 @@ namespace SharedLibraryCore
|
||||
/// connection was restored to a server (the server began responding again)
|
||||
/// </summary>
|
||||
ConnectionRestored,
|
||||
|
||||
SayTeam = 99,
|
||||
|
||||
// events "generated" by clients
|
||||
/// <summary>
|
||||
@ -246,7 +249,7 @@ namespace SharedLibraryCore
|
||||
/// team info printed out by game script
|
||||
/// </summary>
|
||||
JoinTeam = 304,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// used for community generated plugin events
|
||||
/// </summary>
|
||||
@ -267,7 +270,7 @@ namespace SharedLibraryCore
|
||||
public GameEvent()
|
||||
{
|
||||
Time = DateTime.UtcNow;
|
||||
Id = GetNextEventId();
|
||||
IncrementalId = GetNextEventId();
|
||||
}
|
||||
|
||||
~GameEvent()
|
||||
@ -275,8 +278,6 @@ namespace SharedLibraryCore
|
||||
_eventFinishedWaiter.Dispose();
|
||||
}
|
||||
|
||||
public EventSource Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// suptype of the event for more detailed classification
|
||||
/// </summary>
|
||||
@ -293,11 +294,10 @@ namespace SharedLibraryCore
|
||||
public bool IsRemote { get; set; }
|
||||
public object Extra { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
public long Id { get; }
|
||||
public long IncrementalId { get; }
|
||||
public EventFailReason FailReason { get; set; }
|
||||
public bool Failed => FailReason != EventFailReason.None;
|
||||
public Guid CorrelationId { get; set; } = Guid.NewGuid();
|
||||
public List<string> Output { get; set; } = new List<string>();
|
||||
public List<string> Output { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the event should block until it is complete
|
||||
@ -328,23 +328,31 @@ namespace SharedLibraryCore
|
||||
/// <returns>waitable task </returns>
|
||||
public async Task<GameEvent> WaitAsync(TimeSpan timeSpan, CancellationToken token)
|
||||
{
|
||||
var processed = false;
|
||||
Utilities.DefaultLogger.LogDebug("Begin wait for event {Id}", Id);
|
||||
try
|
||||
if (FailReason == EventFailReason.Timeout)
|
||||
{
|
||||
processed = await _eventFinishedWaiter.WaitAsync(timeSpan, token);
|
||||
return this;
|
||||
}
|
||||
|
||||
catch (TaskCanceledException)
|
||||
var processed = false;
|
||||
Utilities.DefaultLogger.LogDebug("Begin wait for {Name}, {Type}, {Id}", Type, GetType().Name,
|
||||
IncrementalId);
|
||||
|
||||
try
|
||||
{
|
||||
await _eventFinishedWaiter.WaitAsync(timeSpan, token);
|
||||
processed = true;
|
||||
}
|
||||
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
processed = false;
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (_eventFinishedWaiter.CurrentCount == 0)
|
||||
if (processed)
|
||||
{
|
||||
_eventFinishedWaiter.Release();
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace SharedLibraryCore.Events.Management;
|
||||
|
||||
public class ClientPersistentIdReceiveEvent : ClientStateEvent
|
||||
{
|
||||
public ClientPersistentIdReceiveEvent(EFClient client, string persistentId)
|
||||
{
|
||||
Client = client;
|
||||
PersistentId = persistentId;
|
||||
}
|
||||
|
||||
public string PersistentId { get; init; }
|
||||
}
|
120
SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs
Normal file
120
SharedLibraryCore/Interfaces/Events/IGameEventSubscriptions.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Events;
|
||||
using SharedLibraryCore.Events.Game;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces.Events;
|
||||
|
||||
public interface IGameEventSubscriptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when game log prints that match has started
|
||||
/// <example>InitGame</example>
|
||||
/// <value><see cref="MatchStartEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<MatchStartEvent, CancellationToken, Task> MatchStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints that match has ended
|
||||
/// <example>ShutdownGame:</example>
|
||||
/// <value><see cref="MatchEndEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<MatchEndEvent, CancellationToken, Task> MatchEnded;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log printed that client has entered the match
|
||||
/// <remarks>J;clientNetworkId;clientSlotNumber;clientName</remarks>
|
||||
/// <example>J;110000100000000;0;bot</example>
|
||||
/// <value><see cref="ClientEnterMatchEvent"/></value>
|
||||
/// </summary>
|
||||
public static event Func<ClientEnterMatchEvent, CancellationToken, Task> ClientEnteredMatch;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints that client has exited the match
|
||||
/// <remarks>Q;clientNetworkId;clientSlotNumber;clientName</remarks>
|
||||
/// <example>Q;110000100000000;0;bot</example>
|
||||
/// <value><see cref="ClientExitMatchEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientExitMatchEvent, CancellationToken, Task> ClientExitedMatch;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints that client has joined a team
|
||||
/// <remarks>JT;clientNetworkId;clientSlotNumber;clientTeam;clientName</remarks>
|
||||
/// <example>JT;110000100000000;0;axis;bot</example>
|
||||
/// <value><see cref="ClientJoinTeamEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientJoinTeamEvent, CancellationToken, Task> ClientJoinedTeam;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints that client has been damaged
|
||||
/// <remarks>D;victimNetworkId;victimSlotNumber;victimTeam;victimName;attackerNetworkId;attackerSlotNumber;attackerTeam;attackerName;weapon;damage;meansOfDeath;hitLocation</remarks>
|
||||
/// <example>D;110000100000000;17;axis;bot_0;110000100000001;4;allies;bot_1;scar_mp;38;MOD_HEAD_SHOT;head</example>
|
||||
/// <value><see cref="ClientDamageEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientDamageEvent, CancellationToken, Task> ClientDamaged;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints that client has been killed
|
||||
/// <remarks>K;victimNetworkId;victimSlotNumber;victimTeam;victimName;attackerNetworkId;attackerSlotNumber;attackerTeam;attackerName;weapon;damage;meansOfDeath;hitLocation</remarks>
|
||||
/// <example>K;110000100000000;17;axis;bot_0;110000100000001;4;allies;bot_1;scar_mp;100;MOD_HEAD_SHOT;head</example>
|
||||
/// <value><see cref="ClientKillEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientKillEvent, CancellationToken, Task> ClientKilled;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints that client entered a chat message
|
||||
/// <remarks>say;clientNetworkId;clientSlotNumber;clientName;message</remarks>
|
||||
/// <example>say;110000100000000;0;bot;hello world!</example>
|
||||
/// <value><see cref="ClientMessageEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientMessageEvent, CancellationToken, Task> ClientMessaged;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints that client entered a command (chat message prefixed with command character(s))
|
||||
/// <remarks>say;clientNetworkId;clientSlotNumber;clientName;command</remarks>
|
||||
/// <example>say;110000100000000;0;bot;!command</example>
|
||||
/// <value><see cref="ClientCommandEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientCommandEvent, CancellationToken, Task> ClientEnteredCommand;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when game log prints user generated script event
|
||||
/// <remarks>GSE;data</remarks>
|
||||
/// <example>GSE;loadBank=1</example>
|
||||
/// <value><see cref="GameScriptEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<GameScriptEvent, CancellationToken, Task> ScriptEventTriggered;
|
||||
|
||||
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||
{
|
||||
return coreEvent switch
|
||||
{
|
||||
MatchStartEvent matchStartEvent => MatchStarted?.InvokeAsync(matchStartEvent, token) ?? Task.CompletedTask,
|
||||
MatchEndEvent matchEndEvent => MatchEnded?.InvokeAsync(matchEndEvent, token) ?? Task.CompletedTask,
|
||||
ClientEnterMatchEvent clientEnterMatchEvent => ClientEnteredMatch?.InvokeAsync(clientEnterMatchEvent, token) ?? Task.CompletedTask,
|
||||
ClientExitMatchEvent clientExitMatchEvent => ClientExitedMatch?.InvokeAsync(clientExitMatchEvent, token) ?? Task.CompletedTask,
|
||||
ClientJoinTeamEvent clientJoinTeamEvent => ClientJoinedTeam?.InvokeAsync(clientJoinTeamEvent, token) ?? Task.CompletedTask,
|
||||
ClientKillEvent clientKillEvent => ClientKilled?.InvokeAsync(clientKillEvent, token) ?? Task.CompletedTask,
|
||||
ClientDamageEvent clientDamageEvent => ClientDamaged?.InvokeAsync(clientDamageEvent, token) ?? Task.CompletedTask,
|
||||
ClientCommandEvent clientCommandEvent => ClientEnteredCommand?.InvokeAsync(clientCommandEvent, token) ?? Task.CompletedTask,
|
||||
ClientMessageEvent clientMessageEvent => ClientMessaged?.InvokeAsync(clientMessageEvent, token) ?? Task.CompletedTask,
|
||||
GameScriptEvent gameScriptEvent => ScriptEventTriggered?.InvokeAsync(gameScriptEvent, token) ?? Task.CompletedTask,
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
|
||||
static void ClearEventInvocations()
|
||||
{
|
||||
MatchStarted = null;
|
||||
MatchEnded = null;
|
||||
ClientEnteredMatch = null;
|
||||
ClientExitedMatch = null;
|
||||
ClientJoinedTeam = null;
|
||||
ClientDamaged = null;
|
||||
ClientKilled = null;
|
||||
ClientMessaged = null;
|
||||
ClientEnteredCommand = null;
|
||||
ScriptEventTriggered = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Events;
|
||||
using SharedLibraryCore.Events.Server;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces.Events;
|
||||
|
||||
public interface IGameServerEventSubscriptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when IW4MAdmin starts monitoring a game server
|
||||
/// <value><see cref="MonitorStartEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<MonitorStartEvent, CancellationToken, Task> MonitoringStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when IW4MAdmin stops monitoring a game server
|
||||
/// <value><see cref="MonitorStopEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<MonitorStopEvent, CancellationToken, Task> MonitoringStopped;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when communication was interrupted with a game server
|
||||
/// <value><see cref="ConnectionInterruptEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ConnectionInterruptEvent, CancellationToken, Task> ConnectionInterrupted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when communication was resumed with a game server
|
||||
/// <value><see cref="ConnectionRestoreEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ConnectionRestoreEvent, CancellationToken, Task> ConnectionRestored;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when updated client data was received from a game server
|
||||
/// <value><see cref="ClientDataUpdateEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientDataUpdateEvent, CancellationToken, Task> ClientDataUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a command was executed on a game server
|
||||
/// <value><see cref="ServerCommandExecuteEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ServerCommandExecuteEvent, CancellationToken, Task> ServerCommandExecuted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a server value is requested for a game server
|
||||
/// <value><see cref="ServerValueRequestEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ServerValueRequestEvent, CancellationToken, Task> ServerValueRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a server value was received from a game server (success or fail)
|
||||
/// <value><see cref="ServerValueReceiveEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ServerValueReceiveEvent, CancellationToken, Task> ServerValueReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a request to set a server value on a game server is received
|
||||
/// <value><see cref="ServerValueSetRequestEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ServerValueSetRequestEvent, CancellationToken, Task> ServerValueSetRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a setting server value on a game server is completed (success or fail)
|
||||
/// <value><see cref="ServerValueSetRequestEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ServerValueSetCompleteEvent, CancellationToken, Task> ServerValueSetCompleted;
|
||||
|
||||
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||
{
|
||||
return coreEvent switch
|
||||
{
|
||||
MonitorStartEvent monitoringStartEvent => MonitoringStarted?.InvokeAsync(monitoringStartEvent, token) ?? Task.CompletedTask,
|
||||
MonitorStopEvent monitorStopEvent => MonitoringStopped?.InvokeAsync(monitorStopEvent, token) ?? Task.CompletedTask,
|
||||
ConnectionInterruptEvent connectionInterruptEvent => ConnectionInterrupted?.InvokeAsync(connectionInterruptEvent, token) ?? Task.CompletedTask,
|
||||
ConnectionRestoreEvent connectionRestoreEvent => ConnectionRestored?.InvokeAsync(connectionRestoreEvent, token) ?? Task.CompletedTask,
|
||||
ClientDataUpdateEvent clientDataUpdateEvent => ClientDataUpdated?.InvokeAsync(clientDataUpdateEvent, token) ?? Task.CompletedTask,
|
||||
ServerCommandExecuteEvent dataReceiveEvent => ServerCommandExecuted?.InvokeAsync(dataReceiveEvent, token) ?? Task.CompletedTask,
|
||||
ServerValueRequestEvent serverValueRequestEvent => ServerValueRequested?.InvokeAsync(serverValueRequestEvent, token) ?? Task.CompletedTask,
|
||||
ServerValueReceiveEvent serverValueReceiveEvent => ServerValueReceived?.InvokeAsync(serverValueReceiveEvent, token) ?? Task.CompletedTask,
|
||||
ServerValueSetRequestEvent serverValueSetRequestEvent => ServerValueSetRequested?.InvokeAsync(serverValueSetRequestEvent, token) ?? Task.CompletedTask,
|
||||
ServerValueSetCompleteEvent serverValueSetCompleteEvent => ServerValueSetCompleted?.InvokeAsync(serverValueSetCompleteEvent, token) ?? Task.CompletedTask,
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
|
||||
static void ClearEventInvocations()
|
||||
{
|
||||
MonitoringStarted = null;
|
||||
MonitoringStopped = null;
|
||||
ConnectionInterrupted = null;
|
||||
ConnectionRestored = null;
|
||||
ClientDataUpdated = null;
|
||||
ServerCommandExecuted = null;
|
||||
ServerValueReceived = null;
|
||||
ServerValueRequested = null;
|
||||
ServerValueSetRequested = null;
|
||||
ServerValueSetCompleted = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Events;
|
||||
using SharedLibraryCore.Events.Management;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces.Events;
|
||||
|
||||
public interface IManagementEventSubscriptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Raised when <see cref="IManager"/> is loading
|
||||
/// </summary>
|
||||
static event Func<IManager, CancellationToken, Task> Load;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when <see cref="IManager"/> is restarting
|
||||
/// </summary>
|
||||
static event Func<IManager, CancellationToken, Task> Unload;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when client enters a tracked state
|
||||
/// <remarks>
|
||||
/// At this point, the client is not guaranteed to be allowed to play on the server.
|
||||
/// See <see cref="ClientStateAuthorized"/> for final state.
|
||||
/// </remarks>
|
||||
/// <value><see cref="ClientStateInitializeEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientStateInitializeEvent, CancellationToken, Task> ClientStateInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when client enters an authorized state (valid data and no bans)
|
||||
/// <value><see cref="ClientStateAuthorizeEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientStateAuthorizeEvent, CancellationToken, Task> ClientStateAuthorized;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when client is no longer tracked (unknown state)
|
||||
/// <remarks>At this point any references to the client should be dropped</remarks>
|
||||
/// <value><see cref="ClientStateDisposeEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientStateDisposeEvent, CancellationToken, Task> ClientStateDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a client receives a penalty
|
||||
/// <value><see cref="ClientPenaltyEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientPenaltyEvent, CancellationToken, Task> ClientPenaltyAdministered;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a client penalty is revoked (eg unflag/unban)
|
||||
/// <value><see cref="ClientPenaltyRevokeEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientPenaltyRevokeEvent, CancellationToken, Task> ClientPenaltyRevoked;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a client command is executed (after completion of the command)
|
||||
/// <value><see cref="ClientExecuteCommandEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientExecuteCommandEvent, CancellationToken, Task> ClientCommandExecuted;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a client's permission level changes
|
||||
/// <value><see cref="ClientPermissionChangeEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientPermissionChangeEvent, CancellationToken, Task> ClientPermissionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a client logs in to the webfront or ingame
|
||||
/// <value><see cref="LoginEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<LoginEvent, CancellationToken, Task> ClientLoggedIn;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a client logs out of the webfront
|
||||
/// <value><see cref="LogoutEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<LogoutEvent, CancellationToken, Task> ClientLoggedOut;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a client's persistent id (stats file marker) is received
|
||||
/// <value><see cref="ClientPersistentIdReceiveEvent"/></value>
|
||||
/// </summary>
|
||||
static event Func<ClientPersistentIdReceiveEvent, CancellationToken, Task> ClientPersistentIdReceived;
|
||||
|
||||
static Task InvokeEventAsync(CoreEvent coreEvent, CancellationToken token)
|
||||
{
|
||||
return coreEvent switch
|
||||
{
|
||||
ClientStateInitializeEvent clientStateInitializeEvent => ClientStateInitialized?.InvokeAsync(
|
||||
clientStateInitializeEvent, token) ?? Task.CompletedTask,
|
||||
ClientStateDisposeEvent clientStateDisposedEvent => ClientStateDisposed?.InvokeAsync(
|
||||
clientStateDisposedEvent, token) ?? Task.CompletedTask,
|
||||
ClientStateAuthorizeEvent clientStateAuthorizeEvent => ClientStateAuthorized?.InvokeAsync(
|
||||
clientStateAuthorizeEvent, token) ?? Task.CompletedTask,
|
||||
ClientPenaltyRevokeEvent clientPenaltyRevokeEvent => ClientPenaltyRevoked?.InvokeAsync(
|
||||
clientPenaltyRevokeEvent, token) ?? Task.CompletedTask,
|
||||
ClientPenaltyEvent clientPenaltyEvent =>
|
||||
ClientPenaltyAdministered?.InvokeAsync(clientPenaltyEvent, token) ?? Task.CompletedTask,
|
||||
ClientPermissionChangeEvent clientPermissionChangeEvent => ClientPermissionChanged?.InvokeAsync(
|
||||
clientPermissionChangeEvent, token) ?? Task.CompletedTask,
|
||||
ClientExecuteCommandEvent clientExecuteCommandEvent => ClientCommandExecuted?.InvokeAsync(
|
||||
clientExecuteCommandEvent, token) ?? Task.CompletedTask,
|
||||
LogoutEvent logoutEvent => ClientLoggedOut?.InvokeAsync(logoutEvent, token) ?? Task.CompletedTask,
|
||||
LoginEvent loginEvent => ClientLoggedIn?.InvokeAsync(loginEvent, token) ?? Task.CompletedTask,
|
||||
ClientPersistentIdReceiveEvent clientPersistentIdReceiveEvent => ClientPersistentIdReceived?.InvokeAsync(
|
||||
clientPersistentIdReceiveEvent, token) ?? Task.CompletedTask,
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
|
||||
static Task InvokeLoadAsync(IManager manager, CancellationToken token) => Load?.InvokeAsync(manager, token) ?? Task.CompletedTask;
|
||||
static Task InvokeUnloadAsync(IManager manager, CancellationToken token) => Unload?.InvokeAsync(manager, token) ?? Task.CompletedTask;
|
||||
|
||||
static void ClearEventInvocations()
|
||||
{
|
||||
Load = null;
|
||||
Unload = null;
|
||||
ClientStateInitialized = null;
|
||||
ClientStateAuthorized = null;
|
||||
ClientStateDisposed = null;
|
||||
ClientPenaltyAdministered = null;
|
||||
ClientPenaltyRevoked = null;
|
||||
ClientCommandExecuted = null;
|
||||
ClientPermissionChanged = null;
|
||||
ClientLoggedIn = null;
|
||||
ClientLoggedOut = null;
|
||||
ClientPersistentIdReceived = null;
|
||||
}
|
||||
}
|
19
SharedLibraryCore/Interfaces/ICoreEventHandler.cs
Normal file
19
SharedLibraryCore/Interfaces/ICoreEventHandler.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Threading;
|
||||
using SharedLibraryCore.Events;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Handles games events (from log, manual events, etc)
|
||||
/// </summary>
|
||||
public interface ICoreEventHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Add a core event event to the queue to be processed
|
||||
/// </summary>
|
||||
/// <param name="manager"><see cref="IManager"/></param>
|
||||
/// <param name="coreEvent"><see cref="CoreEvent"/></param>
|
||||
void QueueEvent(IManager manager, CoreEvent coreEvent);
|
||||
|
||||
void StartProcessing(CancellationToken token);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
@ -16,7 +17,70 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="previousPenalty">previous penalty the kick is occuring for (if applicable)</param>
|
||||
/// <returns></returns>
|
||||
Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null);
|
||||
|
||||
/// <summary>
|
||||
/// Time the most recent match ended
|
||||
/// </summary>
|
||||
DateTime? MatchEndTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Time the current match started
|
||||
/// </summary>
|
||||
DateTime? MatchStartTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List of connected clients
|
||||
/// </summary>
|
||||
IReadOnlyList<EFClient> ConnectedClients { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Game code corresponding to the development studio project
|
||||
/// </summary>
|
||||
Reference.Game GameCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the anticheat/custom callbacks/live radar integration is enabled
|
||||
/// </summary>
|
||||
bool IsLegacyGameIntegrationEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for the server (typically ip:port)
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Network address the server is listening on
|
||||
/// </summary>
|
||||
string ListenAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Network port the server is listening on
|
||||
/// </summary>
|
||||
int ListenPort { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the server (hostname)
|
||||
/// </summary>
|
||||
string ServerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current gametype
|
||||
/// </summary>
|
||||
string Gametype { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Game password (required to join)
|
||||
/// </summary>
|
||||
string GamePassword { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current map the game server is running
|
||||
/// </summary>
|
||||
Map Map { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Database id for EFServer table and references
|
||||
/// </summary>
|
||||
long LegacyDatabaseId { get; }
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Events;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Services;
|
||||
|
||||
@ -34,7 +35,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
Task Init();
|
||||
Task Start();
|
||||
Task Stop();
|
||||
void Restart();
|
||||
Task Restart();
|
||||
|
||||
[Obsolete]
|
||||
ILogger GetLogger(long serverId);
|
||||
@ -87,6 +88,11 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="gameEvent">event to be processed</param>
|
||||
void AddEvent(GameEvent gameEvent);
|
||||
|
||||
/// <summary>
|
||||
/// queues an event for processing
|
||||
/// </summary>
|
||||
void QueueEvent(CoreEvent coreEvent);
|
||||
|
||||
/// <summary>
|
||||
/// adds an additional (script) command to the command list
|
||||
/// </summary>
|
||||
|
11
SharedLibraryCore/Interfaces/IModularAssembly.cs
Normal file
11
SharedLibraryCore/Interfaces/IModularAssembly.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace SharedLibraryCore.Interfaces;
|
||||
|
||||
public interface IModularAssembly
|
||||
{
|
||||
string Name { get; }
|
||||
string Author { get; }
|
||||
string Version { get; }
|
||||
string Scope => string.Empty;
|
||||
string Role => string.Empty;
|
||||
string[] Claims => System.Array.Empty<string>();
|
||||
}
|
@ -55,8 +55,6 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
||||
|
||||
void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// set value of DVAR by name
|
||||
@ -67,9 +65,7 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
||||
|
||||
void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback, CancellationToken token = default);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// executes a console command on the server
|
||||
/// </summary>
|
||||
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using SharedLibraryCore.Events.Management;
|
||||
using SharedLibraryCore.Localization;
|
||||
|
||||
namespace SharedLibraryCore.Database.Models
|
||||
@ -603,7 +604,13 @@ namespace SharedLibraryCore.Database.Models
|
||||
LastConnection = DateTime.UtcNow;
|
||||
|
||||
Utilities.DefaultLogger.LogInformation("Client {client} is leaving the game", ToString());
|
||||
|
||||
|
||||
CurrentServer?.Manager.QueueEvent(new ClientStateDisposeEvent
|
||||
{
|
||||
Source = CurrentServer,
|
||||
Client = this
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await CurrentServer.Manager.GetClientService().Update(this);
|
||||
@ -658,6 +665,11 @@ namespace SharedLibraryCore.Database.Models
|
||||
};
|
||||
|
||||
CurrentServer.Manager.AddEvent(e);
|
||||
CurrentServer.Manager.QueueEvent(new ClientStateAuthorizeEvent
|
||||
{
|
||||
Source = CurrentServer,
|
||||
Client = this
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
@ -63,7 +64,7 @@ namespace SharedLibraryCore
|
||||
{
|
||||
Password = config.Password;
|
||||
IP = config.IPAddress;
|
||||
Port = config.Port;
|
||||
ListenPort = config.Port;
|
||||
Manager = mgr;
|
||||
#pragma warning disable CS0612
|
||||
Logger = deprecatedLogger ?? throw new ArgumentNullException(nameof(deprecatedLogger));
|
||||
@ -89,8 +90,6 @@ namespace SharedLibraryCore
|
||||
? Convert.ToInt64($"{ListenAddress!.Replace(".", "")}{ListenPort}")
|
||||
: $"{ListenAddress!.Replace(".", "")}{ListenPort}".GetStableHashCode();
|
||||
|
||||
public long LegacyEndpoint => EndPoint;
|
||||
|
||||
public abstract long LegacyDatabaseId { get; }
|
||||
public string Id => $"{ListenAddress}:{ListenPort}";
|
||||
|
||||
@ -105,6 +104,7 @@ namespace SharedLibraryCore
|
||||
public List<ChatInfo> ChatHistory { get; protected set; }
|
||||
public ClientHistoryInfo ClientHistory { get; }
|
||||
public Game GameName { get; set; }
|
||||
public Reference.Game GameCode => (Reference.Game)GameName;
|
||||
public DateTime? MatchEndTime { get; protected set; }
|
||||
public DateTime? MatchStartTime { get; protected set; }
|
||||
|
||||
@ -114,14 +114,17 @@ namespace SharedLibraryCore
|
||||
protected set => hostname = value;
|
||||
}
|
||||
|
||||
public string ServerName => Hostname;
|
||||
|
||||
public string Website { get; protected set; }
|
||||
public string Gametype { get; set; }
|
||||
|
||||
public string GametypeName => DefaultSettings.Gametypes.FirstOrDefault(gt => gt.Game == GameName)?.Gametypes
|
||||
public string GametypeName => DefaultSettings.Gametypes?.FirstOrDefault(gt => gt.Game == GameName)?.Gametypes
|
||||
?.FirstOrDefault(gt => gt.Name == Gametype)?.Alias ?? Gametype;
|
||||
|
||||
public string GamePassword { get; protected set; }
|
||||
public Map CurrentMap { get; set; }
|
||||
public Map Map => CurrentMap;
|
||||
|
||||
public int ClientNum
|
||||
{
|
||||
@ -130,9 +133,13 @@ namespace SharedLibraryCore
|
||||
|
||||
public int MaxClients { get; protected set; }
|
||||
public List<EFClient> Clients { get; protected set; }
|
||||
|
||||
public IReadOnlyList<EFClient> ConnectedClients =>
|
||||
new ReadOnlyCollection<EFClient>(GetClientsAsList());
|
||||
public string Password { get; }
|
||||
public bool Throttled { get; protected set; }
|
||||
public bool CustomCallback { get; protected set; }
|
||||
public bool IsLegacyGameIntegrationEnabled => CustomCallback;
|
||||
public string WorkingDirectory { get; protected set; }
|
||||
public IRConConnection RemoteConnection { get; protected set; }
|
||||
public IRConParser RconParser { get; set; }
|
||||
@ -143,7 +150,7 @@ namespace SharedLibraryCore
|
||||
|
||||
// Internal
|
||||
/// <summary>
|
||||
/// this is actually the hostname now
|
||||
/// this is actually the listen address now
|
||||
/// </summary>
|
||||
public string IP { get; protected set; }
|
||||
|
||||
@ -153,7 +160,7 @@ namespace SharedLibraryCore
|
||||
public string Version { get; protected set; }
|
||||
public bool IsInitialized { get; set; }
|
||||
|
||||
public int Port { get; }
|
||||
public int ListenPort { get; }
|
||||
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
|
||||
|
||||
/// <summary>
|
||||
@ -416,35 +423,6 @@ namespace SharedLibraryCore
|
||||
|
||||
public abstract Task<long> GetIdForServer(Server server = null);
|
||||
|
||||
public string GetServerDvar(string dvarName, int timeoutMs = 1000)
|
||||
{
|
||||
using var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
|
||||
try
|
||||
{
|
||||
return this.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetServerDvar(string dvarName, string dvarValue, int timeoutMs = 1000)
|
||||
{
|
||||
using var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
|
||||
try
|
||||
{
|
||||
this.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public EFClient GetClientByNumber(int clientNumber) =>
|
||||
GetClientsAsList().FirstOrDefault(client => client.ClientNumber == clientNumber);
|
||||
}
|
||||
|
@ -4,22 +4,22 @@
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2022.10.13.1</Version>
|
||||
<Version>2023.4.5.1</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
|
||||
<LangVersion>default</LangVersion>
|
||||
<LangVersion>Preview</LangVersion>
|
||||
<PackageTags>IW4MAdmin</PackageTags>
|
||||
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin/</RepositoryUrl>
|
||||
<PackageProjectUrl>https://www.raidmax.org/IW4MAdmin/</PackageProjectUrl>
|
||||
<Copyright>2022</Copyright>
|
||||
<Copyright>2023</Copyright>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<PackageVersion>2022.10.13.1</PackageVersion>
|
||||
<PackageVersion>2023.4.5.1</PackageVersion>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
@ -14,10 +14,13 @@ using System.Threading.Tasks;
|
||||
using Data.Models;
|
||||
using Humanizer;
|
||||
using Humanizer.Localisation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Dtos.Meta;
|
||||
using SharedLibraryCore.Events.Server;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Localization;
|
||||
@ -26,7 +29,6 @@ using static SharedLibraryCore.Server;
|
||||
using static Data.Models.Client.EFClient;
|
||||
using static Data.Models.EFPenalty;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
using RegionInfo = System.Globalization.RegionInfo;
|
||||
|
||||
namespace SharedLibraryCore
|
||||
{
|
||||
@ -43,7 +45,7 @@ namespace SharedLibraryCore
|
||||
public static Encoding EncodingType;
|
||||
public static Layout CurrentLocalization = new Layout(new Dictionary<string, string>());
|
||||
|
||||
public static TimeSpan DefaultCommandTimeout { get; set; } = new(0, 0, Utilities.IsDevelopment ? 360 : 25);
|
||||
public static TimeSpan DefaultCommandTimeout { get; set; } = new(0, 0, /*Utilities.IsDevelopment ? 360 : */25);
|
||||
public static char[] DirectorySeparatorChars = { '\\', '/' };
|
||||
public static char CommandPrefix { get; set; } = '!';
|
||||
|
||||
@ -66,6 +68,22 @@ namespace SharedLibraryCore
|
||||
};
|
||||
}
|
||||
|
||||
public static EFClient AsConsoleClient(this IGameServer server)
|
||||
{
|
||||
return new EFClient
|
||||
{
|
||||
ClientId = 1,
|
||||
State = EFClient.ClientState.Connected,
|
||||
Level = Permission.Console,
|
||||
CurrentServer = server as Server,
|
||||
CurrentAlias = new EFAlias
|
||||
{
|
||||
Name = "IW4MAdmin"
|
||||
},
|
||||
AdministeredPenalties = new List<EFPenalty>()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// fallback id for world events
|
||||
/// </summary>
|
||||
@ -95,14 +113,19 @@ namespace SharedLibraryCore
|
||||
|
||||
/// <summary>
|
||||
/// caps client name to the specified character length - 3
|
||||
/// and adds ellipses to the end of the reamining client name
|
||||
/// and adds ellipses to the end of the remaining client name
|
||||
/// </summary>
|
||||
/// <param name="str">client name</param>
|
||||
/// <param name="name">client name</param>
|
||||
/// <param name="maxLength">max number of characters for the name</param>
|
||||
/// <returns></returns>
|
||||
public static string CapClientName(this string str, int maxLength)
|
||||
public static string CapClientName(this string name, int maxLength)
|
||||
{
|
||||
return str.Length > maxLength ? $"{str.Substring(0, maxLength - 3)}..." : str;
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
|
||||
return name.Length > maxLength ? $"{name[..(maxLength - 3)]}..." : name;
|
||||
}
|
||||
|
||||
public static Permission MatchPermission(string str)
|
||||
@ -712,15 +735,21 @@ namespace SharedLibraryCore
|
||||
|
||||
public static Dictionary<string, string> DictionaryFromKeyValue(this string eventLine)
|
||||
{
|
||||
var values = eventLine.Substring(1).Split('\\');
|
||||
var values = eventLine[1..].Split('\\');
|
||||
|
||||
Dictionary<string, string> dict = null;
|
||||
Dictionary<string, string> dict = new();
|
||||
|
||||
if (values.Length > 1)
|
||||
if (values.Length <= 1)
|
||||
{
|
||||
dict = new Dictionary<string, string>();
|
||||
for (var i = values.Length % 2 == 0 ? 0 : 1; i < values.Length; i += 2)
|
||||
return dict;
|
||||
}
|
||||
|
||||
for (var i = values.Length % 2 == 0 ? 0 : 1; i < values.Length; i += 2)
|
||||
{
|
||||
if (!dict.ContainsKey(values[i]))
|
||||
{
|
||||
dict.Add(values[i], values[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
@ -771,11 +800,6 @@ namespace SharedLibraryCore
|
||||
{
|
||||
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
|
||||
}
|
||||
|
||||
public static void BeginGetDvar(this Server server, string dvarName, AsyncCallback callback, CancellationToken token = default)
|
||||
{
|
||||
server.RconParser.BeginGetDvar(server.RemoteConnection, dvarName, callback, token);
|
||||
}
|
||||
|
||||
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
||||
T fallbackValue = default)
|
||||
@ -807,30 +831,36 @@ namespace SharedLibraryCore
|
||||
return await server.GetDvarAsync(mappedKey, defaultValue, token: token);
|
||||
}
|
||||
|
||||
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue, CancellationToken token = default)
|
||||
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue,
|
||||
CancellationToken token)
|
||||
{
|
||||
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
|
||||
}
|
||||
|
||||
public static void BeginSetDvar(this Server server, string dvarName, object dvarValue,
|
||||
AsyncCallback callback, CancellationToken token = default)
|
||||
{
|
||||
server.RconParser.BeginSetDvar(server.RemoteConnection, dvarName, dvarValue, callback, token);
|
||||
}
|
||||
|
||||
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
||||
{
|
||||
await SetDvarAsync(server, dvarName, dvarValue, default);
|
||||
}
|
||||
|
||||
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName, CancellationToken token = default)
|
||||
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName,
|
||||
CancellationToken token)
|
||||
{
|
||||
return await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName, token);
|
||||
var response = await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName, token);
|
||||
|
||||
server.Manager.QueueEvent(new ServerCommandExecuteEvent
|
||||
{
|
||||
Server = server,
|
||||
Source = server,
|
||||
Command = commandName,
|
||||
Output = response
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName)
|
||||
{
|
||||
return await ExecuteCommandAsync(server, commandName, default);
|
||||
return await ExecuteCommandAsync(server, commandName, default);
|
||||
}
|
||||
|
||||
public static async Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
||||
@ -1262,5 +1292,44 @@ namespace SharedLibraryCore
|
||||
|
||||
public static string MakeAbbreviation(string gameName) => string.Join("",
|
||||
gameName.Split(' ').Select(word => char.ToUpper(word.First())).ToArray());
|
||||
|
||||
public static IServiceCollection AddConfiguration<TConfigurationType>(
|
||||
this IServiceCollection serviceCollection, string fileName = null, TConfigurationType defaultConfig = null)
|
||||
where TConfigurationType : class
|
||||
{
|
||||
serviceCollection.AddSingleton(serviceProvider =>
|
||||
{
|
||||
var configurationHandler =
|
||||
serviceProvider.GetRequiredService<IConfigurationHandlerV2<TConfigurationType>>();
|
||||
|
||||
var configuration =
|
||||
Task.Run(() => configurationHandler.Get(fileName ?? typeof(TConfigurationType).Name, defaultConfig))
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
if (typeof(TConfigurationType).GetInterface(nameof(IBaseConfiguration)) is not null &&
|
||||
defaultConfig is null && configuration is null)
|
||||
{
|
||||
defaultConfig =
|
||||
(TConfigurationType)((IBaseConfiguration)Activator.CreateInstance<TConfigurationType>())
|
||||
.Generate();
|
||||
}
|
||||
|
||||
if (defaultConfig is not null && configuration is null)
|
||||
{
|
||||
Task.Run(() => configurationHandler.Set(defaultConfig)).GetAwaiter().GetResult();
|
||||
configuration = defaultConfig;
|
||||
}
|
||||
|
||||
if (configuration is null)
|
||||
{
|
||||
throw new ConfigurationException(
|
||||
$"Could not register configuration {typeof(TConfigurationType).Name}. Configuration file does not exist and no default configuration was provided.");
|
||||
}
|
||||
|
||||
return configuration;
|
||||
});
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user