6
2026-01-24 12:49:55 +03:00

347 lines
15 KiB
C#

using Content.Server._NF.Construction.Components;
using Content.Server.Construction;
using Content.Server.Construction.Components;
using Content.Server.Stack;
using Content.Server.Storage.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Construction.Components;
using Content.Shared.Exchanger;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Storage;
using Robust.Shared.Containers;
using Robust.Shared.Utility;
using Content.Shared.Wires;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Prototypes;
using Content.Shared.Stacks;
using Content.Shared.Construction.Prototypes;
namespace Content.Server._NF.Construction;
public sealed class PartExchangerSystem : EntitySystem
{
[Dependency] private readonly ConstructionSystem _construction = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly StorageSystem _storage = default!;
[Dependency] private readonly StackSystem _stack = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<PartExchangerComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<PartExchangerComponent, ExchangerDoAfterEvent>(OnDoAfter);
}
private struct UpgradePartState
{
public MachinePartComponent Part;
public StackComponent? Stack;
public bool InContainer;
}
private void OnDoAfter(EntityUid uid, PartExchangerComponent component, DoAfterEvent args)
{
if (args.Cancelled)
{
component.AudioStream = _audio.Stop(component.AudioStream);
return;
}
if (args.Handled || args.Args.Target == null)
return;
if (!TryComp<StorageComponent>(uid, out var storage) || storage.Container == null)
return;
var partsByType = new Dictionary<ProtoId<MachinePartPrototype>, List<(EntityUid, UpgradePartState)>>();
// Insert the contained parts into a dictionary for indexing.
// Note: these parts remain in the starting container.
foreach (var item in storage.Container.ContainedEntities)
{
if (_construction.GetMachinePartState(item, out var partState))
{
UpgradePartState upgrade;
upgrade.Part = partState.Part;
upgrade.Stack = partState.Stack;
upgrade.InContainer = true;
var partType = upgrade.Part.PartType;
if (!partsByType.ContainsKey(partType))
partsByType[partType] = new List<(EntityUid, UpgradePartState)>();
partsByType[partType].Add((item, upgrade));
}
}
// Exchange machine parts with the machine or frame.
if (TryComp<MachineComponent>(args.Args.Target.Value, out var machine))
TryExchangeMachineParts(machine, args.Args.Target.Value, uid, partsByType);
else if (TryComp<MachineFrameComponent>(args.Args.Target.Value, out var machineFrame))
TryConstructMachineParts(machineFrame, args.Args.Target.Value, uid, partsByType);
args.Handled = true;
}
private void TryExchangeMachineParts(MachineComponent machine, EntityUid uid, EntityUid storageUid, Dictionary<ProtoId<MachinePartPrototype>, List<(EntityUid part, UpgradePartState state)>> partsByType)
{
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
if (board == null || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
return;
// Add all components in the machine to form a complete set of available components.
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
{
if (_construction.GetMachinePartState(item, out var partState))
{
UpgradePartState upgrade;
upgrade.Part = partState.Part;
upgrade.Stack = partState.Stack;
upgrade.InContainer = false;
var partType = upgrade.Part.PartType;
if (!partsByType.ContainsKey(partType))
partsByType[partType] = new List<(EntityUid, UpgradePartState)>();
partsByType[partType].Add((item, upgrade));
_container.RemoveEntity(uid, item);
}
}
// Sort by rating in descending order (highest rated parts first)
foreach (var (partKey, partList) in partsByType)
partList.Sort((x, y) => y.state.Part.Rating.CompareTo(x.state.Part.Rating));
var updatedParts = new List<(EntityUid id, MachinePartState state, int index)>();
foreach (var (type, amount) in macBoardComp.Requirements)
{
if (partsByType.ContainsKey(type))
{
var partsNeeded = amount;
int index = 0;
foreach ((var part, var state) in partsByType[type])
{
// No more space for components
if (partsNeeded <= 0)
break;
if (state.Stack is not null)
{
var count = state.Stack.Count;
// Entire stack is needed, add it to the things to bring over.
if (count <= partsNeeded)
{
MachinePartState partState;
partState.Part = state.Part;
partState.Stack = state.Stack;
updatedParts.Add((part, partState, index));
partsNeeded -= count;
}
else
{
// Partial stack is needed, split off what we need, ensure the new entry is moved.
EntityUid splitStack = _stack.Split(part, partsNeeded, Transform(uid).Coordinates, state.Stack) ?? EntityUid.Invalid;
if (splitStack == EntityUid.Invalid)
continue;
// Create a new MachinePartState out of our new entity
if (_construction.GetMachinePartState(splitStack, out var splitState))
{
updatedParts.Add((splitStack, splitState, -1)); // Use -1 for index, nothing to remove
partsNeeded = 0;
}
}
}
else
{
// Not a stack, move the single part.
MachinePartState partState;
partState.Part = state.Part;
partState.Stack = state.Stack;
updatedParts.Add((part, partState, index));
partsNeeded--;
}
// Adjust the index for parts being removed from the container.
index++;
}
}
}
// Move selected parts to the machine, removing them from the dictionary of contained parts.
// Iterate through list backwards, remove later entries first (maintain validity of earlier indices).
for (int i = updatedParts.Count - 1; i >= 0; i--)
{
var part = updatedParts[i];
bool inserted = _container.Insert(part.id, machine.PartContainer);
if (part.index >= 0)
partsByType[part.state.Part.PartType].RemoveAt(part.index);
}
//Put the unused parts back into the container (if they aren't already there)
foreach (var (partType, partSet) in partsByType)
{
foreach (var partState in partSet)
{
if (!partState.state.InContainer)
_storage.Insert(storageUid, partState.part, out _, playSound: false);
}
}
_construction.RefreshParts(uid, machine);
}
private void TryConstructMachineParts(MachineFrameComponent machine, EntityUid uid, EntityUid storageEnt, Dictionary<ProtoId<MachinePartPrototype>, List<(EntityUid part, UpgradePartState state)>> partsByType)
{
var board = machine.BoardContainer.ContainedEntities.FirstOrNull();
if (!machine.HasBoard || !TryComp<MachineBoardComponent>(board, out var macBoardComp))
return;
// Add all components in the machine to form a complete set of available components.
foreach (var item in new ValueList<EntityUid>(machine.PartContainer.ContainedEntities)) //clone so don't modify during enumeration
{
if (_construction.GetMachinePartState(item, out var partState))
{
// Construct our entry
UpgradePartState upgrade;
upgrade.Part = partState.Part;
upgrade.Stack = partState.Stack;
upgrade.InContainer = false;
// Add it to the table
var partType = upgrade.Part.PartType;
if (!partsByType.ContainsKey(partType))
partsByType[partType] = new List<(EntityUid, UpgradePartState)>();
partsByType[partType].Add((item, upgrade));
// Make sure the construction status is consistent with the removed parts.
machine.Progress[partType] -= partState.Quantity();
machine.Progress[partType] = int.Max(0, machine.Progress[partType]); // Ensure progress isn't negative.
_container.RemoveEntity(uid, item);
}
}
// Sort parts in descending order of rating (highest rated parts first)
foreach (var partList in partsByType.Values)
partList.Sort((x, y) => y.state.Part.Rating.CompareTo(x.state.Part.Rating));
var updatedParts = new List<(EntityUid id, MachinePartState state, int index)>();
foreach (var (type, amount) in macBoardComp.Requirements)
{
if (partsByType.ContainsKey(type))
{
var partsNeeded = amount;
var index = 0;
foreach ((var part, var state) in partsByType[type])
{
// No more space for components
if (partsNeeded <= 0)
break;
if (state.Stack is not null)
{
var count = state.Stack.Count;
// Entire stack is needed, add it to the things to bring over.
if (count <= partsNeeded)
{
MachinePartState partState;
partState.Part = state.Part;
partState.Stack = state.Stack;
updatedParts.Add((part, partState, index));
partsNeeded -= count;
}
else
{
// Partial stack is needed, split off what we need, ensure the new entry is moved.
EntityUid splitStack = _stack.Split(part, partsNeeded, Transform(uid).Coordinates, state.Stack) ?? EntityUid.Invalid;
if (splitStack == EntityUid.Invalid)
continue;
// Create a new MachinePartState out of our new entity
if (_construction.GetMachinePartState(splitStack, out var splitState))
{
updatedParts.Add((splitStack, splitState, -1)); // New entity, nothing to remove, set index to -1 to flag this.
partsNeeded = 0;
}
}
}
else
{
// Not a stack, move the single part.
MachinePartState partState;
partState.Part = state.Part;
partState.Stack = state.Stack;
updatedParts.Add((part, partState, index));
partsNeeded--;
}
// Adjust the index for parts being removed from the container.
index++;
}
}
}
// Move selected parts to the machine, removing them from the dictionary of contained parts.
// Iterate through list backwards, remove later entries first (maintain validity of earlier indices).
for (int i = updatedParts.Count - 1; i >= 0; i--)
{
var part = updatedParts[i];
_container.Insert(part.id, machine.PartContainer, force: true);
if (part.index >= 0)
partsByType[part.state.Part.PartType].RemoveAt(part.index);
machine.Progress[part.state.Part.PartType] += part.state.Quantity();
}
//Put the unused parts back into the container (if they aren't already there)
foreach (var (partType, partSet) in partsByType)
{
foreach (var partState in partSet)
{
if (!partState.state.InContainer)
_storage.Insert(storageEnt, partState.part, out _, playSound: false);
}
}
}
private void OnAfterInteract(EntityUid uid, PartExchangerComponent component, AfterInteractEvent args)
{
if (component.DoDistanceCheck && !args.CanReach)
return;
if (args.Target == null)
return;
if (!HasComp<MachineComponent>(args.Target) && !HasComp<MachineFrameComponent>(args.Target))
return;
if (TryComp<WiresPanelComponent>(args.Target, out var panel) && !panel.Open)
{
_popup.PopupEntity(Loc.GetString("construction-step-condition-wire-panel-open"),
args.Target.Value);
return;
}
var audioStream = _audio.PlayPvs(component.ExchangeSound, uid);
if (audioStream != null)
{
component.AudioStream = audioStream.Value.Entity;
}
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.ExchangeDuration, new ExchangerDoAfterEvent(), uid, target: args.Target, used: uid)
{
BreakOnDamage = true,
BreakOnMove = true
});
}
}