Files
OldThink/Content.Server/_Miracle/Nya/ExpectedReplySystem.cs

179 lines
6.3 KiB
C#
Raw Normal View History

using Content.Server.Chat.Managers;
using Robust.Shared.Player;
using Content.Shared._Miracle.Nya;
using Robust.Shared.Enums;
using Robust.Shared.Timing;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using Content.Server.GameTicking;
using Content.Shared._White;
using Robust.Shared.Configuration;
namespace Content.Server._Miracle.Nya;
public sealed class ExpectedReplySystem : EntitySystem
{
[Dependency] private readonly ISharedPlayerManager _playMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly CheatCheckSystem _cheatCheckSystem = default!;
private readonly Dictionary<ICommonSession, PendingReply> _pendingReplies = new();
private const float ReplyTimeoutSeconds = 6.0f;
private readonly HttpClient _httpClient = new();
private string _webhookUrl = "";
public override void Initialize()
{
base.Initialize();
_playMan.PlayerStatusChanged += OnPlayerStatusChanged;
SubscribeLocalEvent<PlayerJoinedLobbyEvent>(OnPlayerJoinedLobby);
_configuration.OnValueChanged(WhiteCVars.ACWebhook, s => _webhookUrl = s, true);
}
private void OnPlayerJoinedLobby(PlayerJoinedLobbyEvent ev)
{
_cheatCheckSystem.RequestCheck(ev.PlayerSession);
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e is { OldStatus: SessionStatus.InGame, NewStatus: SessionStatus.Disconnected })
{
if (_pendingReplies.ContainsKey(e.Session))
{
var warningMsg = $"Игрок отключился во время ожидания ответа! Nya должен был получить: {_pendingReplies[e.Session].Request.ExpectedReplyType}.";
SendSuspiciousActivityAlert(e.Session, warningMsg, 80);
_pendingReplies.Remove(e.Session);
}
}
}
public void ExpectReply<TRequest, TResponse>(
ICommonSession player,
TRequest request,
Action<TResponse, EntitySessionEventArgs> handler)
where TRequest : ExpectedReplyEntityEventArgs
where TResponse : EntityEventArgs
{
var timeout = _timing.CurTime + TimeSpan.FromSeconds(ReplyTimeoutSeconds);
void WrapHandler(EntityEventArgs ev, EntitySessionEventArgs args)
{
if (ev is TResponse response)
handler(response, args);
}
_pendingReplies[player] = new PendingReply(request, timeout, WrapHandler);
RaiseNetworkEvent(request, player.Channel);
}
public bool HandleReply(EntityEventArgs ev, EntitySessionEventArgs args)
{
if (!_pendingReplies.TryGetValue(args.SenderSession, out var pending))
{
var warningMsg = "Получен неожиданный ответ без запроса";
SendSuspiciousActivityAlert(args.SenderSession, warningMsg, 70);
return false;
}
if (pending.Request.ExpectedReplyType != ev.GetType())
{
var warningMsg = $"Получен ответ неверного типа. Ожидался {pending.Request.ExpectedReplyType}, получен {ev.GetType()}";
SendSuspiciousActivityAlert(args.SenderSession, warningMsg, 75);
return false;
}
pending.Handler(ev, args);
_pendingReplies.Remove(args.SenderSession);
return true;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var currentTime = _timing.CurTime;
var timeoutPlayers = new List<ICommonSession>();
foreach (var (player, pending) in _pendingReplies)
{
if (currentTime > pending.TimeoutTime)
timeoutPlayers.Add(player);
}
foreach (var player in timeoutPlayers)
{
HandleTimeout(player);
_pendingReplies.Remove(player);
}
}
private void HandleTimeout(ICommonSession player)
{
var warningMsg = $"Не получен ответ в течение {ReplyTimeoutSeconds} секунд. Будьте бдительны с этим игроком! Nya советует использовать nyagrab!";
SendSuspiciousActivityAlert(player, warningMsg, 65);
}
private async void SendSuspiciousActivityAlert(ICommonSession player, string reason, int severity)
{
var color = severity switch
{
>= 80 => 0xFF0000, // Красный
>= 70 => 0xFFA500, // Оранжевый
_ => 0xFFFF00 // Желтый
};
var warningMsg = $"⚠️ **Система ожидаемых ответов обнаружила подозрительную активность!**\n\n" +
$"**Игрок:** {player.Name}\n" +
$"**IP:** {player.Channel.RemoteEndPoint}\n" +
$"**Уровень подозрительности:** {severity}%\n" +
$"**Причина:** {reason}";
var embed = new
{
title = "⚠️ Подозрительная активность!",
description = warningMsg,
color = color,
timestamp = DateTime.UtcNow.ToString("o")
};
var payload = new
{
embeds = new[] { embed }
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
await _httpClient.PostAsync(_webhookUrl, content);
}
catch (Exception e)
{
Log.Error($"Failed to send Discord webhook: {e}");
}
var inGameMsg = $"[Anticheat] Внимание! Подозрительная активность:\n" +
$"Игрок {player.Name} возможно читер!\n" +
$"Причина обнаружения: {reason}";
_chatManager.SendAdminAnnouncement(inGameMsg);
}
public override void Shutdown()
{
base.Shutdown();
_playMan.PlayerStatusChanged -= OnPlayerStatusChanged;
_pendingReplies.Clear();
}
}