mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-15 03:31:38 +01:00
Большие фиксы
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Chat.TypingIndicator;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -25,11 +26,12 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
//WL-Changes-start
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
//WL-Changes-end
|
||||
|
||||
private readonly ContentSpriteControl<Rgba32> _control = new();
|
||||
private ContentSpriteControl<Rgba32> _control = default!;
|
||||
|
||||
public static readonly ResPath Exports = new ResPath("/Exports");
|
||||
|
||||
@@ -39,6 +41,7 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
|
||||
//WL-Changes-start
|
||||
_sawmill = _logMan.GetSawmill("sprite.export");
|
||||
_control = new(_appearance);
|
||||
//WL-Changes-end
|
||||
|
||||
_resManager.UserData.CreateDir(Exports);
|
||||
@@ -92,6 +95,8 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
Action<ContentSpriteControl<Rgba32>.QueueEntry, Image<Rgba32>> action,
|
||||
CancellationToken cancelToken = default)
|
||||
{
|
||||
const string speechPath = "/Textures/Effects/speech.rsi"; //Я ебал вычислять ЕБУЧИЕ TypingIndicator-ы СУКАААА. легче так
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
@@ -101,12 +106,26 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
// Don't want to wait for engine pr
|
||||
var size = Vector2i.Zero;
|
||||
|
||||
foreach (var layer in spriteComp.AllLayers)
|
||||
var comp_scale = spriteComp.Scale;
|
||||
var offset = spriteComp.Offset;
|
||||
|
||||
foreach (var layer_ in spriteComp.AllLayers)
|
||||
{
|
||||
if (layer_ is not SpriteComponent.Layer layer)
|
||||
continue;
|
||||
|
||||
if (!layer.Visible)
|
||||
continue;
|
||||
|
||||
size = Vector2i.ComponentMax(size, layer.PixelSize);
|
||||
var pixel = layer.PixelSize;
|
||||
var scale = layer.Scale;
|
||||
|
||||
var new_x = (int)MathF.Ceiling((float)pixel.X * scale.X * comp_scale.X + offset.X);
|
||||
var new_y = (int)MathF.Ceiling((float)pixel.Y * scale.Y * comp_scale.Y + offset.Y);
|
||||
|
||||
var new_size = new Vector2i(new_x, new_y);
|
||||
|
||||
size = Vector2i.ComponentMax(size, new_size);
|
||||
}
|
||||
|
||||
// Stop asserts
|
||||
@@ -195,14 +214,22 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
internal Queue<QueueEntry> _queuedTextures = new();
|
||||
private readonly AppearanceSystem _appearance;
|
||||
|
||||
internal readonly Queue<QueueEntry> _queuedTextures;
|
||||
|
||||
private readonly Queue<QueueEntry> _defferedTextures;
|
||||
|
||||
private ISawmill _sawmill;
|
||||
|
||||
public ContentSpriteControl()
|
||||
public ContentSpriteControl(AppearanceSystem appearance)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_sawmill = _logMan.GetSawmill("sprite.export");
|
||||
|
||||
_appearance = appearance;
|
||||
_queuedTextures = new();
|
||||
_defferedTextures = new();
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
@@ -214,35 +241,75 @@ public sealed class ContentSpriteSystem : EntitySystem
|
||||
if (queued.Tcs.Task.IsCanceled)
|
||||
continue;
|
||||
|
||||
try
|
||||
if (ShouldBeDeffered(queued))
|
||||
{
|
||||
if (!_entManager.TryGetComponent(queued.Entity, out MetaDataComponent? metadata))
|
||||
continue;
|
||||
|
||||
var result = queued;
|
||||
|
||||
handle.RenderInRenderTarget(queued.Texture, () =>
|
||||
{
|
||||
handle.DrawEntity(result.Entity, result.Texture.Size / 2, Vector2.One, Angle.Zero,
|
||||
overrideDirection: result.Direction);
|
||||
}, Color.Transparent);
|
||||
|
||||
queued.Texture.CopyPixelsToMemory<T>(image =>
|
||||
{
|
||||
queued.Action.Invoke(queued, image);
|
||||
});
|
||||
|
||||
queued.Tcs.SetResult();
|
||||
_defferedTextures.Enqueue(queued);
|
||||
continue;
|
||||
}
|
||||
catch (Exception exc)
|
||||
|
||||
HandleQueue(queued, handle);
|
||||
}
|
||||
|
||||
while (_defferedTextures.TryDequeue(out var dequeue))
|
||||
{
|
||||
if (dequeue.Tcs.Task.IsCanceled)
|
||||
continue;
|
||||
|
||||
if (ShouldBeDeffered(dequeue))
|
||||
{
|
||||
queued.Texture.Dispose();
|
||||
|
||||
if (!string.IsNullOrEmpty(exc.StackTrace))
|
||||
_sawmill.Fatal(exc.StackTrace);
|
||||
|
||||
queued.Tcs.SetException(exc);
|
||||
_queuedTextures.Enqueue(dequeue);
|
||||
continue;
|
||||
}
|
||||
|
||||
HandleQueue(dequeue, handle);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldBeDeffered(QueueEntry entry)
|
||||
{
|
||||
var entity = entry.Entity;
|
||||
|
||||
if (_appearance.TryGetData<TypingIndicatorState>(entity, TypingIndicatorVisuals.State, out var state))
|
||||
{
|
||||
if (state is not TypingIndicatorState.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void HandleQueue(QueueEntry queued, DrawingHandleScreen handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_entManager.TryGetComponent(queued.Entity, out MetaDataComponent? metadata))
|
||||
return;
|
||||
|
||||
var result = queued;
|
||||
|
||||
handle.RenderInRenderTarget(queued.Texture, () =>
|
||||
{
|
||||
handle.DrawEntity(result.Entity, result.Texture.Size / 2, Vector2.One, Angle.Zero,
|
||||
overrideDirection: result.Direction);
|
||||
}, Color.Transparent);
|
||||
|
||||
queued.Texture.CopyPixelsToMemory<T>(image =>
|
||||
{
|
||||
queued.Action.Invoke(queued, image);
|
||||
});
|
||||
|
||||
queued.Tcs.SetResult();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
queued.Texture.Dispose();
|
||||
|
||||
if (!string.IsNullOrEmpty(exc.StackTrace))
|
||||
_sawmill.Fatal(exc.StackTrace);
|
||||
|
||||
queued.Tcs.SetException(exc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,34 +23,40 @@ namespace Content.Client._WL.Poly
|
||||
SubscribeNetworkEvent<PolyServerQueryEvent>(OnServerQuery);
|
||||
}
|
||||
|
||||
private void OnServerQuery(PolyServerQueryEvent args)
|
||||
private async void OnServerQuery(PolyServerQueryEvent args)
|
||||
{
|
||||
var ent = GetEntity(args.Entity);
|
||||
|
||||
#pragma warning disable CS4014
|
||||
_contentSpriteSystem.Export(ent, Direction.South, (queue, image) =>
|
||||
try
|
||||
{
|
||||
try
|
||||
await _contentSpriteSystem.Export(ent, Direction.South, (queue, image) =>
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
try
|
||||
{
|
||||
//TODO: проверить захватывает ли GC потоки, кхм
|
||||
using var stream = new MemoryStream(1024);
|
||||
|
||||
image.SaveAsPng(stream);
|
||||
image.SaveAsPng(stream);
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
stream.Position = 0;
|
||||
var str = Convert.ToBase64String(stream.GetBuffer());
|
||||
|
||||
stream.Position = 0;
|
||||
var str = reader.ReadToEnd();
|
||||
var ev = new PolyClientResponseEvent(str, args.QueryId);
|
||||
|
||||
var ev = new PolyClientResponseEvent(str, args.QueryId);
|
||||
_sawmill.Info($"Запрос от Поли успешно обработан! Сущность: {ToPrettyString(args.Entity)}");
|
||||
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_sawmill.Error("Неизвестная ошибка при рендере фотографии для Поли!");
|
||||
}
|
||||
});
|
||||
#pragma warning restore CS4014
|
||||
RaiseNetworkEvent(ev);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_sawmill.Error($"Неизвестная ошибка при рендере фотографии для Поли! {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
_sawmill.Error($"Error: {exc.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,20 @@ public sealed partial class ServerApi
|
||||
HttpMethod method,
|
||||
string exactPath,
|
||||
Func<IStatusHandlerContext, Actor, Dictionary<string, string>, Task> handler)
|
||||
{
|
||||
RegisterParameterizedHandler(method, exactPath, async (context, maps) =>
|
||||
{
|
||||
if (await CheckActor(context) is not { } actor)
|
||||
return;
|
||||
|
||||
await handler(context, actor, maps);
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterParameterizedHandler(
|
||||
HttpMethod method,
|
||||
string exactPath,
|
||||
Func<IStatusHandlerContext, Dictionary<string, string>, Task> handler)
|
||||
{
|
||||
_statusHost.AddHandler(async context =>
|
||||
{
|
||||
@@ -56,14 +70,11 @@ public sealed partial class ServerApi
|
||||
if (!await CheckAccess(context))
|
||||
return true;
|
||||
|
||||
if (await CheckActor(context) is not { } actor)
|
||||
return true;
|
||||
|
||||
var formatted_maps = CheckPathes(absolute_path, exactPath);
|
||||
if (formatted_maps.Count == 0)
|
||||
return true;
|
||||
|
||||
await handler(context, actor, formatted_maps);
|
||||
await handler(context, formatted_maps);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -80,13 +91,13 @@ public sealed partial class ServerApi
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
var to_replace = match.Groups[0].Value;
|
||||
var name = match.Groups[1].Value;
|
||||
var to_replace = match.Groups[1].Value;
|
||||
var name = match.Groups[2].Value;
|
||||
|
||||
var inner_regex = new Regex(predictedPath.Replace(to_replace, "(.*)"));
|
||||
var inner_match = inner_regex.Match(realPath).Groups[0].Value;
|
||||
var inner_match = inner_regex.Match(realPath).Groups[1].Value;
|
||||
|
||||
dict.Add(name, inner_match);
|
||||
dict.Add(name.Trim(), inner_match.Trim());
|
||||
}
|
||||
|
||||
return dict;
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server._WL.DiscordAuth;
|
||||
using Content.Server._WL.Poly;
|
||||
@@ -32,7 +31,6 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Content.Server.Administration;
|
||||
|
||||
@@ -119,6 +117,8 @@ public sealed partial class ServerApi : IPostInjectInit
|
||||
RegisterActorHandler(HttpMethod.Patch, "/admin/actions/server/shutdown", ShutdownServer);
|
||||
|
||||
RegisterHandler(HttpMethod.Get, "/admin/info/poly/random_message", PolyMessage);
|
||||
|
||||
RegisterParameterizedHandler(HttpMethod.Get, $"/admin/info/poly/images/{{${Constants.PolyMapImage}}}.png", PolyImage);
|
||||
//wL-Changes-end
|
||||
}
|
||||
|
||||
@@ -163,17 +163,55 @@ public sealed partial class ServerApi : IPostInjectInit
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
var is_ready = poly_system.IsReadyToPick();
|
||||
var how_long = poly_system.HowLongBeforeReady();
|
||||
|
||||
var msg = is_ready
|
||||
? "Поли ожидает подходящего сообщения!"
|
||||
: $"Поли устала! До готовности: {how_long}";
|
||||
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.None,
|
||||
HttpStatusCode.InternalServerError,
|
||||
$"Поли ещё не выбрала подходящего сообщения! До готовности: {poly_system.HowLongBeforeReady()}");
|
||||
msg);
|
||||
return;
|
||||
}
|
||||
|
||||
await context.RespondJsonAsync(entry.Value);
|
||||
}
|
||||
|
||||
private async Task PolyImage(IStatusHandlerContext context, Dictionary<string, string> maps)
|
||||
{
|
||||
var poly_system = _entitySystemManager.GetEntitySystem<PolySystem>();
|
||||
|
||||
if (!maps.TryGetValue(Constants.PolyMapImage, out var map))
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.None,
|
||||
HttpStatusCode.InternalServerError,
|
||||
"Ошибка при получении ссылки!");
|
||||
return;
|
||||
}
|
||||
|
||||
using var stream = poly_system.PickImage(map);
|
||||
|
||||
if (stream == null)
|
||||
{
|
||||
await RespondError(
|
||||
context,
|
||||
ErrorCode.None,
|
||||
HttpStatusCode.InternalServerError,
|
||||
"Изображение не было найдено!");
|
||||
return;
|
||||
}
|
||||
|
||||
await using var resp_stream = await context.RespondStreamAsync();
|
||||
|
||||
stream.CopyTo(resp_stream);
|
||||
}
|
||||
|
||||
private async Task ShutdownServer(IStatusHandlerContext context, Actor actor)
|
||||
{
|
||||
if (!await IsAdmin(actor.Record.UserId))
|
||||
@@ -990,4 +1028,11 @@ public sealed partial class ServerApi : IPostInjectInit
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
//WL-Changes-start
|
||||
private static class Constants
|
||||
{
|
||||
public const string PolyMapImage = "image";
|
||||
}
|
||||
//WL-Changes-end
|
||||
}
|
||||
|
||||
@@ -35,10 +35,8 @@ using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Server.Player;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
|
||||
@@ -12,6 +12,7 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server._WL.Poly
|
||||
@@ -24,6 +25,9 @@ namespace Content.Server._WL.Poly
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configMan = default!;
|
||||
[Dependency] private readonly IPlayerManager _playMan = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private List<MessageEntry> _messages = default!;
|
||||
|
||||
@@ -33,6 +37,7 @@ namespace Content.Server._WL.Poly
|
||||
private bool _neededCleanup = false;
|
||||
|
||||
private Dictionary<string, ChatMessage> _queriedEntities = default!;
|
||||
private Dictionary<string, string?> _handledImages = default!;
|
||||
|
||||
private const int MAX_QUERIES_PER_PLAYER = 20;
|
||||
|
||||
@@ -42,6 +47,9 @@ namespace Content.Server._WL.Poly
|
||||
|
||||
_messages = new();
|
||||
_queriedEntities = new();
|
||||
_handledImages = new();
|
||||
|
||||
_sawmill = _logMan.GetSawmill("poly.server");
|
||||
|
||||
_readyToPick = _configMan.GetCVar(WLCVars.PolyNeededRoundEndCleanup);
|
||||
_chooseInterval = TimeSpan.FromSeconds(_configMan.GetCVar(WLCVars.PolyMessageChooseCooldown));
|
||||
@@ -49,8 +57,6 @@ namespace Content.Server._WL.Poly
|
||||
Subs.CVar(_configMan, WLCVars.PolyMessageChooseCooldown, (new_value) => _chooseInterval = TimeSpan.FromSeconds(new_value), true);
|
||||
Subs.CVar(_configMan, WLCVars.PolyNeededRoundEndCleanup, (needed) => _neededCleanup = needed);
|
||||
|
||||
UpdatesOutsidePrediction = false;
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>((_) =>
|
||||
{
|
||||
if (!_neededCleanup)
|
||||
@@ -76,8 +82,10 @@ namespace Content.Server._WL.Poly
|
||||
if (!ShouldMessageBeChosen(msg))
|
||||
return;
|
||||
|
||||
_readyToPick = false;
|
||||
QueryAddMessage(msg);
|
||||
|
||||
ResetTimer();
|
||||
_readyToPick = false;
|
||||
};
|
||||
|
||||
_playMan.PlayerStatusChanged += (sender, args) =>
|
||||
@@ -85,7 +93,7 @@ namespace Content.Server._WL.Poly
|
||||
if (_playMan.Sessions.Length - 1 != 0)
|
||||
return;
|
||||
|
||||
if (args.NewStatus != SessionStatus.Connected)
|
||||
if (args.NewStatus is not SessionStatus.Connected or SessionStatus.InGame)
|
||||
return;
|
||||
|
||||
HandleQueries();
|
||||
@@ -103,7 +111,7 @@ namespace Content.Server._WL.Poly
|
||||
|
||||
if (_time >= _chooseInterval)
|
||||
{
|
||||
_time = TimeSpan.Zero;
|
||||
ResetTimer();
|
||||
_readyToPick = true;
|
||||
}
|
||||
}
|
||||
@@ -121,19 +129,22 @@ namespace Content.Server._WL.Poly
|
||||
{
|
||||
var queried = args.QueryId;
|
||||
|
||||
if (!_queriedEntities.TryGetValue(queried, out var msg)) //Если событие было выслано всем клиентам, то каждый клиент отправит ответ,
|
||||
return; //И тогда от каждого клиента добавится сообщение, эта проверка нужна, чтоб избежать этого
|
||||
if (!_queriedEntities.TryGetValue(queried, out var msg))
|
||||
return;
|
||||
|
||||
_queriedEntities.Remove(queried);
|
||||
|
||||
var entry = MessageToEntry(msg, args.Stream);
|
||||
var entry = MessageToEntry(msg, queried);
|
||||
|
||||
_messages.Add(entry);
|
||||
_handledImages.Add(queried, args.Stream);
|
||||
}
|
||||
|
||||
private void HandleQueries()
|
||||
{
|
||||
var sessions = _playMan.Sessions.ToDictionary(k => k, v => 0);
|
||||
var sessions = _playMan.Sessions
|
||||
.Where(s => s.Status == SessionStatus.InGame)
|
||||
.ToDictionary(k => k, v => 0);
|
||||
|
||||
if (sessions.Count == 0)
|
||||
return;
|
||||
@@ -143,11 +154,11 @@ namespace Content.Server._WL.Poly
|
||||
if (session_pair == null)
|
||||
return;
|
||||
|
||||
var session = session_pair.Value.Key;
|
||||
var queries = session_pair.Value.Value;
|
||||
|
||||
foreach (var item in _queriedEntities)
|
||||
{
|
||||
var session = session_pair.Value.Key;
|
||||
var queries = session_pair.Value.Value;
|
||||
|
||||
if (queries > MAX_QUERIES_PER_PLAYER)
|
||||
{
|
||||
session_pair = PickSession();
|
||||
@@ -179,6 +190,28 @@ namespace Content.Server._WL.Poly
|
||||
}
|
||||
|
||||
#region Public
|
||||
public void ResetTimer()
|
||||
{
|
||||
_time = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
public Stream? PickImage(string id)
|
||||
{
|
||||
if (!_handledImages.TryGetValue(id, out var stream_string) || stream_string == null)
|
||||
{
|
||||
_handledImages.Remove(id);
|
||||
return null;
|
||||
}
|
||||
|
||||
_handledImages.Remove(id);
|
||||
|
||||
var bytes = Convert.FromBase64String(stream_string);
|
||||
|
||||
var stream = new MemoryStream(bytes);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
_messages.Clear();
|
||||
@@ -190,7 +223,7 @@ namespace Content.Server._WL.Poly
|
||||
return _readyToPick;
|
||||
}
|
||||
|
||||
public MessageEntry MessageToEntry(ChatMessage msg, string? pngBase64 = null)
|
||||
public MessageEntry MessageToEntry(ChatMessage msg, string id)
|
||||
{
|
||||
var sender_ent = GetEntity(msg.SenderEntity);
|
||||
|
||||
@@ -201,7 +234,7 @@ namespace Content.Server._WL.Poly
|
||||
SenderEntityName = Name(sender_ent),
|
||||
RoundId = _ticker.RoundId,
|
||||
IsRoundFlow = _ticker.RunLevel == GameRunLevel.InRound,
|
||||
PngBase64 = pngBase64
|
||||
ID = id
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,7 +259,7 @@ namespace Content.Server._WL.Poly
|
||||
ChatChannel.Damage |
|
||||
ChatChannel.Visual |
|
||||
ChatChannel.Notifications |
|
||||
ChatChannel.AdminRelated |
|
||||
//ChatChannel.AdminRelated | //я всегда буду злодеем >:3
|
||||
ChatChannel.Unspecified;
|
||||
|
||||
if (unnecessary_flags.HasFlag(msg.Channel))
|
||||
@@ -248,5 +281,5 @@ namespace Content.Server._WL.Poly
|
||||
string SenderEntityName,
|
||||
int RoundId,
|
||||
bool IsRoundFlow,
|
||||
string? PngBase64 = null);
|
||||
string ID);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user