fish-station/Content.Server/_Sunrise/CartridgeLoader/Cartridges/PhotoCartridgeSystem.cs

287 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Linq;
using Content.Server.CartridgeLoader;
using Content.Server.DeviceNetwork.Systems;
using Content.Server._Sunrise.Messenger;
using Content.Server.Station.Systems;
using Content.Shared.CartridgeLoader;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared._Sunrise.CartridgeLoader.Cartridges;
using Robust.Server.Player;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Audio.Systems;
namespace Content.Server._Sunrise.CartridgeLoader.Cartridges;
public sealed class PhotoCartridgeSystem : EntitySystem
{
[Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!;
[Dependency] private readonly NetTexturesManager _netTexturesManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly MessengerServerSystem _messengerServer = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly SingletonDeviceNetServerSystem _singletonServer = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _sawmill = default!;
private const int MaxPhotosPerUser = 50;
private const int MaxPhotoSizeBytes = 2 * 1024 * 1024;
private const double MinTimeBetweenCapturesSeconds = 1.0;
private const int MaxPhotoWidth = 512;
private const int MaxPhotoHeight = 512;
private readonly Dictionary<ICommonSession, TimeSpan> _lastCaptureTimes = new();
public override void Initialize()
{
base.Initialize();
_sawmill = _logManager.GetSawmill("photo.cartridge");
SubscribeLocalEvent<PhotoCartridgeComponent, CartridgeMessageEvent>(OnUiMessage);
SubscribeLocalEvent<PhotoCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
_netTexturesManager.OnPhotoCaptureMessage = OnPhotoCaptureMessage;
}
private void OnUiMessage(EntityUid uid, PhotoCartridgeComponent component, CartridgeMessageEvent args)
{
if (args is not PhotoUiMessageEvent photoMessage)
return;
var loaderUid = GetEntity(args.LoaderUid);
if (loaderUid == EntityUid.Invalid)
return;
switch (photoMessage.Action)
{
case PhotoUiAction.CapturePhoto:
HandleCapturePhoto(uid, component, loaderUid);
break;
case PhotoUiAction.SendPhotoToMessenger:
HandleSendPhotoToMessenger(uid, component, loaderUid, photoMessage.PhotoId, photoMessage.RecipientId, photoMessage.GroupId);
break;
case PhotoUiAction.RequestGallery:
UpdateUiState(uid, loaderUid, component);
break;
case PhotoUiAction.DeletePhoto:
HandleDeletePhoto(uid, component, loaderUid, photoMessage.PhotoId);
break;
case PhotoUiAction.ToggleFlash:
if (photoMessage.FlashEnabled.HasValue)
{
component.FlashEnabled = photoMessage.FlashEnabled.Value;
UpdateUiState(uid, loaderUid, component);
}
break;
}
}
private void HandleDeletePhoto(EntityUid uid, PhotoCartridgeComponent component, EntityUid loaderUid, string? photoId)
{
if (string.IsNullOrEmpty(photoId) || !component.PhotoGallery.TryGetValue(photoId, out var metadata))
return;
component.PhotoGallery.Remove(photoId);
UpdateUiState(uid, loaderUid, component);
}
private void OnUiReady(EntityUid uid, PhotoCartridgeComponent component, CartridgeUiReadyEvent args)
{
UpdateUiState(uid, args.Loader, component);
}
private void HandleCapturePhoto(EntityUid uid, PhotoCartridgeComponent component, EntityUid loaderUid)
{
if (component.PhotoGallery.Count >= MaxPhotosPerUser)
{
UpdateUiState(uid, loaderUid, component, errorMessage: Loc.GetString("photo-cartridge-limit-reached"));
return;
}
if (component.FlashEnabled)
{
Spawn(component.FlashEffect, _transform.GetMapCoordinates(loaderUid));
}
_audio.PlayPvs(component.Sound, loaderUid);
UpdateUiState(uid, loaderUid, component);
}
/// <summary>
/// Обработчик сетевого сообщения с захваченным изображением от клиента
/// </summary>
private void OnPhotoCaptureMessage(PdaPhotoCaptureMessage msg)
{
if (!_playerManager.TryGetSessionByChannel(msg.MsgChannel, out var session))
return;
if (_lastCaptureTimes.TryGetValue(session, out var lastCapture))
{
var timeSinceLastCapture = _gameTiming.CurTime - lastCapture;
if (timeSinceLastCapture.TotalSeconds < MinTimeBetweenCapturesSeconds)
{
_sawmill.Debug($"Photo capture rejected: cooldown active ({timeSinceLastCapture.TotalSeconds:F2}s < {MinTimeBetweenCapturesSeconds}s)");
return;
}
}
if (msg.ImageData.Length > MaxPhotoSizeBytes)
{
_sawmill.Warning($"Photo capture rejected: image too large ({msg.ImageData.Length} bytes > {MaxPhotoSizeBytes} bytes)");
return;
}
if (msg.Width <= 0 || msg.Height <= 0 || msg.Width > MaxPhotoWidth || msg.Height > MaxPhotoHeight)
{
_sawmill.Warning($"Photo capture rejected: invalid dimensions ({msg.Width}x{msg.Height})");
return;
}
var entity = GetEntity(msg.LoaderUid);
PhotoCartridgeComponent? photoComponent;
EntityUid? cartridgeUid;
EntityUid pdaUid = EntityUid.Invalid;
if (_cartridgeLoader.TryGetProgram(entity, out cartridgeUid, out photoComponent))
{
pdaUid = entity;
}
else if (TryComp(entity, out photoComponent) && TryComp<CartridgeComponent>(entity, out var cartridge) && cartridge.LoaderUid.HasValue)
{
pdaUid = cartridge.LoaderUid.Value;
cartridgeUid = entity;
}
if (photoComponent == null || cartridgeUid == null)
{
_sawmill.Warning($"Photo capture rejected: photo cartridge not found for entity {ToPrettyString(entity)}");
return;
}
if (photoComponent.PhotoGallery.Count >= MaxPhotosPerUser)
{
UpdateUiState(cartridgeUid!.Value, pdaUid, photoComponent, errorMessage: Loc.GetString("photo-cartridge-limit-reached"));
return;
}
var photoId = Guid.NewGuid().ToString();
var imagePath = $"/NetTextures/Messenger/{photoId}.png";
_netTexturesManager.RegisterDynamicResource(imagePath, msg.ImageData);
var timestamp = _gameTiming.CurTime;
var metadata = new PhotoMetadata(photoId, imagePath, timestamp);
photoComponent.PhotoGallery[photoId] = metadata;
_lastCaptureTimes[session] = timestamp;
_sawmill.Info($"Photo captured from {session.Name}: {photoId}, path: {imagePath}, size: {msg.Width}x{msg.Height}, {msg.ImageData.Length} bytes");
UpdateUiState(cartridgeUid!.Value, pdaUid, photoComponent);
}
private void HandleSendPhotoToMessenger(EntityUid uid, PhotoCartridgeComponent component, EntityUid loaderUid, string? photoId, string? recipientId, string? groupId)
{
if (string.IsNullOrWhiteSpace(photoId))
return;
if (!component.PhotoGallery.TryGetValue(photoId, out var photoMetadata))
{
UpdateUiState(uid, loaderUid, component, errorMessage: Loc.GetString("photo-cartridge-photo-not-found"));
return;
}
var messengerServer = FindMessengerServer(loaderUid);
if (messengerServer == null)
{
UpdateUiState(uid, loaderUid, component, errorMessage: Loc.GetString("photo-cartridge-messenger-unavailable"));
return;
}
if (!TryComp<DeviceNetworkComponent>(loaderUid, out var pdaDevice))
return;
var userId = pdaDevice.Address;
if (string.IsNullOrEmpty(userId))
return;
var timestamp = _gameTiming.CurTime;
var content = Loc.GetString("photo-cartridge-photo-text");
if (!string.IsNullOrWhiteSpace(groupId))
{
_messengerServer.SendGroupMessageWithImage(messengerServer.Value, userId, groupId, content, photoMetadata.ImagePath, timestamp);
}
else if (!string.IsNullOrWhiteSpace(recipientId))
{
_messengerServer.SendPersonalMessageWithImage(messengerServer.Value, userId, recipientId, content, photoMetadata.ImagePath, timestamp);
}
else
{
UpdateUiState(uid, loaderUid, component, errorMessage: Loc.GetString("photo-cartridge-recipient-not-specified"));
return;
}
UpdateUiState(uid, loaderUid, component);
}
private EntityUid? FindMessengerServer(EntityUid pdaUid)
{
var station = _stationSystem.GetOwningStation(pdaUid);
if (station == null)
{
var xform = Transform(pdaUid);
var mapId = xform.MapID;
foreach (var s in _stationSystem.GetStations())
{
if (Transform(s).MapID == mapId)
{
station = s;
break;
}
}
}
if (station == null)
station = _stationSystem.GetStations().FirstOrDefault();
if (station == null)
return null;
if (!_singletonServer.TryGetActiveServerAddress<MessengerServerComponent>(station.Value, out var serverAddress))
return null;
var serverQuery = EntityQueryEnumerator<MessengerServerComponent, DeviceNetworkComponent>();
while (serverQuery.MoveNext(out var uid, out _, out var deviceNetwork))
{
if (deviceNetwork.Address == serverAddress && _singletonServer.IsActiveServer(uid))
{
return uid;
}
}
return null;
}
private void UpdateUiState(EntityUid uid, EntityUid loaderUid, PhotoCartridgeComponent component, string? errorMessage = null)
{
var state = new PhotoUiState(
photos: component.PhotoGallery,
cameraReady: component.PhotoGallery.Count < MaxPhotosPerUser,
flashEnabled: component.FlashEnabled,
errorMessage: errorMessage
);
_cartridgeLoader.UpdateCartridgeUiState(loaderUid, state);
}
private EntityUid GetEntity(NetEntity netEntity)
{
return EntityManager.GetEntity(netEntity);
}
}