[feat] Sockets, i guess mm hmm

# Conflicts:
#	Content.Server/Administration/Systems/BwoinkSystem.cs
#	Content.Server/Chat/Managers/ChatManager.cs
#	Content.Server/Entry/EntryPoint.cs
#	Content.Server/GameTicking/GameTicker.RoundFlow.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Server/RoundEnd/RoundEndSystem.cs
#	Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs
#	Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs
#	Content.Shared/CCVar/CCVars.cs
This commit is contained in:
rhailrake
2023-04-27 06:01:05 +06:00
committed by Remuchi
parent eeec02119d
commit aca6843c0a
26 changed files with 1175 additions and 81 deletions

View File

@@ -3,10 +3,10 @@ using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Discord;
using Content.Server.GameTicking;
using Content.Shared.Administration;
using Content.Shared.CCVar;
@@ -346,13 +346,13 @@ namespace Content.Server.Administration.Systems
{
Username = username,
AvatarUrl = string.IsNullOrWhiteSpace(_avatarUrl) ? null : _avatarUrl,
Embeds = new List<WebhookEmbed>
Embeds = new List<Embed>
{
new()
{
Description = messages,
Color = color,
Footer = new WebhookEmbedFooter
Footer = new EmbedFooter
{
Text = $"{serverName} ({round})",
IconUrl = string.IsNullOrWhiteSpace(_footerIconUrl) ? null : _footerIconUrl
@@ -495,7 +495,7 @@ namespace Content.Server.Administration.Systems
private static string GenerateAHelpMessage(string username, string message, bool admin, string roundTime, GameRunLevel roundState, bool noReceivers = false)
{
var stringbuilder = new StringBuilder();
if (admin)
stringbuilder.Append(":outbox_tray:");
else if (noReceivers)
@@ -509,6 +509,118 @@ namespace Content.Server.Administration.Systems
stringbuilder.Append(message);
return stringbuilder.ToString();
}
// https://discord.com/developers/docs/resources/channel#message-object-message-structure
private struct WebhookPayload
{
[JsonPropertyName("username")]
public string Username { get; set; } = "";
[JsonPropertyName("avatar_url")]
public string? AvatarUrl { get; set; } = "";
[JsonPropertyName("embeds")]
public List<Embed>? Embeds { get; set; } = null;
[JsonPropertyName("allowed_mentions")]
public Dictionary<string, string[]> AllowedMentions { get; set; } =
new()
{
{ "parse", Array.Empty<string>() },
};
public WebhookPayload()
{
}
}
// https://discord.com/developers/docs/resources/channel#embed-object-embed-structure
private struct Embed
{
[JsonPropertyName("description")]
public string Description { get; set; } = "";
[JsonPropertyName("color")]
public int Color { get; set; } = 0;
[JsonPropertyName("footer")]
public EmbedFooter? Footer { get; set; } = null;
public Embed()
{
}
}
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
private struct EmbedFooter
{
[JsonPropertyName("text")]
public string Text { get; set; } = "";
[JsonPropertyName("icon_url")]
public string? IconUrl { get; set; }
public EmbedFooter()
{
}
}
// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-structure
private struct WebhookData
{
[JsonPropertyName("guild_id")]
public string? GuildId { get; set; } = null;
[JsonPropertyName("channel_id")]
public string? ChannelId { get; set; } = null;
public WebhookData()
{
}
}
//WD-EDIT
public void SendUtkaBwoinkMessage(NetUserId receiver, string sender, string text)
{
var bwoinkText = $"[color=red](D) {sender}[/color]: {text}";
_playerManager.TryGetUserId(sender, out var senderId);
var msg = new BwoinkTextMessage(receiver, senderId, bwoinkText);
LogBwoink(msg);
var admins = GetTargetAdmins();
// Notify all admins
foreach (var channel in admins)
{
RaiseNetworkEvent(msg, channel);
}
// Notify player
if (_playerManager.TryGetSessionById(receiver, out var session))
{
if (!admins.Contains(session.ConnectedClient))
RaiseNetworkEvent(msg, session.ConnectedClient);
}
var sendsWebhook = _webhookUrl != string.Empty;
if (sendsWebhook)
{
if (!_messageQueues.ContainsKey(msg.UserId))
_messageQueues[msg.UserId] = new Queue<string>();
var str = text;
var unameLength = sender.Length;
if (unameLength + str.Length + _maxAdditionalChars > DescriptionMax)
{
str = str[..(DescriptionMax - _maxAdditionalChars - unameLength)];
}
_messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(sender, str, true,
_gameTicker.RoundDuration().ToString("hh\\:mm\\:ss"), _gameTicker.RunLevel));
}
}
//WD-EDIT
}
}

View File

@@ -6,6 +6,7 @@ using Content.Server.Administration.Managers;
using Content.Server.Administration.Systems;
using Content.Server.MoMMI;
using Content.Server.Preferences.Managers;
using Content.Server.UtkaIntegration;
using Content.Server.White.Sponsors;
using Content.Shared.Administration;
using Content.Shared.CCVar;
@@ -49,6 +50,7 @@ namespace Content.Server.Chat.Managers
/// WD-EDIT
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
[Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!;
/// WD-EDIT
/// <summary>
@@ -170,7 +172,7 @@ namespace Content.Server.Chat.Managers
public void SendHookOOC(string sender, string message)
{
if (!_oocEnabled && _configurationManager.GetCVar(CCVars.DisablingOOCDisablesRelay))
if (_configurationManager.GetCVar(CCVars.DisableHookedOOC))
{
return;
}
@@ -179,6 +181,28 @@ namespace Content.Server.Chat.Managers
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Hook OOC from {sender}: {message}");
}
//WD-EDIT
public void SendHookAdminChat(string sender, string message)
{
var admins = _adminManager.ActiveAdmins;
var wrappedMessage = Loc.GetString("chat-manager-send-admin-chat-wrap-message",
("adminChannelName", Loc.GetString("chat-manager-admin-discord-channel-name")),
("playerName", sender), ("message", FormattedMessage.EscapeText(message)));
ChatMessageToMany(ChatChannel.Admin, message, wrappedMessage, EntityUid.Invalid, false, true, admins.Select(p => p.ConnectedClient));
var asayEventMessage = new UtkaChatEventMessage()
{
Command = "asay",
Ckey = sender,
Message = message
};
_utkaSocketWrapper.SendMessageToAll(asayEventMessage);
}
//WD-EDIT
#endregion
#region Public OOC Chat API
@@ -254,6 +278,17 @@ namespace Content.Server.Chat.Managers
ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride, author: player.UserId);
_mommiLink.SendOOCMessage(player.Name, message);
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"OOC from {player:Player}: {message}");
//WD-EDIT
var toUtkaMessage = new UtkaChatEventMessage()
{
Command = "ooc",
Ckey = player.Name,
Message = message,
};
_utkaSocketWrapper.SendMessageToAll(toUtkaMessage);
//WD-EDIT
}
private void SendAdminChat(ICommonSession player, string message)
@@ -284,6 +319,17 @@ namespace Content.Server.Chat.Managers
}
_adminLogger.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}");
//WD-EDIT
var asayEventMessage = new UtkaChatEventMessage()
{
Command = "asay",
Ckey = player.Name,
Message = message
};
_utkaSocketWrapper.SendMessageToAll(asayEventMessage);
//WD-EDIT
}
#endregion

View File

@@ -21,6 +21,7 @@ namespace Content.Server.Chat.Managers
void TrySendOOCMessage(ICommonSession player, string message, OOCChatType type);
void SendHookOOC(string sender, string message);
void SendHookAdminChat(string sender, string message); // WD-EDIT
void SendAdminAnnouncement(string message);
void SendAdminAlert(string message);
void SendAdminAlert(EntityUid player, string message);

View File

@@ -8,6 +8,7 @@ using Content.Server.Speech.Components;
using Content.Server.Speech.EntitySystems;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Server.UtkaIntegration;
using Content.Shared.ActionBlocker;
using Content.Shared.CCVar;
using Content.Shared.Chat;
@@ -56,6 +57,10 @@ public sealed partial class ChatSystem : SharedChatSystem
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
//WD-EDIT
[Dependency] private readonly UtkaTCPWrapper _utkaSockets = default!;
//WD-EDIT
public const int VoiceRange = 10; // how far voice goes in world units
public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
@@ -571,6 +576,28 @@ public sealed partial class ChatSystem : SharedChatSystem
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user} as {name}: {action}");
else
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user}: {action}");
//WD-EDIT
string ckey = string.Empty;
if (TryComp<ActorComponent>(source, out var actorComponent))
{
ckey = actorComponent.PlayerSession.Name;
}
if(string.IsNullOrEmpty(ckey)) return;
var utkaEmoteEvent = new UtkaChatMeEvent()
{
Ckey = ckey,
Message = action,
CharacterName = MetaData(source).EntityName
};
_utkaSockets.SendMessageToAll(utkaEmoteEvent);
//WD-EDIT
}
// ReSharper disable once InconsistentNaming

View File

@@ -15,6 +15,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" PrivateAssets="All" />
<PackageReference Include="NetCoreServer" Version="6.7.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />

View File

@@ -29,6 +29,7 @@ using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Server.UtkaIntegration;
using Content.Server.White.JoinQueue;
using Content.Server.White.Sponsors;
@@ -146,6 +147,14 @@ namespace Content.Server.Entry
IoCManager.Resolve<IGameMapManager>().Initialize();
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
IoCManager.Resolve<IBanManager>().Initialize();
IoCManager.Resolve<IBqlQueryManager>().DoAutoRegistrations();
IoCManager.Resolve<RoleBanManager>().Initialize();
//WD-EDIT
IoCManager.Resolve<UtkaTCPWrapper>().Initialize();
UtkaTCPServer.RegisterCommands();
//WD-EDIT
}
}

View File

@@ -20,6 +20,8 @@ using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Content.Server.UtkaIntegration;
using System.Threading.Tasks;
namespace Content.Server.GameTicking
{
@@ -28,6 +30,10 @@ namespace Content.Server.GameTicking
[Dependency] private readonly DiscordWebhook _discord = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
//WD-EDIT
[Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!;
//WD-EDIT
private static readonly Counter RoundNumberMetric = Metrics.CreateCounter(
"ss14_round_number",
"Round number.");
@@ -255,6 +261,7 @@ namespace Content.Server.GameTicking
AnnounceRound();
UpdateInfoText();
RaiseLocalEvent(new RoundStartedEvent(RoundId)); // WD-EDIT
SendRoundStatus("game_started"); //WD-EDIT
#if EXCEPTION_TOLERANCE
}
@@ -435,6 +442,7 @@ namespace Content.Server.GameTicking
UpdateInfoText();
ReqWindowAttentionAll();
SendRoundStatus("lobby_loaded"); //WD-EDIT
}
}
@@ -517,6 +525,22 @@ namespace Content.Server.GameTicking
return true;
}
//WD-EDIT
private void SendRoundStatus(string status)
{
if (!_postInitialized)
return;
var utkaRoundStatusEvent = new UtkaRoundStatusEvent()
{
Message = status
};
_utkaSocketWrapper.SendMessageToAll(utkaRoundStatusEvent);
}
//WD-EDIT
private void UpdateRoundFlow(float frameTime)
{
if (RunLevel == GameRunLevel.InRound)

View File

@@ -19,6 +19,7 @@ using Content.Server.ServerInfo;
using Content.Server.ServerUpdates;
using Content.Server.Voting.Managers;
using Content.Server.Worldgen.Tools;
using Content.Server.UtkaIntegration;
using Content.Server.White.JoinQueue;
using Content.Server.White.Sponsors;
using Content.Shared.Administration.Logs;
@@ -64,6 +65,7 @@ namespace Content.Server.IoC
// WD-EDIT
IoCManager.Register<SponsorsManager>();
IoCManager.Register<JoinQueueManager>();
IoCManager.Register<UtkaTCPWrapper>();
// WD-EDIT
}
}

View File

@@ -12,6 +12,7 @@ using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Server.UtkaIntegration;
using Content.Shared.Database;
using Content.Shared.GameTicking;
using Robust.Shared.Audio.Systems;
@@ -45,6 +46,10 @@ namespace Content.Server.RoundEnd
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
//WD-EDIT
[Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!;
//WD-EDIT
public TimeSpan DefaultCooldownDuration { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>
@@ -52,15 +57,19 @@ namespace Content.Server.RoundEnd
/// </summary>
public TimeSpan DefaultCountdownDuration { get; set; } = TimeSpan.FromMinutes(10);
private CancellationTokenSource? _countdownTokenSource = null;
private CancellationTokenSource? _cooldownTokenSource = null;
public TimeSpan? LastCountdownStart { get; set; } = null;
public TimeSpan? ExpectedCountdownEnd { get; set; } = null;
private CancellationTokenSource? _countdownTokenSource;
private CancellationTokenSource? _cooldownTokenSource;
public TimeSpan? LastCountdownStart { get; set; }
public TimeSpan? ExpectedCountdownEnd { get; set; }
public TimeSpan? ExpectedShuttleLength => ExpectedCountdownEnd - LastCountdownStart;
public TimeSpan? ShuttleTimeLeft => ExpectedCountdownEnd - _gameTiming.CurTime;
public TimeSpan AutoCallStartTime;
private bool _autoCalledBefore = false;
private bool _autoCalledBefore;
public override void Initialize()
{
@@ -100,9 +109,12 @@ namespace Content.Server.RoundEnd
/// </summary>
public EntityUid? GetStation()
{
AllEntityQuery<StationEmergencyShuttleComponent, StationDataComponent>().MoveNext(out _, out _, out var data);
AllEntityQuery<StationEmergencyShuttleComponent, StationDataComponent>()
.MoveNext(out _, out _, out var data);
if (data == null)
return null;
var targetGrid = _stationSystem.GetLargestGrid(data);
return targetGrid == null ? null : Transform(targetGrid.Value).MapUid;
}
@@ -112,13 +124,9 @@ namespace Content.Server.RoundEnd
/// </summary>
public EntityUid? GetCentcomm()
{
if (AllEntityQuery<StationCentcommComponent, TransformComponent>()
.MoveNext(out var centcomm, out var xform))
{
return xform.MapUid;
}
return null;
return AllEntityQuery<StationCentcommComponent, TransformComponent>().MoveNext(out _, out var xform)
? xform.MapUid
: null;
}
public bool CanCallOrRecall()
@@ -131,7 +139,11 @@ namespace Content.Server.RoundEnd
return _countdownTokenSource != null;
}
public void RequestRoundEnd(EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "Station")
public void RequestRoundEnd(
EntityUid? requester = null,
bool checkCooldown = true,
string text = "round-end-system-shuttle-called-announcement",
string name = "Station")
{
var duration = DefaultCountdownDuration;
@@ -149,18 +161,28 @@ namespace Content.Server.RoundEnd
RequestRoundEnd(duration, requester, checkCooldown, text, name);
}
public void RequestRoundEnd(TimeSpan countdownTime, EntityUid? requester = null, bool checkCooldown = true, string text = "round-end-system-shuttle-called-announcement", string name = "Station")
public void RequestRoundEnd(
TimeSpan countdownTime,
EntityUid? requester = null,
bool checkCooldown = true,
string text = "round-end-system-shuttle-called-announcement",
string name = "Station")
{
if (_gameTicker.RunLevel != GameRunLevel.InRound) return;
if (_gameTicker.RunLevel != GameRunLevel.InRound)
return;
if (checkCooldown && _cooldownTokenSource != null) return;
if (checkCooldown && _cooldownTokenSource != null)
return;
if (_countdownTokenSource != null) return;
_countdownTokenSource = new();
if (_countdownTokenSource != null)
return;
_countdownTokenSource = new CancellationTokenSource();
if (requester != null)
{
_adminLogger.Add(LogType.ShuttleCalled, LogImpact.High, $"Shuttle called by {ToPrettyString(requester.Value):user}");
_adminLogger.Add(LogType.ShuttleCalled, LogImpact.High,
$"Shuttle called by {ToPrettyString(requester.Value):user}");
}
else
{
@@ -183,8 +205,8 @@ namespace Content.Server.RoundEnd
}
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString(text,
("time", time),
("units", Loc.GetString(units))),
("time", time),
("units", Loc.GetString(units))),
name,
false,
null,
@@ -199,6 +221,8 @@ namespace Content.Server.RoundEnd
ActivateCooldown();
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
SendRoundStatus("shuttle_called");
var shuttle = _shuttle.GetShuttle();
if (shuttle != null && TryComp<DeviceNetworkComponent>(shuttle, out var net))
{
@@ -208,25 +232,33 @@ namespace Content.Server.RoundEnd
[ShuttleTimerMasks.SourceMap] = GetCentcomm(),
[ShuttleTimerMasks.DestMap] = GetStation(),
[ShuttleTimerMasks.ShuttleTime] = countdownTime,
[ShuttleTimerMasks.SourceTime] = countdownTime + TimeSpan.FromSeconds(_shuttle.TransitTime + _cfg.GetCVar(CCVars.EmergencyShuttleDockTime)),
[ShuttleTimerMasks.SourceTime] = countdownTime +
TimeSpan.FromSeconds(_shuttle.TransitTime + _cfg.GetCVar(CCVars.EmergencyShuttleDockTime)),
[ShuttleTimerMasks.DestTime] = countdownTime,
};
_deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, net.TransmitFrequency);
}
}
public void CancelRoundEndCountdown(EntityUid? requester = null, bool checkCooldown = true)
{
if (_gameTicker.RunLevel != GameRunLevel.InRound) return;
if (checkCooldown && _cooldownTokenSource != null) return;
if (_gameTicker.RunLevel != GameRunLevel.InRound)
return;
if (checkCooldown && _cooldownTokenSource != null)
return;
if (_countdownTokenSource == null)
return;
if (_countdownTokenSource == null) return;
_countdownTokenSource.Cancel();
_countdownTokenSource = null;
if (requester != null)
{
_adminLogger.Add(LogType.ShuttleRecalled, LogImpact.High, $"Shuttle recalled by {ToPrettyString(requester.Value):user}");
_adminLogger.Add(LogType.ShuttleRecalled, LogImpact.High,
$"Shuttle recalled by {ToPrettyString(requester.Value):user}");
}
else
{
@@ -243,6 +275,10 @@ namespace Content.Server.RoundEnd
ActivateCooldown();
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
//WD-EDIT
SendRoundStatus("shuttle_recalled");
//WD-EDIT
// remove all active shuttle timers
var zero = TimeSpan.Zero;
var shuttle = _shuttle.GetShuttle();
@@ -256,21 +292,36 @@ namespace Content.Server.RoundEnd
[ShuttleTimerMasks.ShuttleTime] = zero,
[ShuttleTimerMasks.SourceTime] = zero,
[ShuttleTimerMasks.DestTime] = zero,
[ShuttleTimerMasks.Text] = new string?[] { string.Empty, string.Empty }
[ShuttleTimerMasks.Text] = new[] { string.Empty, string.Empty }
};
_deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, net.TransmitFrequency);
}
}
//WD-EDIT
private void SendRoundStatus(string status)
{
var utkaRoundStatusEvent = new UtkaRoundStatusEvent()
{
Message = status
};
_utkaSocketWrapper.SendMessageToAll(utkaRoundStatusEvent);
}
//WD-EDIT
public void EndRound(TimeSpan? countdownTime = null)
{
if (_gameTicker.RunLevel != GameRunLevel.InRound) return;
if (_gameTicker.RunLevel != GameRunLevel.InRound)
return;
LastCountdownStart = null;
ExpectedCountdownEnd = null;
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
_gameTicker.EndRound();
_countdownTokenSource?.Cancel();
_countdownTokenSource = new();
_countdownTokenSource = new CancellationTokenSource();
countdownTime ??= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.RoundRestartTime));
int time;
@@ -285,11 +336,13 @@ namespace Content.Server.RoundEnd
time = countdownTime.Value.Minutes;
unitsLocString = "eta-units-minutes";
}
_chatManager.DispatchServerAnnouncement(
Loc.GetString(
"round-end-system-round-restart-eta-announcement",
("time", time),
("units", Loc.GetString(unitsLocString))));
Timer.Spawn(countdownTime.Value, AfterEndRoundRestart, _countdownTokenSource.Token);
}
@@ -301,7 +354,8 @@ namespace Content.Server.RoundEnd
/// <param name="sender"></param>
/// <param name="textCall"></param>
/// <param name="textAnnounce"></param>
public void DoRoundEndBehavior(RoundEndBehavior behavior,
public void DoRoundEndBehavior(
RoundEndBehavior behavior,
TimeSpan time,
string sender = "comms-console-announcement-title-centcom",
string textCall = "round-end-system-shuttle-called-announcement",
@@ -325,13 +379,16 @@ namespace Content.Server.RoundEnd
RequestRoundEnd(time, null, false, textCall,
Loc.GetString(sender));
}
break;
}
}
private void AfterEndRoundRestart()
{
if (_gameTicker.RunLevel != GameRunLevel.PostRound) return;
if (_gameTicker.RunLevel != GameRunLevel.PostRound)
return;
Reset();
_gameTicker.RestartRound();
}
@@ -351,8 +408,10 @@ namespace Content.Server.RoundEnd
public override void Update(float frameTime)
{
// Check if we should auto-call.
int mins = _autoCalledBefore ? _cfg.GetCVar(CCVars.EmergencyShuttleAutoCallExtensionTime)
: _cfg.GetCVar(CCVars.EmergencyShuttleAutoCallTime);
int mins = _autoCalledBefore
? _cfg.GetCVar(CCVars.EmergencyShuttleAutoCallExtensionTime)
: _cfg.GetCVar(CCVars.EmergencyShuttleAutoCallTime);
if (mins != 0 && _gameTiming.CurTime - AutoCallStartTime > TimeSpan.FromMinutes(mins))
{
if (!_shuttle.EmergencyShuttleArrived && ExpectedCountdownEnd is null)

View File

@@ -234,6 +234,11 @@ public sealed partial class EmergencyShuttleSystem
_shuttle.AddFTLDestination(comp.Entity.Value, true);
}
if (EarlyLaunchAuthorized)
SendRoundStatus("shuttle_escaped");
else
SendRoundStatus("shuttle_left");
}
}

View File

@@ -15,6 +15,7 @@ using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Server.UtkaIntegration;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Database;
@@ -62,6 +63,10 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
[Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
//WD-EDIT
[Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!;
//WD-EDIT
private ISawmill _sawmill = default!;
private const float ShuttleSpawnBuffer = 1f;
@@ -185,6 +190,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
if (config == null)
return;
SendRoundStatus("shuttle_docked");
RaiseNetworkEvent(new EmergencyShuttlePositionMessage()
{
StationUid = GetNetEntity(targetGrid),
@@ -197,25 +203,25 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
/// </summary>
private void OnEmergencyFTL(EntityUid uid, EmergencyShuttleComponent component, ref FTLStartedEvent args)
{
TimeSpan ftlTime = TimeSpan.FromSeconds
var ftlTime = TimeSpan.FromSeconds
(
TryComp<FTLComponent>(uid, out var ftlComp) ?
ftlComp.TravelTime : ShuttleSystem.DefaultTravelTime
TryComp<FTLComponent>(uid, out var ftlComp) ? ftlComp.TravelTime : ShuttleSystem.DefaultTravelTime
);
if (TryComp<DeviceNetworkComponent>(uid, out var netComp))
if (!TryComp<DeviceNetworkComponent>(uid, out var netComp))
return;
var payload = new NetworkPayload
{
var payload = new NetworkPayload
{
[ShuttleTimerMasks.ShuttleMap] = uid,
[ShuttleTimerMasks.SourceMap] = args.FromMapUid,
[ShuttleTimerMasks.DestMap] = args.TargetCoordinates.GetMapUid(_entityManager),
[ShuttleTimerMasks.ShuttleTime] = ftlTime,
[ShuttleTimerMasks.SourceTime] = ftlTime,
[ShuttleTimerMasks.DestTime] = ftlTime
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency);
}
[ShuttleTimerMasks.ShuttleMap] = uid,
[ShuttleTimerMasks.SourceMap] = args.FromMapUid,
[ShuttleTimerMasks.DestMap] = args.TargetCoordinates.GetMapUid(_entityManager),
[ShuttleTimerMasks.ShuttleTime] = ftlTime,
[ShuttleTimerMasks.SourceTime] = ftlTime,
[ShuttleTimerMasks.DestTime] = ftlTime
};
_deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency);
}
/// <summary>
@@ -225,20 +231,21 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
{
var countdownTime = TimeSpan.FromSeconds(_configManager.GetCVar(CCVars.RoundRestartTime));
var shuttle = args.Entity;
if (TryComp<DeviceNetworkComponent>(shuttle, out var net))
if (!TryComp<DeviceNetworkComponent>(shuttle, out var net))
return;
var payload = new NetworkPayload
{
var payload = new NetworkPayload
{
[ShuttleTimerMasks.ShuttleMap] = shuttle,
[ShuttleTimerMasks.SourceMap] = _roundEnd.GetCentcomm(),
[ShuttleTimerMasks.DestMap] = _roundEnd.GetStation(),
[ShuttleTimerMasks.ShuttleTime] = countdownTime,
[ShuttleTimerMasks.SourceTime] = countdownTime,
[ShuttleTimerMasks.DestTime] = countdownTime,
[ShuttleTimerMasks.Text] = new string?[] { "BYE!" }
};
_deviceNetworkSystem.QueuePacket(shuttle, null, payload, net.TransmitFrequency);
}
[ShuttleTimerMasks.ShuttleMap] = shuttle,
[ShuttleTimerMasks.SourceMap] = _roundEnd.GetCentcomm(),
[ShuttleTimerMasks.DestMap] = _roundEnd.GetStation(),
[ShuttleTimerMasks.ShuttleTime] = countdownTime,
[ShuttleTimerMasks.SourceTime] = countdownTime,
[ShuttleTimerMasks.DestTime] = countdownTime,
[ShuttleTimerMasks.Text] = new[] { "BYE!" }
};
_deviceNetworkSystem.QueuePacket(shuttle, null, payload, net.TransmitFrequency);
}
/// <summary>
@@ -258,8 +265,12 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
// UHH GOOD LUCK
if (targetGrid == null)
{
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to dock with station {ToPrettyString(stationUid)}");
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false);
_logger.Add(LogType.EmergencyShuttle, LogImpact.High,
$"Emergency shuttle {ToPrettyString(stationUid)} unable to dock with station {ToPrettyString(stationUid)}");
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-good-luck"),
playDefaultSound: false);
// TODO: Need filter extensions or something don't blame me.
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
return;
@@ -271,8 +282,12 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
{
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false);
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform,
xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid,
Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"),
("direction", angle.GetDir())), playDefaultSound: false);
}
// shuttle timers
@@ -289,10 +304,14 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
[ShuttleTimerMasks.DestTime] = time + TimeSpan.FromSeconds(TransitTime),
[ShuttleTimerMasks.Docked] = true
};
_deviceNetworkSystem.QueuePacket(stationShuttle.EmergencyShuttle.Value, null, payload, netComp.TransmitFrequency);
_deviceNetworkSystem.QueuePacket(stationShuttle.EmergencyShuttle.Value, null, payload,
netComp.TransmitFrequency);
}
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} docked with stations");
_logger.Add(LogType.EmergencyShuttle, LogImpact.High,
$"Emergency shuttle {ToPrettyString(stationUid)} docked with stations");
// TODO: Need filter extensions or something don't blame me.
_audio.PlayGlobal("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast(), true);
}
@@ -300,11 +319,16 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
{
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false);
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform,
xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid,
Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false);
}
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}");
_logger.Add(LogType.EmergencyShuttle, LogImpact.High,
$"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}");
// TODO: Need filter extensions or something don't blame me.
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
}
@@ -372,7 +396,9 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
var query = AllEntityQuery<StationEmergencyShuttleComponent>();
while (query.MoveNext(out var uid, out var comp))
{
AddEmergencyShuttle(uid, comp);
}
}
private void AddCentcomm(StationCentcommComponent component)
@@ -409,10 +435,11 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
}
var mapId = _mapManager.CreateMap();
var grid = _map.LoadGrid(mapId, component.Map.ToString(), new MapLoadOptions()
var grid = _map.LoadGrid(mapId, component.Map.ToString(), new MapLoadOptions
{
LoadMap = false,
});
var map = _mapManager.GetMapEntityId(mapId);
if (!Exists(map))
@@ -535,11 +562,26 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
return false;
}
private bool IsOnGrid(TransformComponent xform, EntityUid shuttle, MapGridComponent? grid = null, TransformComponent? shuttleXform = null)
private bool IsOnGrid(
TransformComponent xform,
EntityUid shuttle,
MapGridComponent? grid = null,
TransformComponent? shuttleXform = null)
{
if (!Resolve(shuttle, ref grid, ref shuttleXform))
return false;
return _transformSystem.GetWorldMatrix(shuttleXform).TransformBox(grid.LocalAABB).Contains(_transformSystem.GetWorldPosition(xform));
return _transformSystem.GetWorldMatrix(shuttleXform).TransformBox(grid.LocalAABB)
.Contains(_transformSystem.GetWorldPosition(xform));
}
private void SendRoundStatus(string status)
{
var utkaRoundStatusEvent = new UtkaRoundStatusEvent()
{
Message = status
};
_utkaSocketWrapper.SendMessageToAll(utkaRoundStatusEvent);
}
}

View File

@@ -0,0 +1,11 @@
using System.Net;
using Content.Server.UtkaIntegration.TCP;
namespace Content.Server.UtkaIntegration;
public interface IUtkaCommand
{
string Name { get; }
Type RequestMessageType { get; }
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage);
}

View File

@@ -0,0 +1,43 @@
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using Content.Server.Administration.Managers;
using Content.Server.UtkaIntegration.TCP;
using Content.Shared.CCVar;
using Robust.Shared;
using Robust.Shared.Configuration;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaAdminWhoCommand : IUtkaCommand
{
public string Name => "adminwho";
public Type RequestMessageType => typeof(UtkaAdminWhoRequest);
[Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!;
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage)
{
if(baseMessage is not UtkaAdminWhoRequest message) return;
IoCManager.InjectDependencies(this);
var adminManager = IoCManager.Resolve<IAdminManager>();
var admins = adminManager.ActiveAdmins.ToList();
var adminsList = new List<string>();
foreach (var admin in admins)
{
adminsList.Add(admin.Name);
}
var toUtkaMessage = new UtkaAdminWhoResponse()
{
Admins = adminsList
};
_utkaSocketWrapper.SendMessageToAll(toUtkaMessage);
}
}

View File

@@ -0,0 +1,29 @@
using System.Linq;
using System.Net;
using Content.Server.Administration.Managers;
using Content.Server.Chat.Managers;
using Content.Server.UtkaIntegration.TCP;
using Content.Shared.Chat;
using Robust.Shared.Utility;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaAssayCommand : IUtkaCommand
{
public string Name => "asay";
public Type RequestMessageType => typeof(UtkaAsayRequest);
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage)
{
if(baseMessage is not UtkaAsayRequest message) return;
var ckey = message.ACkey;
if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(ckey)) return;
var chatManager = IoCManager.Resolve<IChatManager>();
chatManager.SendHookAdminChat(ckey, message.Message);
}
}

View File

@@ -0,0 +1,57 @@
using Content.Server.UtkaIntegration.TCP;
using Content.Shared.CCVar;
using Content.Shared.White;
using Robust.Shared.Configuration;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaAuthenticationCommand : IUtkaCommand
{
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly UtkaTCPWrapper _utkaTcpWrapper = default!;
public string Name => "handshake";
public Type RequestMessageType => typeof(UtkaHandshakeMessage);
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage)
{
if (baseMessage is not UtkaHandshakeMessage message)
return;
IoCManager.InjectDependencies(this);
if (string.IsNullOrWhiteSpace(message.Key))
{
SendMessage(session, "key_missmatch");
return;
}
var key = _configurationManager.GetCVar(WhiteCVars.UtkaSocketKey);
if (key != message.Key)
{
SendMessage(session, "key_missmatch");
return;
}
if (session.Authenticated)
{
SendMessage(session, "already_authentificated");
return;
}
session.Authenticated = true;
SendMessage(session, "handshake_accepted");
}
private void SendMessage(UtkaTCPSession session, string message)
{
var response = new UtkaHandshakeMessage()
{
Key = _configurationManager.GetCVar(WhiteCVars.UtkaSocketKey),
Message = message
};
_utkaTcpWrapper.SendMessageToClient(session, response);
}
}

View File

@@ -0,0 +1,36 @@
using Content.Server.Administration.Systems;
using Content.Server.UtkaIntegration.TCP;
using Robust.Server.Player;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaPmCommand : IUtkaCommand
{
public string Name => "discord_pm";
public Type RequestMessageType => typeof(UtkaPmRequest);
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private UtkaTCPWrapper _utkaSocketWrapper = default!;
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage)
{
if(baseMessage is not UtkaPmRequest message) return;
var _bwoink = EntitySystem.Get<BwoinkSystem>();
IoCManager.InjectDependencies(this);
if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(message.Sender) || string.IsNullOrWhiteSpace(message.Reciever)) return;
var toUtkaMessage = new UtkaPmResponse();
if (!_playerManager.TryGetUserId(message.Reciever, out var reciever))
{
toUtkaMessage.Message = false;
_utkaSocketWrapper.SendMessageToAll(toUtkaMessage);
return;
}
_bwoink.SendUtkaBwoinkMessage(reciever, message.Sender, message.Message);
toUtkaMessage.Message = true;
_utkaSocketWrapper.SendMessageToAll(toUtkaMessage);
}
}

View File

@@ -0,0 +1,21 @@
using System.Net;
using Content.Server.Chat.Managers;
using Content.Server.UtkaIntegration.TCP;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaSendOOCMessage : IUtkaCommand
{
public string Name => "ooc";
public Type RequestMessageType => typeof(UtkaOOCRequest);
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage)
{
if (baseMessage is not UtkaOOCRequest message) return;
if(string.IsNullOrWhiteSpace(message.Message) || string.IsNullOrWhiteSpace(message.CKey)) return;
var chatSystem = IoCManager.Resolve<IChatManager>();
chatSystem.SendHookOOC($"{message.CKey}", $"{message.Message}");
}
}

View File

@@ -0,0 +1,85 @@
using System.Linq;
using System.Net;
using Content.Server.Administration.Managers;
using Content.Server.AlertLevel;
using Content.Server.GameTicking;
using Content.Server.Maps;
using Content.Server.RoundEnd;
using Content.Server.Station.Systems;
using Content.Server.UtkaIntegration.TCP;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Player;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaStatusCommand : IUtkaCommand
{
public string Name => "status";
public Type RequestMessageType => typeof(UtkaStatusRequsets);
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly UtkaTCPWrapper _utkaSocketWrapper = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly IGameMapManager _gameMapManager = default!;
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage)
{
if(baseMessage is not UtkaStatusRequsets message) return;
var _gameTicker = EntitySystem.Get<GameTicker>();
var _roundEndSystem = EntitySystem.Get<RoundEndSystem>();
var _station = EntitySystem.Get<StationSystem>();
IoCManager.InjectDependencies(this);
var players = Filter.GetAllPlayers().ToList().Count;
var admins = _adminManager.ActiveAdmins.Select(x => x.Name).ToList().Count;
string shuttleData = string.Empty;
if (_roundEndSystem.ExpectedCountdownEnd == null)
{
shuttleData = "idle";
}
else
{
shuttleData = "called";
}
var roundDuration = _gameTicker.RoundDuration().TotalSeconds;
string? gameMap = null;
string? stationCode = null;
foreach (var station in _station.Stations)
{
if (!_entMan.TryGetComponent(station, out AlertLevelComponent? alert) || stationCode != null)
{
continue;
}
if (alert is { CurrentLevel: { } })
{
stationCode = alert.CurrentLevel;
var map = _gameMapManager.GetSelectedMap();
gameMap = map?.MapName ?? Loc.GetString("discord-round-unknown-map");
}
}
var toUtkaMessage = new UtkaStatusResponse()
{
Players = players,
Admins = admins,
Map = gameMap,
ShuttleStatus = shuttleData,
RoundDuration = roundDuration,
StationCode = stationCode
};
_utkaSocketWrapper.SendMessageToAll(toUtkaMessage);
}
}

View File

@@ -0,0 +1,40 @@
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using Content.Server.UtkaIntegration.TCP;
using Content.Shared.CCVar;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Player;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaWhoCommand : IUtkaCommand
{
public string Name => "who";
public Type RequestMessageType => typeof(UtkaWhoRequest);
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private UtkaTCPWrapper _utkaSocketWrapper = default!;
public void Execute(UtkaTCPSession session, UtkaBaseMessage baseMessage)
{
if(baseMessage is not UtkaWhoRequest _) return;
IoCManager.InjectDependencies(this);
var players = Filter.GetAllPlayers().ToList();
var playerNames = players
.Where(player => player.Status != SessionStatus.Disconnected)
.Select(x => x.Name);
var toUtkaMessage = new UtkaWhoResponse()
{
Players = playerNames.ToList()
};
_utkaSocketWrapper.SendMessageToAll(toUtkaMessage);
}
}

View File

@@ -0,0 +1,113 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.UtkaIntegration.TCP;
using Content.Shared.CCVar;
using Content.Shared.White;
using NetCoreServer;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
using YamlDotNet.Core.Tokens;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaTCPServer : TcpServer
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
public static readonly Dictionary<string, IUtkaCommand> Commands = new();
private List<UtkaTCPSession> _authenticatedSessions = new();
private string? _key;
protected override TcpSession CreateSession() { return new UtkaTCPSession(this); }
public UtkaTCPServer(IPAddress address, int port) : base(address, port)
{
IoCManager.InjectDependencies(this);
_cfg.OnValueChanged(WhiteCVars.UtkaSocketKey, key => _key = key, true);
OptionKeepAlive = true;
}
public void SendMessageToAll(UtkaBaseMessage message)
{
foreach (var session in Sessions.Values.Cast<UtkaTCPSession>())
{
if(!session.Authenticated) continue;
session.SendAsync(JsonSerializer.Serialize(message, message.GetType()));
}
}
public void SendMessageToClient(UtkaTCPSession session, UtkaBaseMessage message)
{
session.SendAsync(JsonSerializer.Serialize(message, message.GetType()));
}
protected override void OnConnected(TcpSession session)
{
var utkaSession = (UtkaTCPSession) session;
var cancellationToken = new CancellationTokenSource();
utkaSession.OnMessageReceived += (sender, message) =>
{
ExecuteCommand(utkaSession, message);
};
var autoDisconnectionTimer = new Timer(25000, false, () =>
{
if (!utkaSession.Authenticated)
{
utkaSession.Disconnect();
}
});
_timerManager.AddTimer(autoDisconnectionTimer, cancellationToken.Token);
}
protected override void OnDisconnecting(TcpSession session)
{
_authenticatedSessions.Remove((session as UtkaTCPSession)!);
base.OnDisconnecting(session);
}
protected override void OnError(SocketError error)
{
}
private void ExecuteCommand(UtkaTCPSession session, UtkaBaseMessage fromUtkaMessage)
{
var command = fromUtkaMessage.Command!;
if (!Commands.ContainsKey(command))
{
return;
}
_taskManager.RunOnMainThread(() => Commands[command].Execute(session, fromUtkaMessage));
}
public static void RegisterCommands()
{
var assembly = Assembly.GetExecutingAssembly();
var types = assembly.GetTypes();
var commands = types.Where(type => typeof(IUtkaCommand).IsAssignableFrom(type) && type.GetInterfaces().Contains(typeof(IUtkaCommand))).ToList();
foreach (var command in commands)
{
if (Activator.CreateInstance(command) is IUtkaCommand utkaCommand)
{
Commands[utkaCommand.Name] = utkaCommand;
}
}
}
}

View File

@@ -0,0 +1,81 @@
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using NetCoreServer;
using Newtonsoft.Json.Linq;
namespace Content.Server.UtkaIntegration.TCP;
public sealed class UtkaTCPSession : TcpSession
{
public event EventHandler<UtkaBaseMessage>? OnMessageReceived;
public bool Authenticated { get; set; }
public UtkaTCPSession(TcpServer server) : base(server)
{
}
protected override void OnReceived(byte[] buffer, long offset, long size)
{
if (!ValidateMessage(buffer, offset, size, out var message))
{
this.SendAsync("Validation fail");
return;
}
OnMessageReceived?.Invoke(this, message!);
}
protected override void OnError(SocketError error)
{
SendAsync($"{error.ToString()}");
base.OnError(error);
}
protected override void OnConnected()
{
SendAsync("Hello from грабли, знай утка я ебал тебя в зад!!!");
base.OnConnected();
}
private bool ValidateMessage(byte[] buffer, long offset, long size, out UtkaBaseMessage? fromDiscordMessage)
{
var message = Encoding.UTF8.GetString(buffer, (int) offset, (int) size);
fromDiscordMessage = null;
if (string.IsNullOrEmpty(message))
{
return false;
}
var commandName = JObject.Parse(message)["command"];
if (commandName == null)
return false;
var utkaCommand = UtkaTCPServer.Commands.Values.FirstOrDefault(x => x.Name == commandName.ToString());
if (utkaCommand == null)
return false;
var messageType = utkaCommand.RequestMessageType;
try
{
fromDiscordMessage = JsonSerializer.Deserialize(message, messageType) as UtkaBaseMessage;
}
catch (Exception e)
{
return false;
}
return true;
}
protected override void OnDisconnected()
{
base.OnDisconnecting();
Dispose();
}
}

View File

@@ -0,0 +1,64 @@
using System.Net;
using Content.Server.UtkaIntegration.TCP;
using Content.Shared.CCVar;
using Content.Shared.White;
using Robust.Shared;
using Robust.Shared.Configuration;
namespace Content.Server.UtkaIntegration;
public sealed class UtkaTCPWrapper
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
private UtkaTCPServer _server = default!;
private string _key = string.Empty;
private bool _initialized;
public void Initialize()
{
if(_initialized) return;
_key = _cfg.GetCVar(WhiteCVars.UtkaSocketKey);
if (string.IsNullOrEmpty(_key))
{
return;
}
var port = _cfg.GetCVar(CVars.NetPort) + 100;
try
{
_server = new UtkaTCPServer(IPAddress.Any, port);
}
catch (Exception e)
{
return;
}
_server.Start();
_initialized = true;
}
public void SendMessageToAll(UtkaBaseMessage message)
{
_server.SendMessageToAll(message);
}
public void SendMessageToClient(UtkaTCPSession session, UtkaBaseMessage message)
{
_server.SendMessageToClient(session, message);
}
public void Shutdown()
{
_server.Stop();
_server.Multicast("Server shutting down.");
_server.DisconnectAll();
_server.Dispose();
}
}

View File

@@ -0,0 +1,172 @@
using System.Text.Json.Serialization;
namespace Content.Server.UtkaIntegration;
public class UtkaBaseMessage
{
[JsonPropertyName("command")]
public virtual string? Command { get; set; }
}
public class UtkaHandshakeMessage : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string Command => "handshake";
[JsonPropertyName("key")]
public string? Key { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public class UtkaOOCRequest : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "ooc";
[JsonPropertyName("ckey")]
public string? CKey { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public class UtkaAsayRequest : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "asay";
[JsonPropertyName("a_ckey")]
public string? ACkey { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public class UtkaPmRequest : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "discord_pm";
[JsonPropertyName("sender")]
public string? Sender { get; set; }
[JsonPropertyName("receiver")]
public string? Reciever { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public class UtkaPmResponse : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "discord_pm";
[JsonPropertyName("message")]
public bool? Message { get; set; }
}
public class UtkaWhoRequest : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "who";
}
public class UtkaWhoResponse : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "who";
[JsonPropertyName("players")]
public List<string>? Players { get; set; }
}
public class UtkaAdminWhoRequest : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "adminwho";
}
public class UtkaAdminWhoResponse : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "adminwho";
[JsonPropertyName("admins")]
public List<string>? Admins { get; set; }
}
public class UtkaStatusRequsets : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "status";
}
public class UtkaStatusResponse : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "status";
[JsonPropertyName("players")]
public int? Players { get; set; }
[JsonPropertyName("admins")]
public int? Admins { get; set; }
[JsonPropertyName("map")]
public string? Map { get; set; }
[JsonPropertyName("round_duration")]
public double RoundDuration { get; set; }
[JsonPropertyName("shuttle_status")]
public string? ShuttleStatus { get; set; }
[JsonPropertyName("station_code")]
public string? StationCode { get; set; }
}
public class UtkaRoundstatusUpdate : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "roundstatus";
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public sealed class UtkaChatEventMessage : UtkaBaseMessage
{
[JsonPropertyName("ckey")]
public string? Ckey { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public sealed class UtkaRoundStatusEvent : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "roundstatus";
[JsonPropertyName("message")]
public string? Message { get; set; }
}
public sealed class UtkaChatMeEvent : UtkaBaseMessage
{
[JsonPropertyName("command")]
public override string? Command => "me";
[JsonPropertyName("ckey")]
public string? Ckey { get; set; }
[JsonPropertyName("message")]
public string? Message { get; set; }
[JsonPropertyName("character_name")]
public string? CharacterName { get; set; }
}

View File

@@ -1166,7 +1166,7 @@ namespace Content.Shared.CCVar
/// <summary>
/// If true, whenever OOC is disabled the Discord OOC relay will also be disabled.
/// </summary>
public static readonly CVarDef<bool> DisablingOOCDisablesRelay = CVarDef.Create("ooc.disabling_ooc_disables_relay", true, CVar.SERVERONLY);
public static readonly CVarDef<bool> DisableHookedOOC = CVarDef.Create("ooc.disabling_ooc_disables_relay", false, CVar.SERVERONLY); //WD-EDIT
/// <summary>
/// Whether or not OOC chat should be enabled during a round.
@@ -1361,6 +1361,12 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> ArrivalsReturns =
CVarDef.Create("shuttle.arrivals_returns", false, CVar.SERVERONLY);
/// <summary>
/// Whether cargo shuttles are enabled.
/// </summary>
public static readonly CVarDef<bool> CargoShuttles =
CVarDef.Create("shuttle.cargo", true, CVar.SERVERONLY);
/// <summary>
/// Whether to automatically spawn escape shuttles.
/// </summary>

View File

@@ -5,7 +5,7 @@ namespace Content.Shared.White;
/*
* PUT YOUR CUSTOM VARS HERE
* DO IT OR I WILL KILL YOU
* with love, by Hail-Rakes
* with love, by hailrakes
*/
@@ -56,4 +56,10 @@ public sealed class WhiteCVars
public static readonly CVarDef<bool> DiscordRoundStartOnly =
CVarDef.Create("discord.round_start_only", false, CVar.SERVERONLY);
/*
* Sockets
*/
public static readonly CVarDef<string> UtkaSocketKey = CVarDef.Create("utka.socket_key", "ass", CVar.SERVERONLY | CVar.CONFIDENTIAL);
}

View File

@@ -71,6 +71,7 @@ SERVER_CONTENT_ASSEMBLIES = [
SERVER_EXTRA_ASSEMBLIES = [
"Npgsql.",
"Microsoft",
"NetCoreServer"
]
SERVER_NOT_EXTRA_ASSEMBLIES = [