fish-station/Content.Server/_Sunrise/PrinterDoc/PrinterDocSystem.cs
2025-08-15 17:13:20 +03:00

471 lines
18 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 System.Text.RegularExpressions;
using Content.Server.GameTicking.Events;
using Content.Server.Materials;
using Content.Server.Station.Systems;
using Content.Shared._Sunrise.PrinterDoc;
using Content.Shared._Sunrise.SunriseCCVars;
using Content.Shared.Buckle.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Emag.Components;
using Content.Shared.Emag.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Materials;
using Content.Shared.Paper;
using Content.Shared.Tag;
using Content.Shared.Labels.Components;
using Content.Shared.Labels.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server._Sunrise.PrinterDoc;
public sealed class PrinterDocSystem : EntitySystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly PaperSystem _paperSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly LabelSystem _labelSystem = default!;
private TimeSpan _roundStartTime;
private readonly Dictionary<string, string> _docCache = new();
private static readonly Regex DocRegex =
new("<Document>(.*?)</Document>", RegexOptions.Singleline | RegexOptions.Compiled);
private const string SunriseRoot = "/ServerInfo/Documents/_Sunrise"; //Стандартные шаблоны
private const string LustRoot = "/ServerInfo/Documents/_Lust"; //Путь к документам Qillu для Lust_Station
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
SubscribeLocalEvent<PrinterDocComponent, BoundUIOpenedEvent>(OnUiOpened);
SubscribeLocalEvent<PrinterDocComponent, PrinterDocPrintMessage>(OnPrintMessage);
SubscribeLocalEvent<PrinterDocComponent, PrinterDocCopyMessage>(OnCopyMessage);
SubscribeLocalEvent<PrinterDocComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PrinterDocComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<PrinterDocComponent, EntInsertedIntoContainerMessage>(OnItemInserted);
SubscribeLocalEvent<PrinterDocComponent, EntRemovedFromContainerMessage>(OnItemRemoved);
SubscribeLocalEvent<PrinterDocComponent, MaterialAmountChangedEvent>(OnMaterialAmountChanged);
SubscribeLocalEvent<PrinterDocComponent, StrappedEvent>(OnStasisStrapped);
SubscribeLocalEvent<PrinterDocComponent, UnstrappedEvent>(OnStasisUnstrapped);
SubscribeLocalEvent<PrinterDocComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<PrinterDocComponent, ComponentStartup>(OnPrinterStartup);
_configManager.OnValueChanged(SunriseCCVars.PrinterDocTemplatePack, _ =>
{
CacheAllDocuments();
RebuildAllPrinters();
}, false);
CacheAllDocuments();
}
private void OnRoundStart(RoundStartingEvent ev)
{
_roundStartTime = _timing.CurTime;
}
private void CacheAllDocuments()
{
_docCache.Clear();
foreach (var template in _proto.EnumeratePrototypes<DocTemplatePrototype>())
{
if (template.Content == default)
continue;
var resolved = ResolveTemplatePath(template.Content);
if (resolved is null)
continue;
using var file = _resourceManager.ContentFileReadText(resolved.Value);
var fileText = file.ReadToEnd();
var match = DocRegex.Match(fileText);
var content = match.Success ? match.Groups[1].Value.Trim() : fileText.Trim();
_docCache[template.ID] = content;
}
}
// Мгновенная смена шаблонов в принтерах при смене CVar-а
private void RebuildAllPrinters()
{
var query = EntityQueryEnumerator<PrinterDocComponent>();
while (query.MoveNext(out var uid, out var comp))
{
comp.Templates.Clear();
var isEmagged = HasComp<EmaggedComponent>(uid);
foreach (var template in _proto.EnumeratePrototypes<DocTemplatePrototype>())
{
if (string.IsNullOrEmpty(template.Component))
continue;
if (template.Content == default)
continue;
var resolved = ResolveTemplatePath(template.Content);
if (!resolved.HasValue)
continue;
if (template.IsPublic || isEmagged)
comp.Templates.Add(template.ID);
}
UpdateUserInterface(uid, comp);
}
}
private void OnUiOpened(EntityUid uid, PrinterDocComponent comp, BoundUIOpenedEvent args) => UpdateUserInterface(uid, comp);
private void OnSolutionChanged(EntityUid uid, PrinterDocComponent comp, SolutionContainerChangedEvent args) => UpdateUserInterface(uid, comp);
private void OnItemRemoved(EntityUid uid, PrinterDocComponent comp, EntRemovedFromContainerMessage args) => UpdateUserInterface(uid, comp);
private void OnStasisStrapped(EntityUid uid, PrinterDocComponent comp, ref StrappedEvent args) => UpdateUserInterface(uid, comp);
private void OnStasisUnstrapped(EntityUid uid, PrinterDocComponent comp, ref UnstrappedEvent args) => UpdateUserInterface(uid, comp);
private void OnMaterialAmountChanged(EntityUid uid, PrinterDocComponent comp, ref MaterialAmountChangedEvent args) => UpdateUserInterface(uid, comp);
private void OnItemInserted(EntityUid uid, PrinterDocComponent comp, EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != PrinterDocComponent.CopySlotId)
return;
if (!TryComp<PaperComponent>(args.Entity, out var paperComp))
{
UpdateUserInterface(uid, comp);
return;
}
if (string.IsNullOrWhiteSpace(paperComp.Content))
{
Timer.Spawn(TimeSpan.FromMilliseconds(1), () =>
{
if (Deleted(uid) || Deleted(args.Entity))
return;
if (!comp.CopySlot.HasItem || comp.CopySlot.Item != args.Entity)
return;
if (TryComp<TagComponent>(args.Entity, out var tag) && tag.Tags.Contains("Paper"))
{
_materialStorage.TryChangeMaterialAmount(uid, comp.PaperMaterial, 100);
QueueDel(args.Entity);
UpdateUserInterface(uid, comp);
}
});
return;
}
UpdateUserInterface(uid, comp);
}
private void OnPrintMessage(EntityUid uid, PrinterDocComponent comp, PrinterDocPrintMessage msg)
{
if (comp.JobQueue.Count >= comp.MaxQueueSize)
return;
if (!TryConsumeResources(uid, comp))
return;
comp.JobQueue.Enqueue((PrinterJobType.Print, msg.TemplateId));
UpdateUserInterface(uid, comp);
}
private void OnCopyMessage(EntityUid uid, PrinterDocComponent comp, PrinterDocCopyMessage msg)
{
if (comp.JobQueue.Count >= comp.MaxQueueSize)
return;
if (!TryConsumeResources(uid, comp))
return;
comp.JobQueue.Enqueue((PrinterJobType.Copy, null));
UpdateUserInterface(uid, comp);
}
private void OnMapInit(EntityUid uid, PrinterDocComponent comp, MapInitEvent args)
{
_itemSlotsSystem.AddItemSlot(uid, PrinterDocComponent.CopySlotId, comp.CopySlot);
comp.Templates.Clear();
var isEmagged = HasComp<EmaggedComponent>(uid);
foreach (var template in _proto.EnumeratePrototypes<DocTemplatePrototype>())
{
if (string.IsNullOrEmpty(template.Component))
continue;
if (template.Content == default)
continue;
var resolved = ResolveTemplatePath(template.Content);
if (!resolved.HasValue)
continue;
if (template.IsPublic || isEmagged)
comp.Templates.Add(template.ID);
}
UpdateUserInterface(uid, comp);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var enumerator = EntityQueryEnumerator<PrinterDocComponent>();
while (enumerator.MoveNext(out var uid, out var comp))
{
if (comp.IsProcessing || comp.JobQueue.Count == 0)
continue;
var (type, templateId) = comp.JobQueue.Dequeue();
comp.IsProcessing = true;
string jobTitle = type == PrinterJobType.Print && templateId != null &&
_proto.TryIndex<DocTemplatePrototype>(templateId, out var proto)
? Loc.GetString(proto.Name)
: templateId ?? "Документ";
comp.CurrentJobView = new PrinterJobView(jobTitle, type);
UpdateUserInterface(uid, comp);
_audioSystem.PlayPvs(comp.PrintSound, uid);
Timer.Spawn(TimeSpan.FromSeconds(comp.JobDuration), () =>
{
if (Deleted(uid))
return;
_ = type switch
{
PrinterJobType.Print when templateId != null => TryPrintInternal(uid, comp, templateId),
PrinterJobType.Copy => TryCopyInternal(uid, comp),
_ => false
};
comp.IsProcessing = false;
comp.CurrentJobView = null;
UpdateUserInterface(uid, comp);
});
}
}
private bool TryConsumeResources(EntityUid uid, PrinterDocComponent comp)
{
if (!_solution.TryGetSolution(uid, comp.Solution, out _, out var solution))
return false;
if (!solution.TryGetReagentQuantity(new ReagentId(comp.IncReagentProto, null), out var incVolume) || incVolume < comp.IncCost)
return false;
if (!_materialStorage.TryChangeMaterialAmount(uid, comp.PaperMaterial, -comp.PaperCost))
return false;
solution.RemoveReagent(new ReagentId(comp.IncReagentProto, null), comp.IncCost);
return true;
}
private bool TryPrintInternal(EntityUid uid, PrinterDocComponent comp, string templateId)
{
var paper = Spawn(comp.PaperProtoId, Transform(uid).Coordinates);
if (!TryComp<PaperComponent>(paper, out var paperComp) || !_docCache.TryGetValue(templateId, out var content))
return false;
int offsetHours = 3;
int offsetYears = 1000;
try
{
offsetHours = _configManager.GetCVar(SunriseCCVars.PrinterDocTimeOffsetHours);
offsetYears = _configManager.GetCVar(SunriseCCVars.PrinterDocYearOffset);
}
catch
{
}
var date = DateTime.UtcNow
.AddHours(offsetHours)
.AddYears(offsetYears)
.ToString("dd.MM.yyyy");
var shift = _timing.CurTime - _roundStartTime;
var timeString = $"{shift:hh\\:mm} {date}";
var station = _stationSystem.GetOwningStation(uid);
var stationName = station is null ? string.Empty : Name(station.Value);
content = content.Replace("{timeString}", timeString);
content = content.Replace("{stationName}", stationName);
_paperSystem.SetContent((paper, paperComp), content);
return true;
}
private bool TryCopyInternal(EntityUid uid, PrinterDocComponent comp)
{
var paper = Spawn(comp.PaperProtoId, Transform(uid).Coordinates);
if (!TryComp<PaperComponent>(paper, out var paperComp))
return false;
if (TryComp<StrapComponent>(uid, out var strap) && strap.BuckledEntities.Count != 0)
{
var buckled = strap.BuckledEntities.First();
if (TryComp<HumanoidAppearanceComponent>(buckled, out var humanoidAppearance))
{
var buttTexture = _proto.TryIndex(humanoidAppearance.Species, out var species) ? species.ButtScanTexture : null;
var content = $"[tex path=\"{buttTexture}\" scale=15]";
_paperSystem.SetContent((paper, paperComp), content);
paperComp.EditingDisabled = true;
return true;
}
}
if (comp.CopySlot.HasItem && TryComp<PaperComponent>(comp.CopySlot.Item, out var srcPaper))
{
_paperSystem.SetContent((paper, paperComp), srcPaper.Content);
paperComp.EditingDisabled = srcPaper.EditingDisabled;
if (srcPaper.StampState != null && srcPaper.StampedBy != null)
foreach (var stamp in srcPaper.StampedBy)
_paperSystem.TryStamp((paper, paperComp), stamp, srcPaper.StampState);
if (TryComp<LabelComponent>(comp.CopySlot.Item, out var srcLabel) && !string.IsNullOrWhiteSpace(srcLabel.CurrentLabel))
_labelSystem.Label(paper, srcLabel.CurrentLabel);
return true;
}
return false;
}
public void UpdateUserInterface(EntityUid uid, PrinterDocComponent comp)
{
if (!_solution.TryGetSolution(uid, comp.Solution, out _, out var solution))
return;
float incVolume = solution.TryGetReagentQuantity(new ReagentId(comp.IncReagentProto, null), out var inc) ? inc.Value : 0;
var availablePaper = _materialStorage.GetMaterialAmount(uid, comp.PaperMaterial);
var state = new PrinterDocBoundUserInterfaceState(
paperCount: availablePaper / 100,
inkAmount: incVolume / 100,
templates: comp.Templates.Select(t => t.ToString()).ToList(),
canCopy: CanCopy(uid, comp),
currentJob: comp.CurrentJobView,
queue: comp.JobQueue.Select(j =>
{
string title = j.Type == PrinterJobType.Print && j.TemplateId != null &&
_proto.TryIndex<DocTemplatePrototype>(j.TemplateId, out var proto)
? Loc.GetString(proto.Name)
: j.TemplateId ?? "Документ";
return new PrinterJobView(title, j.Type);
}).ToList()
);
_userInterfaceSystem.SetUiState(uid, PrinterDocUiKey.Key, state);
}
public bool CanCopy(EntityUid uid, PrinterDocComponent comp)
{
var hasCopyPaper = comp.CopySlot.HasItem;
var hasBuckleUser = TryComp<StrapComponent>(uid, out var strap) && strap.BuckledEntities.Count != 0 &&
TryComp<HumanoidAppearanceComponent>(strap.BuckledEntities.First(), out _);
return hasBuckleUser || hasCopyPaper;
}
private void OnEmagged(EntityUid uid, PrinterDocComponent component, ref GotEmaggedEvent args)
{
if (!_emag.CompareFlag(args.Type, EmagType.Interaction))
return;
args.Handled = true;
component.Templates.Clear();
foreach (var template in _proto.EnumeratePrototypes<DocTemplatePrototype>())
{
if (!string.IsNullOrEmpty(template.Component))
component.Templates.Add(template.ID);
}
Dirty(uid, component);
UpdateUserInterface(uid, component);
}
private void OnPrinterStartup(EntityUid uid, PrinterDocComponent comp, ref ComponentStartup args)
{
TryInitPrinterResources(uid, comp);
}
private void TryInitPrinterResources(EntityUid uid, PrinterDocComponent comp)
{
if (Deleted(uid) || comp.Initialized)
return;
if (!_solution.TryGetSolution(uid, comp.Solution, out var solEnt, out var solution))
{
Timer.Spawn(TimeSpan.Zero, () => TryInitPrinterResources(uid, comp));
return;
}
_materialStorage.TryChangeMaterialAmount(uid, comp.PaperMaterial, comp.InitialPaperAmount);
var reagent = new ReagentId(comp.IncReagentProto, null);
solution.AddReagent(reagent, comp.InitialInkAmount);
_solution.UpdateChemicals(solEnt.Value, needsReactionsProcessing: false);
comp.Initialized = true;
UpdateUserInterface(uid, comp);
}
// Система для переключения используемых шаблонов документов через CVar
private string GetTemplateRoot()
{
string pack;
try { pack = _configManager.GetCVar(SunriseCCVars.PrinterDocTemplatePack); }
catch { pack = "sunrise"; }
if (string.IsNullOrWhiteSpace(pack))
pack = "sunrise";
pack = pack.Trim();
return pack.Equals("lust", StringComparison.OrdinalIgnoreCase) ? LustRoot : SunriseRoot;
}
private ResPath? ResolveTemplatePath(ResPath contentPath)
{
var root = GetTemplateRoot();
var content = contentPath.ToString();
if (!content.StartsWith(root, StringComparison.OrdinalIgnoreCase))
return null;
var rp = new ResPath(content);
return _resourceManager.ContentFileExists(rp) ? rp : (ResPath?)null;
}
}