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.Prototypes; using Content.Shared.Construction.Prototypes; namespace Content.Server.Construction; public sealed class MachineFrameSystem : EntitySystem { [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; } // If this changes in the future, 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(). // Frontier: restore upgradeable parts // Handle parts if (TryComp(args.Used, out var machinePart)) { if (TryInsertPart(uid, args.Used, component, machinePart)) args.Handled = true; return; } // End Frontier 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 (!_container.Insert(args.Used, component.PartContainer)) 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 (!_container.Insert(args.Used, component.PartContainer)) 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 (!_container.Insert(used, component.BoardContainer)) 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; } // Frontier: restore upgradeable parts /// Whether or not the function had any effect. Does not indicate success. private bool TryInsertPart(EntityUid uid, EntityUid used, MachineFrameComponent component, MachinePartComponent machinePart) { if (!component.Requirements.ContainsKey(machinePart.PartType)) return false; if (component.Progress[machinePart.PartType] >= component.Requirements[machinePart.PartType]) return false; // Check for stack if (TryComp(used, out var stack)) { int needed = component.Requirements[machinePart.PartType] - component.Progress[machinePart.PartType]; var count = stack.Count; if (count < needed) { if (!_container.TryRemoveFromContainer(used)) return false; if (!_container.Insert(used, component.PartContainer)) return true; component.Progress[machinePart.PartType] += count; return true; } var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack); if (splitStack == null) return false; if (!_container.Insert(splitStack.Value, component.PartContainer)) return true; component.Progress[machinePart.PartType] += needed; } // No stack else { if (!_container.TryRemoveFromContainer(used)) return false; if (!_container.Insert(used, component.PartContainer)) return true; component.Progress[machinePart.PartType]++; } if (IsComplete(component)) _popupSystem.PopupEntity(Loc.GetString("machine-frame-component-on-complete"), uid); return true; } // Frontier /// 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 (!_container.Insert(used, component.PartContainer)) return true; component.MaterialProgress[type] += count; return true; } var splitStack = _stack.Split(used, needed, Transform(uid).Coordinates, stack); if (splitStack == null) return false; if (!_container.Insert(splitStack.Value, component.PartContainer)) 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; // Frontier: restore upgradeable parts foreach (var (type, amount) in component.Requirements) { if (component.Progress[type] < amount) return false; } // End Frontier 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, int>(machineBoard.Requirements); // Frontier: upgradeable machine parts component.MaterialRequirements = new Dictionary, int>(machineBoard.StackRequirements); component.ComponentRequirements = new Dictionary(machineBoard.ComponentRequirements); component.TagRequirements = new Dictionary, GenericPartInfo>(machineBoard.TagRequirements); component.Progress.Clear(); // Frontier: upgradeable machine parts component.MaterialProgress.Clear(); component.ComponentProgress.Clear(); component.TagProgress.Clear(); // Frontier: upgradeable machine parts foreach (var (partType, _) in component.Requirements) { component.Progress[partType] = 0; } // End Frontier 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.Requirements.Clear(); // Frontier component.TagRequirements.Clear(); component.MaterialRequirements.Clear(); component.ComponentRequirements.Clear(); component.TagRequirements.Clear(); component.Progress.Clear(); // Frontier 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) { // Frontier: upgradeable machine parts if (TryComp(part, out var machinePart)) { var type = machinePart.PartType; if (!component.Requirements.ContainsKey(type)) continue; int quantity = 1; if (TryComp(part, out var partStack)) quantity = partStack.Count; if (!component.Progress.ContainsKey(type)) component.Progress[type] = quantity; else component.Progress[type] += quantity; continue; } // End Frontier 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.TryAdd(compName, 1)) 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.TryAdd(tagName, 1)) component.TagProgress[tagName]++; } } } private void OnMachineFrameExamined(EntityUid uid, MachineFrameComponent component, ExaminedEvent args) { if (!args.IsInDetailsRange || !component.HasBoard) return; var board = component.BoardContainer.ContainedEntities[0]; args.PushMarkup(Loc.GetString("machine-frame-component-on-examine-label", ("board", Name(board)))); } }