Fix flatpacker exploit ignoring board costs (#42445)

Fix flatpacks ignoring costs and board requirements
This commit is contained in:
Nemanja
2026-01-15 19:22:24 -05:00
committed by GitHub
parent 57ac7bbe4f
commit d2ac15c76f
5 changed files with 70 additions and 71 deletions

View File

@@ -1,13 +1,11 @@
using System.Linq;
using Content.Client.Materials;
using Content.Client.Materials.UI;
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Materials;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -61,57 +59,48 @@ public sealed partial class FlatpackCreatorMenu : FancyWindow
!_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
return;
var flatpackerEnt = (_owner, flatpacker);
if (flatpacker.Packing)
{
PackButton.Disabled = true;
}
else if (_currentBoard != null)
{
Dictionary<string, int> cost;
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var machineBoardComp))
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, machineBoardComp));
else
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
PackButton.Disabled = !_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var curCost)
|| !_materialStorage.CanChangeMaterialAmount(_owner, curCost);
}
if (_currentBoard == itemSlot.Item)
return;
_currentBoard = itemSlot.Item;
CostHeaderLabel.Visible = _currentBoard != null;
CostHeaderLabel.Visible = false;
InsertLabel.Visible = _currentBoard == null;
if (_currentBoard is not null)
if (_currentBoard is null)
{
string? prototype = null;
Dictionary<string, int>? cost = null;
MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(string.Empty);
PackButton.Disabled = true;
return;
}
if (_entityManager.TryGetComponent<MachineBoardComponent>(_currentBoard, out var newMachineBoardComp))
{
prototype = newMachineBoardComp.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), (_currentBoard.Value, newMachineBoardComp));
}
else if (_entityManager.TryGetComponent<ComputerBoardComponent>(_currentBoard, out var computerBoard))
{
prototype = computerBoard.Prototype;
cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker), null);
}
if (prototype is not null && cost is not null)
{
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
MachineSprite.SetPrototype(prototype);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
}
if (_flatpack.TryGetFlatpackResultPrototype(_currentBoard.Value, out var prototype) &&
_flatpack.TryGetFlatpackCreationCost(flatpackerEnt, _currentBoard.Value, out var cost))
{
var proto = _prototypeManager.Index<EntityPrototype>(prototype);
MachineSprite.SetPrototype(prototype);
MachineNameLabel.SetMessage(proto.Name);
CostLabel.SetMarkup(GetCostString(cost));
CostHeaderLabel.Visible = true;
}
else
{
MachineSprite.SetPrototype(NoBoardEffectId);
CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
MachineNameLabel.SetMessage(" ");
CostLabel.SetMarkup(Loc.GetString("flatpacker-ui-board-invalid-label"));
MachineNameLabel.SetMessage(string.Empty);
PackButton.Disabled = true;
}
}

View File

@@ -1,11 +1,9 @@
using Content.Server.Audio;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Construction;
using Content.Shared.Construction.Components;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Power;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server.Construction;
@@ -35,16 +33,8 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
return;
Dictionary<string, int> cost;
if (TryComp<MachineBoardComponent>(board, out var machine))
cost = GetFlatpackCreationCost(ent, (board, machine));
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
cost = GetFlatpackCreationCost(ent, null);
else
{
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
if (!TryGetFlatpackCreationCost(ent, board, out var cost))
return;
}
if (!MaterialStorage.CanChangeMaterialAmount(uid, cost))
return;
@@ -80,29 +70,15 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } board)
return;
Dictionary<string, int> cost;
EntProtoId proto;
if (TryComp<MachineBoardComponent>(board, out var machine))
{
cost = GetFlatpackCreationCost(ent, (board, machine));
proto = machine.Prototype;
}
else if (TryComp<ComputerBoardComponent>(board, out var computer) && computer.Prototype != null)
{
cost = GetFlatpackCreationCost(ent, null);
proto = computer.Prototype;
}
else
{
Log.Error($"Encountered invalid flatpack board while packing: {ToPrettyString(board)}");
if (!TryGetFlatpackCreationCost(ent, board, out var cost) ||
!TryGetFlatpackResultPrototype(board, out var proto))
return;
}
if (!MaterialStorage.TryChangeMaterialAmount((ent, null), cost))
return;
var flatpack = Spawn(comp.BaseFlatpackPrototype, Transform(ent).Coordinates);
SetupFlatpack(flatpack, proto, board);
SetupFlatpack(flatpack, proto.Value, board);
Del(board);
}

View File

@@ -58,11 +58,11 @@ namespace Content.Shared.Construction
}
}
public Dictionary<string, int> GetMachineBoardMaterialCost(Entity<MachineBoardComponent> entity, int coefficient = 1)
public bool TryGetMachineBoardMaterialCost(Entity<MachineBoardComponent> entity, out Dictionary<string, int> materials, int coefficient = 1)
{
var (_, comp) = entity;
var materials = new Dictionary<string, int>();
materials = new Dictionary<string, int>();
foreach (var (stackId, amount) in comp.StackRequirements)
{
@@ -89,9 +89,14 @@ namespace Content.Shared.Construction
materials[mat] += matAmount * amount * coefficient;
}
}
else
{
// The item has no material cost, so we cannot get the full cost.
return false;
}
}
var genericPartInfo = comp.ComponentRequirements.Values.Concat(comp.ComponentRequirements.Values);
var genericPartInfo = comp.ComponentRequirements.Values.Concat(comp.TagRequirements.Values);
foreach (var info in genericPartInfo)
{
var amount = info.Amount;
@@ -118,9 +123,15 @@ namespace Content.Shared.Construction
materials[mat] += matAmount * amount * coefficient;
}
}
else
{
// The item has no material cost, so we cannot get the full cost.
return false;
}
}
return materials;
// We were able to construct all elements of the recipe.
return true;
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Construction.Components;
using Content.Shared.Administration.Logs;
using Content.Shared.Containers.ItemSlots;
@@ -125,14 +126,34 @@ public abstract class SharedFlatpackSystem : EntitySystem
Appearance.SetData(ent, FlatpackVisuals.Machine, MetaData(board).EntityPrototype?.ID ?? string.Empty);
}
/// <param name="machineBoard">The machine board to pack. If null, this implies we are packing a computer board</param>
public Dictionary<string, int> GetFlatpackCreationCost(Entity<FlatpackCreatorComponent> entity, Entity<MachineBoardComponent>? machineBoard)
/// <summary>
/// Returns the prototype from a board that the flatpacker will create.
/// </summary>
public bool TryGetFlatpackResultPrototype(EntityUid board, [NotNullWhen(true)] out EntProtoId? prototype)
{
Dictionary<string, int> cost = new();
prototype = null;
if (TryComp<MachineBoardComponent>(board, out var machine))
prototype = machine.Prototype;
else if (TryComp<ComputerBoardComponent>(board, out var computer))
prototype = computer.Prototype;
return prototype is not null;
}
/// <summary>
/// Tries to get the cost to produce an item, fails if unable to produce it.
/// </summary>
/// <param name="entity">The flatpacking machine</param>
/// <param name="machineBoard">The machine board to pack. If null, this implies we are packing a computer board</param>
/// <param name="cost">Cost to produce</param>
public bool TryGetFlatpackCreationCost(Entity<FlatpackCreatorComponent> entity, EntityUid machineBoard, out Dictionary<string, int> cost)
{
cost = new();
Dictionary<ProtoId<MaterialPrototype>, int> baseCost;
if (machineBoard is not null)
if (TryComp<MachineBoardComponent>(machineBoard, out var machineBoardComp))
{
cost = MachinePart.GetMachineBoardMaterialCost(machineBoard.Value, -1);
if (!MachinePart.TryGetMachineBoardMaterialCost((machineBoard, machineBoardComp), out cost, -1))
return false;
baseCost = entity.Comp.BaseMachineCost;
}
else
@@ -144,6 +165,6 @@ public abstract class SharedFlatpackSystem : EntitySystem
cost[mat] -= amount;
}
return cost;
return true;
}
}

View File

@@ -8,5 +8,7 @@ flatpacker-ui-title = Flatpacker 1001
flatpacker-ui-materials-label = Materials
flatpacker-ui-cost-label = Packing Cost
flatpacker-ui-no-board-label = No board present!
flatpacker-ui-board-invalid-label = [color=red]Invalid board!
Unable to print![/color]
flatpacker-ui-insert-board = Insert a board to begin.
flatpacker-ui-pack-button = Pack