using Content.Server.Construction.Components; using Content.Server.Stack; using Content.Shared.Construction.Components; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Stacks; using Content.Shared.Tag; using Content.Shared.Popups; using Robust.Shared.Containers; using Robust.Shared.Utility; namespace Content.Server.Construction; public sealed class MachineFrameSystem : EntitySystem { [Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly StackSystem _stack = default!; [Dependency] private readonly ConstructionSystem _construction = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnInteractUsing); SubscribeLocalEvent(OnMachineFrameExamined); } private void OnInit(EntityUid uid, MachineFrameComponent component, ComponentInit args) { component.BoardContainer = _container.EnsureContainer(uid, MachineFrameComponent.BoardContainerName); component.PartContainer = _container.EnsureContainer(uid, MachineFrameComponent.PartContainerName); } private void OnStartup(EntityUid uid, MachineFrameComponent component, ComponentStartup args) { RegenerateProgress(component); if (TryComp(uid, out var construction) && construction.TargetNode == null) { // Attempt to set pathfinding to the machine node... _construction.SetPathfindingTarget(uid, "machine", construction); } } private void OnInteractUsing(EntityUid uid, MachineFrameComponent component, InteractUsingEvent args) { if (args.Handled) return; if (!component.HasBoard) { if (TryInsertBoard(uid, args.Used, component)) args.Handled = true; return; } // Machine parts cannot currently satisfy stack/component/tag restrictions. Similarly stacks cannot satisfy // component/tag restrictions. However, there is no reason this cannot be supported in the future. If this // changes, then RegenerateProgress() also needs to be updated. // // Note that one entity is ALLOWED to satisfy more than one kind of component or tag requirements. This is // necessary in order to avoid weird entity-ordering shenanigans in RegenerateProgress(). // Handle parts if (TryComp(args.Used, out var machinePart)) { if (TryInsertPart(uid, args.Used, component, machinePart)) args.Handled = true; return; } // Handle stacks if (TryComp(args.Used, out var stack)) { if (TryInsertStack(uid, args.Used, component, stack)) args.Handled = true; return; } // Handle component requirements foreach (var (compName, info) in component.ComponentRequirements) { if (component.ComponentProgress[compName] >= info.Amount) continue; var registration = _factory.GetRegistration(compName); if (!HasComp(args.Used, registration.Type)) continue; // Insert the entity, if it hasn't already been inserted if (!args.Handled) { if (!_container.TryRemoveFromContainer(args.Used)) return; args.Handled = true; if (!component.PartContainer.Insert(args.Used)) return; } component.ComponentProgress[compName]++; if (IsComplete(component)) { _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); return; } } // Handle tag requirements if (!TryComp(args.Used, out var tagComp)) return; foreach (var (tagName, info) in component.TagRequirements) { if (component.TagProgress[tagName] >= info.Amount) continue; if (!_tag.HasTag(tagComp, tagName)) continue; // Insert the entity, if it hasn't already been inserted if (!args.Handled) { if (!_container.TryRemoveFromContainer(args.Used)) return; args.Handled = true; if (!component.PartContainer.Insert(args.Used)) return; } component.TagProgress[tagName]++; args.Handled = true; if (IsComplete(component)) { _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); return; } } } /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertBoard(EntityUid uid, EntityUid used, MachineFrameComponent component) { if (!TryComp(used, out var machineBoard)) return false; if (!_container.TryRemoveFromContainer(used)) return false; if (!component.BoardContainer.Insert(used)) return true; ResetProgressAndRequirements(component, machineBoard); // Reset edge so that prying the components off works correctly. if (TryComp(uid, out ConstructionComponent? construction)) _construction.ResetEdge(uid, construction); return true; } /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertPart(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart) { DebugTools.Assert(!HasComp(uid)); if (!component.Requirements.ContainsKey(machinePart.PartType)) return false; if (component.Progress[machinePart.PartType] >= component.Requirements[machinePart.PartType]) return false; if (!_container.TryRemoveFromContainer(used)) return false; if (!component.PartContainer.Insert(used)) return true; component.Progress[machinePart.PartType]++; if (IsComplete(component)) _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); return true; } /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertStack(EntityUid uid, EntityUid used, MachineFrameComponent component, StackComponent stack) { var type = stack.StackTypeId; if (!component.MaterialRequirements.ContainsKey(type)) return false; var progress = component.MaterialProgress[type]; var requirement = component.MaterialRequirements[type]; var needed = requirement - progress; if (needed <= 0) return false; var count = stack.Count; if (count < needed) { if (!_container.TryRemoveFromContainer(used)) return false; if (!component.PartContainer.Insert(used)) return true; component.MaterialProgress[type] += count; return true; } var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack); if (splitStack == null) return false; if (!component.PartContainer.Insert(splitStack.Value)) return true; component.MaterialProgress[type] += needed; if (IsComplete(component)) _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); return true; } public bool IsComplete(MachineFrameComponent component) { if (!component.HasBoard) return false; foreach (var (part, amount) in component.Requirements) { if (component.Progress[part] < amount) return false; } foreach (var (type, amount) in component.MaterialRequirements) { if (component.MaterialProgress[type] < amount) return false; } foreach (var (compName, info) in component.ComponentRequirements) { if (component.ComponentProgress[compName] < info.Amount) return false; } foreach (var (tagName, info) in component.TagRequirements) { if (component.TagProgress[tagName] < info.Amount) return false; } return true; } public void ResetProgressAndRequirements(MachineFrameComponent component, MachineBoardComponent machineBoard) { component.Requirements = new Dictionary(machineBoard.Requirements); component.MaterialRequirements = new Dictionary(machineBoard.MaterialIdRequirements); component.ComponentRequirements = new Dictionary(machineBoard.ComponentRequirements); component.TagRequirements = new Dictionary(machineBoard.TagRequirements); component.Progress.Clear(); component.MaterialProgress.Clear(); component.ComponentProgress.Clear(); component.TagProgress.Clear(); foreach (var (machinePart, _) in component.Requirements) { component.Progress[machinePart] = 0; } foreach (var (stackType, _) in component.MaterialRequirements) { component.MaterialProgress[stackType] = 0; } foreach (var (compName, _) in component.ComponentRequirements) { component.ComponentProgress[compName] = 0; } foreach (var (compName, _) in component.TagRequirements) { component.TagProgress[compName] = 0; } } public void RegenerateProgress(MachineFrameComponent component) { if (!component.HasBoard) { component.TagRequirements.Clear(); component.MaterialRequirements.Clear(); component.ComponentRequirements.Clear(); component.TagRequirements.Clear(); component.Progress.Clear(); component.MaterialProgress.Clear(); component.ComponentProgress.Clear(); component.TagProgress.Clear(); return; } var board = component.BoardContainer.ContainedEntities[0]; if (!TryComp(board, out var machineBoard)) return; ResetProgressAndRequirements(component, machineBoard); // If the following code is updated, you need to make sure that it matches the logic in OnInteractUsing() foreach (var part in component.PartContainer.ContainedEntities) { if (TryComp(part, out var machinePart)) { DebugTools.Assert(!HasComp(part)); // Check this is part of the requirements... if (!component.Requirements.ContainsKey(machinePart.PartType)) continue; if (!component.Progress.ContainsKey(machinePart.PartType)) component.Progress[machinePart.PartType] = 1; else component.Progress[machinePart.PartType]++; continue; } if (TryComp(part, out var stack)) { var type = stack.StackTypeId; if (!component.MaterialRequirements.ContainsKey(type)) continue; if (!component.MaterialProgress.ContainsKey(type)) component.MaterialProgress[type] = stack.Count; else component.MaterialProgress[type] += stack.Count; continue; } // I have many regrets. foreach (var (compName, _) in component.ComponentRequirements) { var registration = _factory.GetRegistration(compName); if (!HasComp(part, registration.Type)) continue; if (!component.ComponentProgress.ContainsKey(compName)) component.ComponentProgress[compName] = 1; else component.ComponentProgress[compName]++; } if (!TryComp(part, out var tagComp)) continue; // I have MANY regrets. foreach (var tagName in component.TagRequirements.Keys) { if (!_tag.HasTag(tagComp, tagName)) continue; if (!component.TagProgress.ContainsKey(tagName)) component.TagProgress[tagName] = 1; else component.TagProgress[tagName]++; } } } private void OnMachineFrameExamined(EntityUid uid, MachineFrameComponent component, ExaminedEvent args) { if (!args.IsInDetailsRange) return; if (component.HasBoard) args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", EntityManager.GetComponent(component.BoardContainer.ContainedEntities[0]).EntityName))); } }