using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events;
namespace SharedLibraryCore
{
public class GameEvent : CoreEvent
{
public enum EventFailReason
{
///
/// event execution did not fail
///
None,
///
/// an internal exception prevented the event
/// from executing
///
Exception,
///
/// event origin didn't have the necessary privileges
/// to execute the command
///
Permission,
///
/// executing the event would cause an invalid state
///
Invalid,
///
/// client is doing too much of something
///
Throttle,
///
/// the event timed out before completion
///
Timeout
}
[Flags]
public enum EventRequiredEntity
{
None = 1,
Origin = 2,
Target = 4
}
public enum EventSource
{
Unspecified,
Log,
Status,
Internal
}
public enum EventType
{
///
/// the event wasn't parsed properly
///
Unknown,
// events "generated" by the server
///
/// a server started being monitored
///
Start,
///
/// a server stopped being monitored
///
Stop,
///
/// a client was detecting as connecting via log
///
Connect,
///
/// a client was detecting joining by RCon
///
Join,
///
/// a client was detected leaving via log
///
Quit,
///
/// a client was detected leaving by RCon
///
Disconnect,
///
/// the current map ended
///
MapEnd,
///
/// the current map changed
///
MapChange,
///
/// a client was detected as starting to connect
///
PreConnect,
///
/// a client was detecting as starting to disconnect
///
PreDisconnect,
///
/// a client's information was updated
///
Update,
///
/// connection was lost to a server (the server has not responded after a number of attempts)
///
ConnectionLost,
///
/// connection was restored to a server (the server began responding again)
///
ConnectionRestored,
SayTeam = 99,
// events "generated" by clients
///
/// a client sent a message
///
Say = 100,
///
/// a client was warned
///
Warn = 101,
///
/// all warnings for a client were cleared
///
WarnClear = 102,
///
/// a client was reported
///
Report = 103,
///
/// a client was flagged
///
Flag = 104,
///
/// a client was unflagged
///
Unflag = 105,
///
/// a client was kicked
///
Kick = 106,
///
/// a client was tempbanned
///
TempBan = 107,
///
/// a client was banned
///
Ban = 108,
///
/// a client was unbanned
///
Unban = 109,
///
/// a client entered a command
///
Command = 110,
///
/// a client's permission was changed
///
ChangePermission = 111,
///
/// client logged in to webfront
///
Login = 112,
///
/// client logged out of webfront
///
Logout = 113,
///
/// meta value updated on client
///
MetaUpdated = 114,
// events "generated" by IW4MAdmin
///
/// a message is sent to all clients
///
Broadcast = 200,
///
/// a message is sent to a specific client
///
Tell = 201,
// events "generated" by script/log
///
/// AC Damage Log
///
ScriptDamage = 300,
///
/// AC Kill Log
///
ScriptKill = 301,
///
/// damage info printed out by game script
///
Damage = 302,
///
/// kill info printed out by game script
///
Kill = 303,
///
/// team info printed out by game script
///
JoinTeam = 304,
///
/// used for community generated plugin events
///
Other
}
private static long NextEventId;
private readonly SemaphoreSlim _eventFinishedWaiter = new(0, 1);
public string Data; // Data is usually the message sent by player
public string Message;
public EFClient Origin;
public Server Owner;
public EFClient Target;
public EventType Type;
public string TypeName => Type.ToString();
public GameEvent()
{
Time = DateTime.UtcNow;
IncrementalId = GetNextEventId();
}
~GameEvent()
{
_eventFinishedWaiter.Dispose();
}
///
/// suptype of the event for more detailed classification
///
public string Subtype { get; set; }
public EventRequiredEntity RequiredEntity { get; set; }
///
/// Specifies the game time offset as printed in the log
///
public long? GameTime { get; set; }
public EFClient ImpersonationOrigin { get; set; }
public bool IsRemote { get; set; }
public object Extra { get; set; }
public DateTime Time { get; set; }
public long IncrementalId { get; }
public EventFailReason FailReason { get; set; }
public bool Failed => FailReason != EventFailReason.None;
public List Output { get; set; } = new();
///
/// Indicates if the event should block until it is complete
///
public bool IsBlocking { get; set; }
private static long GetNextEventId()
{
return Interlocked.Increment(ref NextEventId);
}
public void Complete()
{
if (_eventFinishedWaiter.CurrentCount == 0)
{
_eventFinishedWaiter.Release();
}
}
public async Task WaitAsync()
{
return await WaitAsync(Utilities.DefaultCommandTimeout, new CancellationToken());
}
///
/// asynchronously wait for GameEvent to be processed
///
/// waitable task
public async Task WaitAsync(TimeSpan timeSpan, CancellationToken token)
{
if (FailReason == EventFailReason.Timeout)
{
return this;
}
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 (processed)
{
Complete();
}
}
if (!processed)
{
using (LogContext.PushProperty("Server", Owner?.ToString()))
{
Utilities.DefaultLogger.LogError("Waiting for event to complete timed out {@eventData}",
new { Event = this, Message, Origin = Origin?.ToString(), Target = Target?.ToString() });
}
}
// this lets us know if the the action timed out
FailReason = FailReason == EventFailReason.None && !processed ? EventFailReason.Timeout : FailReason;
return this;
}
}
}