diff --git a/Content.Client/Construction/FlatpackSystem.cs b/Content.Client/Construction/FlatpackSystem.cs
new file mode 100644
index 0000000000..911ff1279c
--- /dev/null
+++ b/Content.Client/Construction/FlatpackSystem.cs
@@ -0,0 +1,48 @@
+using Content.Shared.Construction;
+using Content.Shared.Construction.Components;
+using Robust.Client.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Construction;
+
+///
+public sealed class FlatpackSystem : SharedFlatpackSystem
+{
+ [Dependency] private readonly AppearanceSystem _appearance = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAppearanceChange);
+ }
+
+ private void OnAppearanceChange(Entity ent, ref AppearanceChangeEvent args)
+ {
+ var (_, comp) = ent;
+ if (!_appearance.TryGetData(ent, FlatpackVisuals.Machine, out var machineBoardId) || args.Sprite == null)
+ return;
+
+ if (!PrototypeManager.TryIndex(machineBoardId, out var machineBoardPrototype))
+ return;
+
+ if (!machineBoardPrototype.TryGetComponent(out var sprite))
+ return;
+
+ Color? color = null;
+ foreach (var layer in sprite.AllLayers)
+ {
+ if (layer.RsiState.Name is not { } spriteState)
+ continue;
+
+ if (!comp.BoardColors.TryGetValue(spriteState, out var c))
+ continue;
+ color = c;
+ break;
+ }
+
+ if (color != null)
+ args.Sprite.LayerSetColor(FlatpackVisualLayers.Overlay, color.Value);
+ }
+}
diff --git a/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs b/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
new file mode 100644
index 0000000000..86f1b8b83c
--- /dev/null
+++ b/Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
@@ -0,0 +1,40 @@
+using Content.Shared.Construction.Components;
+using JetBrains.Annotations;
+
+namespace Content.Client.Construction.UI
+{
+ [UsedImplicitly]
+ public sealed class FlatpackCreatorBoundUserInterface : BoundUserInterface
+ {
+ [ViewVariables]
+ private FlatpackCreatorMenu? _menu;
+
+ public FlatpackCreatorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = new FlatpackCreatorMenu(Owner);
+ _menu.OnClose += Close;
+
+ _menu.PackButtonPressed += () =>
+ {
+ SendMessage(new FlatpackCreatorStartPackBuiMessage());
+ };
+
+ _menu.OpenCentered();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ _menu?.Dispose();
+ }
+ }
+}
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
new file mode 100644
index 0000000000..5dffc5aa7f
--- /dev/null
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
new file mode 100644
index 0000000000..3041f6a504
--- /dev/null
+++ b/Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
@@ -0,0 +1,147 @@
+using System.Linq;
+using Content.Client.Materials;
+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;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Construction.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class FlatpackCreatorMenu : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private readonly ItemSlotsSystem _itemSlots;
+ private readonly FlatpackSystem _flatpack;
+ private readonly MaterialStorageSystem _materialStorage;
+ private readonly SpriteSystem _spriteSystem;
+
+ private readonly EntityUid _owner;
+
+ private EntityUid? _currentBoard = EntityUid.Invalid;
+ private EntityUid? _machinePreview;
+
+ public event Action? PackButtonPressed;
+
+ public FlatpackCreatorMenu(EntityUid uid)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ _itemSlots = _entityManager.System();
+ _flatpack = _entityManager.System();
+ _materialStorage = _entityManager.System();
+ _spriteSystem = _entityManager.System();
+
+ _owner = uid;
+
+ PackButton.OnPressed += _ => PackButtonPressed?.Invoke();
+
+ MaterialStorageControl.SetOwner(uid);
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (_machinePreview is not { } && _entityManager.Deleted(_machinePreview))
+ {
+ _machinePreview = null;
+ MachineSprite.SetEntity(_machinePreview);
+ }
+
+ if (!_entityManager.TryGetComponent(_owner, out var flatpacker) ||
+ !_itemSlots.TryGetSlot(_owner, flatpacker.SlotId, out var itemSlot))
+ return;
+
+ if (flatpacker.Packing)
+ {
+ PackButton.Disabled = true;
+ }
+ else if (_currentBoard != null)
+ {
+ //todo double trycomp is kinda stinky.
+ if (_entityManager.TryGetComponent(_currentBoard, out var board) &&
+ board.Prototype != null)
+ {
+ var cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker),
+ (_currentBoard.Value, board));
+ PackButton.Disabled = !_materialStorage.CanChangeMaterialAmount(_owner, cost);
+ }
+ }
+
+ if (_currentBoard == itemSlot.Item)
+ return;
+
+ if (_machinePreview != null)
+ _entityManager.DeleteEntity(_machinePreview);
+
+ _currentBoard = itemSlot.Item;
+ CostHeaderLabel.Visible = _currentBoard != null;
+
+ if (_currentBoard != null &&
+ _entityManager.TryGetComponent(_currentBoard, out var machineBoard) &&
+ machineBoard.Prototype != null)
+ {
+ var proto = _prototypeManager.Index(machineBoard.Prototype);
+ _machinePreview = _entityManager.Spawn(proto.ID);
+ _spriteSystem.ForceUpdate(_machinePreview.Value);
+ MachineNameLabel.SetMessage(proto.Name);
+ var cost = _flatpack.GetFlatpackCreationCost((_owner, flatpacker),
+ (_currentBoard.Value, machineBoard));
+ CostLabel.SetMarkup(GetCostString(cost));
+ }
+ else
+ {
+ _machinePreview = null;
+ MachineNameLabel.SetMessage(" ");
+ CostLabel.SetMessage(Loc.GetString("flatpacker-ui-no-board-label"));
+ PackButton.Disabled = true;
+ }
+
+ MachineSprite.SetEntity(_machinePreview);
+ }
+
+ //todo beautify
+ private string GetCostString(Dictionary costs)
+ {
+ var orderedCosts = costs.OrderBy(p => p.Value);
+ var msg = new FormattedMessage();
+ foreach (var (mat, amount) in orderedCosts)
+ {
+ var matProto = _prototypeManager.Index(mat);
+
+ var sheetVolume = _materialStorage.GetSheetVolume(matProto);
+ var sheets = (float) -amount / sheetVolume;
+ var amountText = Loc.GetString("lathe-menu-material-amount",
+ ("amount", sheets),
+ ("unit", Loc.GetString(matProto.Unit)));
+ var text = Loc.GetString("lathe-menu-tooltip-display",
+ ("amount", amountText),
+ ("material", Loc.GetString(matProto.Name)));
+
+ msg.AddMarkup(text);
+ msg.PushNewline();
+ }
+ msg.Pop();
+
+ return msg.ToMarkup();
+ }
+
+ public override void Close()
+ {
+ base.Close();
+
+ _entityManager.DeleteEntity(_machinePreview);
+ _machinePreview = null;
+ }
+}
diff --git a/Content.Client/Materials/UI/MaterialDisplay.xaml b/Content.Client/Materials/UI/MaterialDisplay.xaml
index 653a3f0463..097736ddd3 100644
--- a/Content.Client/Materials/UI/MaterialDisplay.xaml
+++ b/Content.Client/Materials/UI/MaterialDisplay.xaml
@@ -16,7 +16,6 @@
diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
index c95bd1957f..3ef247d529 100644
--- a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
+++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
@@ -37,9 +37,9 @@ public sealed partial class MaterialStorageControl : BoxContainer
if (_owner == null)
return;
- if (!_entityManager.TryGetComponent(_owner, out var materialStorage))
+ if (_entityManager.Deleted(_owner) || !_entityManager.TryGetComponent(_owner, out var materialStorage))
{
- Dispose();
+ _owner = null;
return;
}
diff --git a/Content.Server/Ame/Components/AmePartComponent.cs b/Content.Server/Ame/Components/AmePartComponent.cs
deleted file mode 100644
index 2d294747d7..0000000000
--- a/Content.Server/Ame/Components/AmePartComponent.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.Tools;
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Ame.Components;
-
-///
-/// Packaged AME machinery that can be deployed to construct an AME.
-///
-[RegisterComponent]
-public sealed partial class AmePartComponent : Component
-{
- ///
- /// The sound played when the AME shielding is unpacked.
- ///
- [DataField("unwrapSound")]
- public SoundSpecifier UnwrapSound = new SoundPathSpecifier("/Audio/Effects/unwrap.ogg");
-
- ///
- /// The tool quality required to deploy the packaged AME shielding.
- ///
- [DataField("qualityNeeded", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string QualityNeeded = "Pulsing";
-}
diff --git a/Content.Server/Ame/EntitySystems/AmePartSystem.cs b/Content.Server/Ame/EntitySystems/AmePartSystem.cs
deleted file mode 100644
index a75c092e2e..0000000000
--- a/Content.Server/Ame/EntitySystems/AmePartSystem.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System.Linq;
-using Content.Server.Administration.Logs;
-using Content.Server.Ame.Components;
-using Content.Server.Popups;
-using Content.Server.Tools;
-using Content.Shared.Database;
-using Content.Shared.Hands.Components;
-using Content.Shared.Interaction;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Map;
-
-namespace Content.Server.Ame.EntitySystems;
-
-public sealed class AmePartSystem : EntitySystem
-{
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly ToolSystem _toolSystem = default!;
- [Dependency] private readonly IAdminLogManager _adminLogger = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnPartInteractUsing);
- }
-
- private void OnPartInteractUsing(EntityUid uid, AmePartComponent component, InteractUsingEvent args)
- {
- if (!_toolSystem.HasQuality(args.Used, component.QualityNeeded))
- return;
-
- if (!_mapManager.TryGetGrid(args.ClickLocation.GetGridUid(EntityManager), out var mapGrid))
- return; // No AME in space.
-
- var snapPos = mapGrid.TileIndicesFor(args.ClickLocation);
- if (mapGrid.GetAnchoredEntities(snapPos).Any(sc => HasComp(sc)))
- {
- _popupSystem.PopupEntity(Loc.GetString("ame-part-component-shielding-already-present"), uid, args.User);
- return;
- }
-
- var ent = Spawn("AmeShielding", mapGrid.GridTileToLocal(snapPos));
-
- _adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(args.User):player} unpacked {ToPrettyString(ent)} at {Transform(ent).Coordinates} from {ToPrettyString(uid)}");
-
- _audioSystem.PlayPvs(component.UnwrapSound, uid);
-
- QueueDel(uid);
- }
-}
diff --git a/Content.Server/Construction/FlatpackSystem.cs b/Content.Server/Construction/FlatpackSystem.cs
new file mode 100644
index 0000000000..b5f9228158
--- /dev/null
+++ b/Content.Server/Construction/FlatpackSystem.cs
@@ -0,0 +1,98 @@
+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 Robust.Shared.Timing;
+
+namespace Content.Server.Construction;
+
+///
+public sealed class FlatpackSystem : SharedFlatpackSystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
+ [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStartPack);
+ SubscribeLocalEvent(OnPowerChanged);
+ }
+
+ private void OnStartPack(Entity ent, ref FlatpackCreatorStartPackBuiMessage args)
+ {
+ var (uid, comp) = ent;
+ if (!this.IsPowered(ent, EntityManager) || comp.Packing)
+ return;
+
+ if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
+ return;
+
+ if (!TryComp(machineBoard, out var boardComp))
+ return;
+
+ if (!MaterialStorage.CanChangeMaterialAmount(uid, GetFlatpackCreationCost(ent, (machineBoard, boardComp))))
+ return;
+
+ comp.Packing = true;
+ comp.PackEndTime = _timing.CurTime + comp.PackDuration;
+ Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, true);
+ _ambientSound.SetAmbience(uid, true);
+ Dirty(uid, comp);
+ }
+
+ private void OnPowerChanged(Entity ent, ref PowerChangedEvent args)
+ {
+ if (args.Powered)
+ return;
+ FinishPacking(ent, true);
+ }
+
+ private void FinishPacking(Entity ent, bool interrupted)
+ {
+ var (uid, comp) = ent;
+
+ comp.Packing = false;
+ Appearance.SetData(uid, FlatpackCreatorVisuals.Packing, false);
+ _ambientSound.SetAmbience(uid, false);
+ Dirty(uid, comp);
+
+ if (interrupted)
+ return;
+
+ if (!_itemSlots.TryGetSlot(uid, comp.SlotId, out var itemSlot) || itemSlot.Item is not { } machineBoard)
+ return;
+
+ if (!TryComp(machineBoard, out var boardComp))
+ return;
+
+ var materialCost = GetFlatpackCreationCost(ent, (machineBoard, boardComp));
+ if (!MaterialStorage.TryChangeMaterialAmount((ent, null), materialCost))
+ return;
+
+ var flatpack = Spawn(comp.BaseFlatpackPrototype, Transform(ent).Coordinates);
+ SetupFlatpack(flatpack, (machineBoard, boardComp));
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (!comp.Packing)
+ continue;
+
+ if (_timing.CurTime < comp.PackEndTime)
+ continue;
+
+ FinishPacking((uid, comp), false);
+ }
+ }
+}
diff --git a/Content.Shared/Construction/Components/FlatpackComponent.cs b/Content.Shared/Construction/Components/FlatpackComponent.cs
new file mode 100644
index 0000000000..5cb178075b
--- /dev/null
+++ b/Content.Shared/Construction/Components/FlatpackComponent.cs
@@ -0,0 +1,51 @@
+using Content.Shared.Tools;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Construction.Components;
+
+///
+/// This is used for an object that can instantly create a machine upon having a tool applied to it.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedFlatpackSystem))]
+public sealed partial class FlatpackComponent : Component
+{
+ ///
+ /// The tool quality that, upon used to interact with this object, will create the
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public ProtoId QualityNeeded = "Pulsing";
+
+ ///
+ /// The entity that is spawned when this object is unpacked.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public EntProtoId? Entity;
+
+ ///
+ /// Sound effect played upon the object being unpacked.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
+ public SoundSpecifier UnpackSound = new SoundPathSpecifier("/Audio/Effects/unwrap.ogg");
+
+ ///
+ /// A dictionary relating a machine board sprite state to a color used for the overlay.
+ /// Kinda shitty but it gets the job done.
+ ///
+ [DataField]
+ public Dictionary BoardColors = new();
+}
+
+[Serializable, NetSerializable]
+public enum FlatpackVisuals : byte
+{
+ Machine
+}
+
+public enum FlatpackVisualLayers : byte
+{
+ Overlay
+}
diff --git a/Content.Shared/Construction/Components/FlatpackCreatorComponent.cs b/Content.Shared/Construction/Components/FlatpackCreatorComponent.cs
new file mode 100644
index 0000000000..0f52d63628
--- /dev/null
+++ b/Content.Shared/Construction/Components/FlatpackCreatorComponent.cs
@@ -0,0 +1,69 @@
+using Content.Shared.Materials;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Construction.Components;
+
+///
+/// This is used for a machine that creates flatpacks at the cost of materials
+///
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(SharedFlatpackSystem))]
+[AutoGenerateComponentState]
+public sealed partial class FlatpackCreatorComponent : Component
+{
+ ///
+ /// Whether or not packing is occuring
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public bool Packing;
+
+ ///
+ /// The time at which packing ends
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public TimeSpan PackEndTime;
+
+ ///
+ /// How long packing lasts.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan PackDuration = TimeSpan.FromSeconds(3);
+
+ ///
+ /// The prototype used when spawning a flatpack.
+ ///
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntProtoId BaseFlatpackPrototype = "BaseFlatpack";
+
+ ///
+ /// A default cost applied to all flatpacks outside of the cost of constructing the machine.
+ ///
+ [DataField]
+ public Dictionary, int> BaseMaterialCost = new();
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string SlotId = "board_slot";
+}
+
+[Serializable, NetSerializable]
+public enum FlatpackCreatorUIKey : byte
+{
+ Key
+}
+
+[Serializable, NetSerializable]
+public enum FlatpackCreatorVisuals : byte
+{
+ Packing
+}
+
+[Serializable, NetSerializable]
+public sealed class FlatpackCreatorStartPackBuiMessage : BoundUserInterfaceMessage
+{
+
+}
diff --git a/Content.Shared/Construction/MachinePartSystem.cs b/Content.Shared/Construction/MachinePartSystem.cs
index d84557b2de..b13dc20c6d 100644
--- a/Content.Shared/Construction/MachinePartSystem.cs
+++ b/Content.Shared/Construction/MachinePartSystem.cs
@@ -1,6 +1,10 @@
+using System.Linq;
using Content.Shared.Construction.Components;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Examine;
+using Content.Shared.Lathe;
+using Content.Shared.Materials;
+using Content.Shared.Stacks;
using Robust.Shared.Prototypes;
namespace Content.Shared.Construction
@@ -11,6 +15,7 @@ namespace Content.Shared.Construction
public sealed class MachinePartSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly SharedLatheSystem _lathe = default!;
public override void Initialize()
{
@@ -61,5 +66,87 @@ namespace Content.Shared.Construction
args.PushMarkup(Loc.GetString("machine-part-component-on-examine-type-text", ("type",
Loc.GetString(_prototype.Index(component.PartType).Name))));
}
+
+ public Dictionary GetMachineBoardMaterialCost(Entity entity, int coefficient = 1)
+ {
+ var (_, comp) = entity;
+
+ var materials = new Dictionary();
+ foreach (var (partId, amount) in comp.Requirements)
+ {
+ var partProto = _prototype.Index(partId);
+
+ if (!_lathe.TryGetRecipesFromEntity(partProto.StockPartPrototype, out var recipes))
+ continue;
+
+ var partRecipe = recipes[0];
+ if (recipes.Count > 1)
+ partRecipe = recipes.MinBy(p => p.RequiredMaterials.Values.Sum());
+
+ foreach (var (mat, matAmount) in partRecipe!.RequiredMaterials)
+ {
+ materials.TryAdd(mat, 0);
+ materials[mat] += matAmount * amount * coefficient;
+ }
+ }
+
+ foreach (var (stackId, amount) in comp.MaterialIdRequirements)
+ {
+ var stackProto = _prototype.Index(stackId);
+
+ if (_prototype.TryIndex(stackProto.Spawn, out var defaultProto) &&
+ defaultProto.TryGetComponent(out var physComp))
+ {
+ foreach (var (mat, matAmount) in physComp.MaterialComposition)
+ {
+ materials.TryAdd(mat, 0);
+ materials[mat] += matAmount * amount * coefficient;
+ }
+ }
+ else if (_lathe.TryGetRecipesFromEntity(stackProto.Spawn, out var recipes))
+ {
+ var partRecipe = recipes[0];
+ if (recipes.Count > 1)
+ partRecipe = recipes.MinBy(p => p.RequiredMaterials.Values.Sum());
+
+ foreach (var (mat, matAmount) in partRecipe!.RequiredMaterials)
+ {
+ materials.TryAdd(mat, 0);
+ materials[mat] += matAmount * amount * coefficient;
+ }
+ }
+ }
+
+ var genericPartInfo = comp.ComponentRequirements.Values.Concat(comp.ComponentRequirements.Values);
+ foreach (var info in genericPartInfo)
+ {
+ var amount = info.Amount;
+ var defaultProtoId = info.DefaultPrototype;
+
+ if (_lathe.TryGetRecipesFromEntity(defaultProtoId, out var recipes))
+ {
+ var partRecipe = recipes[0];
+ if (recipes.Count > 1)
+ partRecipe = recipes.MinBy(p => p.RequiredMaterials.Values.Sum());
+
+ foreach (var (mat, matAmount) in partRecipe!.RequiredMaterials)
+ {
+ materials.TryAdd(mat, 0);
+ materials[mat] += matAmount * amount * coefficient;
+ }
+ }
+ else if (_prototype.TryIndex(defaultProtoId, out var defaultProto) &&
+ defaultProto.TryGetComponent(out var physComp))
+ {
+ foreach (var (mat, matAmount) in physComp.MaterialComposition)
+ {
+ materials.TryAdd(mat, 0);
+ materials[mat] += matAmount * amount * coefficient;
+ }
+ }
+ }
+
+ return materials;
+ }
}
}
diff --git a/Content.Shared/Construction/SharedFlatpackSystem.cs b/Content.Shared/Construction/SharedFlatpackSystem.cs
new file mode 100644
index 0000000000..094f9d86d5
--- /dev/null
+++ b/Content.Shared/Construction/SharedFlatpackSystem.cs
@@ -0,0 +1,150 @@
+using System.Numerics;
+using Content.Shared.Construction.Components;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Materials;
+using Content.Shared.Popups;
+using Content.Shared.Tools.Systems;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Construction;
+
+public abstract class SharedFlatpackSystem : EntitySystem
+{
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
+ [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
+ [Dependency] private readonly SharedMapSystem _map = default!;
+ [Dependency] protected readonly MachinePartSystem MachinePart = default!;
+ [Dependency] protected readonly SharedMaterialStorageSystem MaterialStorage = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedToolSystem _tool = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnFlatpackInteractUsing);
+ SubscribeLocalEvent(OnFlatpackExamined);
+
+ SubscribeLocalEvent(OnCreatorRemovingAttempt);
+ SubscribeLocalEvent(OnCreatorUnpaused);
+ }
+
+ private void OnFlatpackInteractUsing(Entity ent, ref InteractUsingEvent args)
+ {
+ var (uid, comp) = ent;
+ if (!_tool.HasQuality(args.Used, comp.QualityNeeded) || _container.IsEntityInContainer(ent))
+ return;
+
+ var xform = Transform(ent);
+
+ if (xform.GridUid is not { } grid || !TryComp(grid, out var gridComp))
+ return;
+
+ args.Handled = true;
+
+ if (comp.Entity == null)
+ {
+ Log.Error($"No entity prototype present for flatpack {ToPrettyString(ent)}.");
+
+ if (_net.IsServer)
+ QueueDel(ent);
+ return;
+ }
+
+ var buildPos = _map.TileIndicesFor(grid, gridComp, xform.Coordinates);
+ var intersecting = _entityLookup.GetEntitiesIntersecting(buildPos.ToEntityCoordinates(grid, _mapManager).Offset(new Vector2(0.5f, 0.5f))
+ , LookupFlags.Dynamic | LookupFlags.Static);
+
+ // todo make this logic smarter.
+ // This should eventually allow for shit like building microwaves on tables and such.
+ foreach (var intersect in intersecting)
+ {
+ if (!TryComp(intersect, out var intersectBody))
+ continue;
+
+ if (!intersectBody.Hard || !intersectBody.CanCollide)
+ continue;
+
+ // this popup is on the server because the mispredicts on the intersection is crazy
+ if (_net.IsServer)
+ _popup.PopupEntity(Loc.GetString("flatpack-unpack-no-room"), uid, args.User);
+ return;
+ }
+
+ if (_net.IsServer)
+ {
+ var spawn = Spawn(comp.Entity, _map.GridTileToLocal(grid, gridComp, buildPos));
+ _adminLogger.Add(LogType.Construction, LogImpact.Low,
+ $"{ToPrettyString(args.User):player} unpacked {ToPrettyString(spawn):entity} at {xform.Coordinates} from {ToPrettyString(uid):entity}");
+ QueueDel(uid);
+ }
+
+ _audio.PlayPredicted(comp.UnpackSound, args.Used, args.User);
+ }
+
+ private void OnFlatpackExamined(Entity ent, ref ExaminedEvent args)
+ {
+ if (!args.IsInDetailsRange)
+ return;
+ args.PushMarkup(Loc.GetString("flatpack-examine"));
+ }
+
+ private void OnCreatorRemovingAttempt(Entity ent, ref ContainerIsRemovingAttemptEvent args)
+ {
+ if (args.Container.ID == ent.Comp.SlotId && ent.Comp.Packing)
+ args.Cancel();
+ }
+
+ private void OnCreatorUnpaused(Entity ent, ref EntityUnpausedEvent args)
+ {
+ ent.Comp.PackEndTime += args.PausedTime;
+ }
+
+ public void SetupFlatpack(Entity ent, Entity machineBoard)
+ {
+ if (!Resolve(ent, ref ent.Comp) || !Resolve(machineBoard, ref machineBoard.Comp))
+ return;
+
+ if (machineBoard.Comp.Prototype is not { } machinePrototypeId)
+ return;
+
+ var comp = ent.Comp!;
+ var machinePrototype = PrototypeManager.Index(machinePrototypeId);
+
+ var meta = MetaData(ent);
+ _metaData.SetEntityName(ent, Loc.GetString("flatpack-entity-name", ("name", machinePrototype.Name)), meta);
+ _metaData.SetEntityDescription(ent, Loc.GetString("flatpack-entity-description", ("name", machinePrototype.Name)), meta);
+
+ comp.Entity = machinePrototypeId;
+ Dirty(ent, comp);
+
+ Appearance.SetData(ent, FlatpackVisuals.Machine, MetaData(machineBoard).EntityPrototype?.ID ?? string.Empty);
+ }
+
+ public Dictionary GetFlatpackCreationCost(Entity entity, Entity machineBoard)
+ {
+ var cost = MachinePart.GetMachineBoardMaterialCost(machineBoard, -1);
+ foreach (var (mat, amount) in entity.Comp.BaseMaterialCost)
+ {
+ cost.TryAdd(mat, 0);
+ cost[mat] -= amount;
+ }
+
+ return cost;
+ }
+}
diff --git a/Content.Shared/Lathe/SharedLatheSystem.cs b/Content.Shared/Lathe/SharedLatheSystem.cs
index 9debaa7719..b41a91f9c3 100644
--- a/Content.Shared/Lathe/SharedLatheSystem.cs
+++ b/Content.Shared/Lathe/SharedLatheSystem.cs
@@ -1,8 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
using Content.Shared.Emag.Systems;
using Content.Shared.Materials;
using Content.Shared.Research.Prototypes;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
namespace Content.Shared.Lathe;
@@ -14,11 +16,15 @@ public abstract class SharedLatheSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly SharedMaterialStorageSystem _materialStorage = default!;
+ private readonly Dictionary> _inverseRecipeDictionary = new();
+
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent(OnEmagged);
+ SubscribeLocalEvent(OnPrototypesReloaded);
+ BuildInverseRecipeDictionary();
}
[PublicAPI]
@@ -53,4 +59,28 @@ public abstract class SharedLatheSystem : EntitySystem
=> reduce ? (int) MathF.Ceiling(original * multiplier) : original;
protected abstract bool HasRecipe(EntityUid uid, LatheRecipePrototype recipe, LatheComponent component);
+
+ private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
+ {
+ if (!obj.WasModified())
+ return;
+ BuildInverseRecipeDictionary();
+ }
+
+ private void BuildInverseRecipeDictionary()
+ {
+ _inverseRecipeDictionary.Clear();
+ foreach (var latheRecipe in _proto.EnumeratePrototypes())
+ {
+ _inverseRecipeDictionary.GetOrNew(latheRecipe.Result).Add(latheRecipe);
+ }
+ }
+
+ public bool TryGetRecipesFromEntity(string prototype, [NotNullWhen(true)] out List? recipes)
+ {
+ recipes = new();
+ if (_inverseRecipeDictionary.TryGetValue(prototype, out var r))
+ recipes.AddRange(r);
+ return recipes.Count != 0;
+ }
}
diff --git a/Content.Shared/Materials/SharedMaterialStorageSystem.cs b/Content.Shared/Materials/SharedMaterialStorageSystem.cs
index a0cd7a9e45..9f7c87df3c 100644
--- a/Content.Shared/Materials/SharedMaterialStorageSystem.cs
+++ b/Content.Shared/Materials/SharedMaterialStorageSystem.cs
@@ -81,7 +81,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
{
if (!Resolve(uid, ref component))
return 0; //you have nothing
- return !component.Storage.TryGetValue(material, out var amount) ? 0 : amount;
+ return component.Storage.GetValueOrDefault(material, 0);
}
///
@@ -123,9 +123,35 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
{
if (!Resolve(uid, ref component))
return false;
- return CanTakeVolume(uid, volume, component) &&
- (component.MaterialWhiteList == null || component.MaterialWhiteList.Contains(materialId)) &&
- (!component.Storage.TryGetValue(materialId, out var amount) || amount + volume >= 0);
+
+ if (!CanTakeVolume(uid, volume, component))
+ return false;
+
+ if (component.MaterialWhiteList != null && !component.MaterialWhiteList.Contains(materialId))
+ return false;
+
+ var amount = component.Storage.GetValueOrDefault(materialId);
+ return amount + volume >= 0;
+ }
+
+ ///
+ /// Checks if the specified materials can be changed by the specified volumes.
+ ///
+ ///
+ ///
+ /// If the amount can be changed
+ public bool CanChangeMaterialAmount(Entity entity, Dictionary materials)
+ {
+ if (!Resolve(entity, ref entity.Comp))
+ return false;
+
+ foreach (var (material, amount) in materials)
+ {
+ if (!CanChangeMaterialAmount(entity, material, amount, entity.Comp))
+ return false;
+ }
+
+ return true;
}
///
@@ -136,20 +162,47 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
///
///
///
+ ///
/// If it was successful
- public bool TryChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null)
+ public bool TryChangeMaterialAmount(EntityUid uid, string materialId, int volume, MaterialStorageComponent? component = null, bool dirty = true)
{
if (!Resolve(uid, ref component))
return false;
if (!CanChangeMaterialAmount(uid, materialId, volume, component))
return false;
- if (!component.Storage.ContainsKey(materialId))
- component.Storage.Add(materialId, 0);
+ component.Storage.TryAdd(materialId, 0);
component.Storage[materialId] += volume;
var ev = new MaterialAmountChangedEvent();
RaiseLocalEvent(uid, ref ev);
- Dirty(component);
+
+ if (dirty)
+ Dirty(uid, component);
+ return true;
+ }
+
+ ///
+ /// Changes the amount of a specific material in the storage.
+ /// Still respects the filters in place.
+ ///
+ ///
+ ///
+ /// If the amount can be changed
+ public bool TryChangeMaterialAmount(Entity entity, Dictionary materials)
+ {
+ if (!Resolve(entity, ref entity.Comp))
+ return false;
+
+ if (!CanChangeMaterialAmount(entity, materials))
+ return false;
+
+ foreach (var (material, amount) in materials)
+ {
+ if (!TryChangeMaterialAmount(entity, material, amount, entity.Comp, false))
+ return false;
+ }
+
+ Dirty(entity, entity.Comp);
return true;
}
@@ -225,7 +278,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
insertingComp.MaterialColor = lastMat?.Color;
}
_appearance.SetData(receiver, MaterialStorageVisuals.Inserting, true);
- Dirty(insertingComp);
+ Dirty(receiver, insertingComp);
var ev = new MaterialEntityInsertedEvent(material);
RaiseLocalEvent(receiver, ref ev);
@@ -245,7 +298,7 @@ public abstract class SharedMaterialStorageSystem : EntitySystem
var ev = new GetMaterialWhitelistEvent(uid);
RaiseLocalEvent(uid, ref ev);
component.MaterialWhiteList = ev.Whitelist;
- Dirty(component);
+ Dirty(uid, component);
}
private void OnInteractUsing(EntityUid uid, MaterialStorageComponent component, InteractUsingEvent args)
diff --git a/Resources/Locale/en-US/construction/components/flatpack.ftl b/Resources/Locale/en-US/construction/components/flatpack.ftl
new file mode 100644
index 0000000000..8449da7048
--- /dev/null
+++ b/Resources/Locale/en-US/construction/components/flatpack.ftl
@@ -0,0 +1,11 @@
+flatpack-unpack-no-room = No room to unpack!
+flatpack-examine = Use a [color=yellow]multitool[/color] to unpack this.
+flatpack-entity-name = {$name} flatpack
+flatpack-entity-description = A flatpack used for constructing {INDEFINITE($name)} {$name}.
+
+flatpacker-item-slot-name = Machine board slot
+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-pack-button = Pack
diff --git a/Resources/Locale/en-US/research/technologies.ftl b/Resources/Locale/en-US/research/technologies.ftl
index 9693c5be41..c978aeaa58 100644
--- a/Resources/Locale/en-US/research/technologies.ftl
+++ b/Resources/Locale/en-US/research/technologies.ftl
@@ -8,6 +8,7 @@ research-discipline-civilian-services = Civilian Services
research-technology-fulton = Fultons
research-technology-salvage-equipment = Salvage Equipment
research-technology-advanced-powercells = Advanced Powercells
+research-technology-mechanical-compression = Mechanical Compression
research-technology-compact-power = Compact Power
research-technology-industrial-engineering = Industrial Engineering
research-technology-power-generation = Power Generation
diff --git a/Resources/Locale/en-US/wires/wire-names.ftl b/Resources/Locale/en-US/wires/wire-names.ftl
index 4a94dc9ac6..041d07d130 100644
--- a/Resources/Locale/en-US/wires/wire-names.ftl
+++ b/Resources/Locale/en-US/wires/wire-names.ftl
@@ -36,6 +36,7 @@ wires-board-name-firelock = Firelock Control
wires-board-name-windoor = Windoor Control
wires-board-name-mech = Mech
wires-board-name-fatextractor = FatExtractor
+wires-board-name-flatpacker = Flatpacker
# names that get displayed in the wire hacking hud & admin logs.
diff --git a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
index 8a9b3f16b3..d683195f2f 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/Circuitboards/Machine/production.yml
@@ -982,6 +982,19 @@
DefaultPrototype: ForkPlastic
ExamineName: Utensil
+- type: entity
+ parent: BaseMachineCircuitboard
+ id: FlatpackerMachineCircuitboard
+ name: Flatpacker 1001 machine board
+ components:
+ - type: MachineBoard
+ prototype: MachineFlatpacker
+ requirements:
+ Manipulator: 2
+ MatterBin: 1
+ materialRequirements:
+ Steel: 1
+
- type: entity
id: EmitterCircuitboard
parent: BaseMachineCircuitboard
diff --git a/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml
new file mode 100644
index 0000000000..31578bdd9c
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Devices/flatpack.yml
@@ -0,0 +1,33 @@
+- type: entity
+ parent: BaseItem
+ id: BaseFlatpack
+ name: base flatpack
+ description: A flatpack used for constructing something.
+ categories:
+ - hideSpawnMenu
+ components:
+ - type: Item
+ size: Normal
+ - type: Sprite
+ sprite: Objects/Devices/flatpack.rsi
+ layers:
+ - state: base
+ - state: overlay
+ color: "#cec8ac"
+ map: ["enum.FlatpackVisualLayers.Overlay"]
+ - state: icon-default
+ - type: Appearance
+ - type: Flatpack
+ boardColors:
+ command: "#334E6D"
+ medical: "#52B4E9"
+ service: "#9FED58"
+ engineering: "#EFB341"
+ security: "#DE3A3A"
+ science: "#D381C9"
+ supply: "#A46106"
+ - type: StaticPrice
+ price: 500
+ - type: Tag
+ tags:
+ - DroneUsable
diff --git a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml
index da389bc0a7..5f1a06a283 100644
--- a/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml
+++ b/Resources/Prototypes/Entities/Objects/Devices/station_beacon.yml
@@ -97,7 +97,7 @@
- type: entity
parent: BaseItem
id: StationBeaconPart
- name: station beacon assembly
+ name: station beacon flatpack
description: A flatpack used for constructing a station beacon.
components:
- type: Item
diff --git a/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml b/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml
index a3674178bf..62aa0dccdd 100644
--- a/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml
+++ b/Resources/Prototypes/Entities/Objects/Power/antimatter_part.yml
@@ -1,8 +1,8 @@
- type: entity
parent: BaseItem
id: AmePart
- name: AME part
- description: A flatpack used for constructing an antimatter engine reactor. Use a multitool to unpack it.
+ name: AME flatpack
+ description: A flatpack used for constructing an antimatter engine reactor.
components:
- type: Item
size: Normal
@@ -10,7 +10,8 @@
- type: Sprite
sprite: Objects/Power/AME/ame_part.rsi
state: box
- - type: AmePart
+ - type: Flatpack
+ entity: AmeShielding
- type: StaticPrice
price: 500
- type: GuideHelp
diff --git a/Resources/Prototypes/Entities/Objects/Power/solar_parts.yml b/Resources/Prototypes/Entities/Objects/Power/solar_parts.yml
index 7c8e7fa495..0fcd11f9b0 100644
--- a/Resources/Prototypes/Entities/Objects/Power/solar_parts.yml
+++ b/Resources/Prototypes/Entities/Objects/Power/solar_parts.yml
@@ -1,14 +1,16 @@
- type: entity
parent: BaseItem
id: SolarAssemblyPart
- name: solar assembly part
+ name: solar assembly flatpack
+ description: A flatpack used for constructing a solar assembly.
components:
- type: Item
size: Normal
+ - type: Flatpack
+ entity: SolarAssembly
- type: Sprite
sprite: Objects/Power/solar_parts.rsi
state: solar_assembly_parts
- type: Tag
tags:
- - SolarAssemblyPart
- - DroneUsable
+ - DroneUsable
diff --git a/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml
new file mode 100644
index 0000000000..529d2ee9f0
--- /dev/null
+++ b/Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml
@@ -0,0 +1,82 @@
+- type: entity
+ id: MachineFlatpacker
+ parent: [ BaseMachinePowered, ConstructibleMachine ]
+ name: Flatpacker 1001
+ description: An industrial machine used for expediting machine construction across the station.
+ components:
+ - type: Sprite
+ sprite: Structures/Machines/flatpacker.rsi
+ snapCardinals: true
+ layers:
+ - state: base
+ - state: screen
+ map: ["enum.PowerDeviceVisualLayers.Powered"]
+ shader: unshaded
+ - state: panel
+ map: ["enum.WiresVisualLayers.MaintenancePanel"]
+ - state: packing
+ map: ["anim"]
+ visible: false
+ - state: inserting
+ visible: false
+ map: ["enum.MaterialStorageVisualLayers.Inserting"]
+ - type: GenericVisualizer
+ visuals:
+ enum.PowerDeviceVisuals.Powered:
+ enum.PowerDeviceVisualLayers.Powered:
+ True: { visible: true }
+ False: { visible: false }
+ enum.FlatpackCreatorVisuals.Packing:
+ anim:
+ True: { visible: true }
+ False: { visible: false }
+ - type: FlatpackCreator
+ baseMaterialCost:
+ Steel: 600
+ Plastic: 200
+ - type: Machine
+ board: FlatpackerMachineCircuitboard
+ - type: MaterialStorage
+ whitelist:
+ tags:
+ - Sheet
+ - RawMaterial
+ - Ingot
+ - type: AmbientSound
+ enabled: false
+ volume: 5
+ range: 3
+ sound:
+ path: /Audio/Items/rped.ogg
+ - type: WiresPanel
+ - type: WiresVisuals
+ - type: Wires
+ boardName: wires-board-name-flatpacker
+ layoutId: Flatpacker
+ - type: Appearance
+ - type: ActivatableUI
+ key: enum.FlatpackCreatorUIKey.Key
+ - type: ActivatableUIRequiresPower
+ - type: UserInterface
+ interfaces:
+ - key: enum.FlatpackCreatorUIKey.Key
+ type: FlatpackCreatorBoundUserInterface
+ - type: ItemSlots
+ slots:
+ board_slot:
+ name: flatpacker-item-slot-name
+ whitelist:
+ components:
+ - MachineBoard
+ - type: ContainerContainer
+ containers:
+ machine_board: !type:Container
+ machine_parts: !type:Container
+ board_slot: !type:ContainerSlot
+ - type: Construction
+ containers:
+ - machine_parts
+ - machine_board
+ - board_slot
+ - type: StaticPrice
+ price: 2000
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index 4600a23ca9..899047c324 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -389,6 +389,7 @@
- MicrowaveMachineCircuitboard
- ElectricGrillMachineCircuitboard
- FatExtractorMachineCircuitboard
+ - FlatpackerMachineCircuitboard
- SheetifierMachineCircuitboard
- ShuttleConsoleCircuitboard
- RadarConsoleCircuitboard
diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml
index 750bdadf06..445ee0728d 100644
--- a/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml
+++ b/Resources/Prototypes/Entities/Structures/Power/Generation/solar.yml
@@ -41,7 +41,7 @@
loadNode: output
sprite: Structures/Power/Generation/solar_panel.rsi
state: static
- collectionName: SolarPanel
+ collectionName: SolarPanel
- type: Anchorable
- type: Pullable
- type: Electrified
@@ -157,6 +157,7 @@
- type: Construction
graph: SolarPanel
node: solarassembly
+ defaultTarget: solarpanel
- type: entity
id: SolarTracker
diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml
index 38c4c247ac..3b5c04f4fe 100644
--- a/Resources/Prototypes/Recipes/Lathes/electronics.yml
+++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml
@@ -619,6 +619,15 @@
Steel: 100
Glass: 900
+- type: latheRecipe
+ id: FlatpackerMachineCircuitboard
+ result: FlatpackerMachineCircuitboard
+ completetime: 4
+ materials:
+ Steel: 100
+ Glass: 900
+ Gold: 100
+
- type: latheRecipe
id: SheetifierMachineCircuitboard
result: SheetifierMachineCircuitboard
diff --git a/Resources/Prototypes/Research/industrial.yml b/Resources/Prototypes/Research/industrial.yml
index f287bcd953..d991645be0 100644
--- a/Resources/Prototypes/Research/industrial.yml
+++ b/Resources/Prototypes/Research/industrial.yml
@@ -27,6 +27,18 @@
recipeUnlocks:
- PowerCellHigh
+- type: technology
+ id: MechanicalCompression
+ name: research-technology-mechanical-compression
+ icon:
+ sprite: Structures/Machines/flatpacker.rsi
+ state: base
+ discipline: Industrial
+ tier: 1
+ cost: 10000
+ recipeUnlocks:
+ - FlatpackerMachineCircuitboard
+
- type: technology
id: CompactPower
name: research-technology-compact-power
diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml
index a8e8ef1488..44e8d9593f 100644
--- a/Resources/Prototypes/tags.yml
+++ b/Resources/Prototypes/tags.yml
@@ -1011,9 +1011,6 @@
- type: Tag
id: Soap
-- type: Tag
- id: SolarAssemblyPart
-
- type: Tag
id: SolarTrackerElectronics
diff --git a/Resources/Textures/Objects/Devices/flatpack.rsi/base.png b/Resources/Textures/Objects/Devices/flatpack.rsi/base.png
new file mode 100644
index 0000000000..628808adc7
Binary files /dev/null and b/Resources/Textures/Objects/Devices/flatpack.rsi/base.png differ
diff --git a/Resources/Textures/Objects/Devices/flatpack.rsi/icon-default.png b/Resources/Textures/Objects/Devices/flatpack.rsi/icon-default.png
new file mode 100644
index 0000000000..da9da035a0
Binary files /dev/null and b/Resources/Textures/Objects/Devices/flatpack.rsi/icon-default.png differ
diff --git a/Resources/Textures/Objects/Devices/flatpack.rsi/meta.json b/Resources/Textures/Objects/Devices/flatpack.rsi/meta.json
new file mode 100644
index 0000000000..6381b5c80b
--- /dev/null
+++ b/Resources/Textures/Objects/Devices/flatpack.rsi/meta.json
@@ -0,0 +1,20 @@
+{
+ "version": 1,
+ "license": "CC0-1.0",
+ "copyright": "Created by EmoGarbage404 (github) for SS14",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "base"
+ },
+ {
+ "name": "overlay"
+ },
+ {
+ "name": "icon-default"
+ }
+ ]
+}
diff --git a/Resources/Textures/Objects/Devices/flatpack.rsi/overlay.png b/Resources/Textures/Objects/Devices/flatpack.rsi/overlay.png
new file mode 100644
index 0000000000..c066933cd5
Binary files /dev/null and b/Resources/Textures/Objects/Devices/flatpack.rsi/overlay.png differ
diff --git a/Resources/Textures/Structures/Machines/flatpacker.rsi/base.png b/Resources/Textures/Structures/Machines/flatpacker.rsi/base.png
new file mode 100644
index 0000000000..b92a8de43d
Binary files /dev/null and b/Resources/Textures/Structures/Machines/flatpacker.rsi/base.png differ
diff --git a/Resources/Textures/Structures/Machines/flatpacker.rsi/inserting.png b/Resources/Textures/Structures/Machines/flatpacker.rsi/inserting.png
new file mode 100644
index 0000000000..e8226a9553
Binary files /dev/null and b/Resources/Textures/Structures/Machines/flatpacker.rsi/inserting.png differ
diff --git a/Resources/Textures/Structures/Machines/flatpacker.rsi/meta.json b/Resources/Textures/Structures/Machines/flatpacker.rsi/meta.json
new file mode 100644
index 0000000000..0082154aea
--- /dev/null
+++ b/Resources/Textures/Structures/Machines/flatpacker.rsi/meta.json
@@ -0,0 +1,42 @@
+{
+ "version": 1,
+ "license": "CC0-1.0",
+ "copyright": "Based on flatpacker sprites from vgstation, adapted by EmoGarbage404 (github)",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "base"
+ },
+ {
+ "name": "panel"
+ },
+ {
+ "name": "screen"
+ },
+ {
+ "name": "packing",
+ "delays": [
+ [
+ 0.1,
+ 0.1,
+ 0.1,
+ 0.1
+ ]
+ ]
+ },
+ {
+ "name": "inserting",
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
+ }
+ ]
+}
diff --git a/Resources/Textures/Structures/Machines/flatpacker.rsi/packing.png b/Resources/Textures/Structures/Machines/flatpacker.rsi/packing.png
new file mode 100644
index 0000000000..2b07b99831
Binary files /dev/null and b/Resources/Textures/Structures/Machines/flatpacker.rsi/packing.png differ
diff --git a/Resources/Textures/Structures/Machines/flatpacker.rsi/panel.png b/Resources/Textures/Structures/Machines/flatpacker.rsi/panel.png
new file mode 100644
index 0000000000..b21c6df3a8
Binary files /dev/null and b/Resources/Textures/Structures/Machines/flatpacker.rsi/panel.png differ
diff --git a/Resources/Textures/Structures/Machines/flatpacker.rsi/screen.png b/Resources/Textures/Structures/Machines/flatpacker.rsi/screen.png
new file mode 100644
index 0000000000..cd09bb993a
Binary files /dev/null and b/Resources/Textures/Structures/Machines/flatpacker.rsi/screen.png differ
diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings
index 7d681e02b1..42cf8d1cab 100644
--- a/SpaceStation14.sln.DotSettings
+++ b/SpaceStation14.sln.DotSettings
@@ -602,6 +602,7 @@ public sealed partial class $CLASS$ : Shared$CLASS$ {
True
True
True
+ True
True
True
True