From 1c11332fa4b77d556e7f3db17f391dbd2664cda5 Mon Sep 17 00:00:00 2001
From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Date: Wed, 3 Jan 2024 01:16:02 -0500
Subject: [PATCH] Flatpacks and the Flatpacker 1001 (#23338)
* Flatpacker and flatpacks
* ok that's good enough
* convert solars/AME to flatpacks
* mats, mats, we are the mats
* basic mechanics are DONE
* thing
* final UI
* sloth
* rped jumpscare
* rename
---
Content.Client/Construction/FlatpackSystem.cs | 48 ++++++
.../UI/FlatpackCreatorBoundUserInterface.cs | 40 +++++
.../Construction/UI/FlatpackCreatorMenu.xaml | 43 +++++
.../UI/FlatpackCreatorMenu.xaml.cs | 147 +++++++++++++++++
.../Materials/UI/MaterialDisplay.xaml | 1 -
.../UI/MaterialStorageControl.xaml.cs | 4 +-
.../Ame/Components/AmePartComponent.cs | 24 ---
.../Ame/EntitySystems/AmePartSystem.cs | 53 -------
Content.Server/Construction/FlatpackSystem.cs | 98 ++++++++++++
.../Components/FlatpackComponent.cs | 51 ++++++
.../Components/FlatpackCreatorComponent.cs | 69 ++++++++
.../Construction/MachinePartSystem.cs | 87 ++++++++++
.../Construction/SharedFlatpackSystem.cs | 150 ++++++++++++++++++
Content.Shared/Lathe/SharedLatheSystem.cs | 30 ++++
.../Materials/SharedMaterialStorageSystem.cs | 73 +++++++--
.../construction/components/flatpack.ftl | 11 ++
.../Locale/en-US/research/technologies.ftl | 1 +
Resources/Locale/en-US/wires/wire-names.ftl | 1 +
.../Circuitboards/Machine/production.yml | 13 ++
.../Entities/Objects/Devices/flatpack.yml | 33 ++++
.../Objects/Devices/station_beacon.yml | 2 +-
.../Objects/Power/antimatter_part.yml | 7 +-
.../Entities/Objects/Power/solar_parts.yml | 8 +-
.../Structures/Machines/flatpacker.yml | 82 ++++++++++
.../Entities/Structures/Machines/lathe.yml | 1 +
.../Structures/Power/Generation/solar.yml | 3 +-
.../Prototypes/Recipes/Lathes/electronics.yml | 9 ++
Resources/Prototypes/Research/industrial.yml | 12 ++
Resources/Prototypes/tags.yml | 3 -
.../Objects/Devices/flatpack.rsi/base.png | Bin 0 -> 331 bytes
.../Devices/flatpack.rsi/icon-default.png | Bin 0 -> 175 bytes
.../Objects/Devices/flatpack.rsi/meta.json | 20 +++
.../Objects/Devices/flatpack.rsi/overlay.png | Bin 0 -> 144 bytes
.../Machines/flatpacker.rsi/base.png | Bin 0 -> 1046 bytes
.../Machines/flatpacker.rsi/inserting.png | Bin 0 -> 235 bytes
.../Machines/flatpacker.rsi/meta.json | 42 +++++
.../Machines/flatpacker.rsi/packing.png | Bin 0 -> 1161 bytes
.../Machines/flatpacker.rsi/panel.png | Bin 0 -> 292 bytes
.../Machines/flatpacker.rsi/screen.png | Bin 0 -> 155 bytes
SpaceStation14.sln.DotSettings | 1 +
40 files changed, 1066 insertions(+), 101 deletions(-)
create mode 100644 Content.Client/Construction/FlatpackSystem.cs
create mode 100644 Content.Client/Construction/UI/FlatpackCreatorBoundUserInterface.cs
create mode 100644 Content.Client/Construction/UI/FlatpackCreatorMenu.xaml
create mode 100644 Content.Client/Construction/UI/FlatpackCreatorMenu.xaml.cs
delete mode 100644 Content.Server/Ame/Components/AmePartComponent.cs
delete mode 100644 Content.Server/Ame/EntitySystems/AmePartSystem.cs
create mode 100644 Content.Server/Construction/FlatpackSystem.cs
create mode 100644 Content.Shared/Construction/Components/FlatpackComponent.cs
create mode 100644 Content.Shared/Construction/Components/FlatpackCreatorComponent.cs
create mode 100644 Content.Shared/Construction/SharedFlatpackSystem.cs
create mode 100644 Resources/Locale/en-US/construction/components/flatpack.ftl
create mode 100644 Resources/Prototypes/Entities/Objects/Devices/flatpack.yml
create mode 100644 Resources/Prototypes/Entities/Structures/Machines/flatpacker.yml
create mode 100644 Resources/Textures/Objects/Devices/flatpack.rsi/base.png
create mode 100644 Resources/Textures/Objects/Devices/flatpack.rsi/icon-default.png
create mode 100644 Resources/Textures/Objects/Devices/flatpack.rsi/meta.json
create mode 100644 Resources/Textures/Objects/Devices/flatpack.rsi/overlay.png
create mode 100644 Resources/Textures/Structures/Machines/flatpacker.rsi/base.png
create mode 100644 Resources/Textures/Structures/Machines/flatpacker.rsi/inserting.png
create mode 100644 Resources/Textures/Structures/Machines/flatpacker.rsi/meta.json
create mode 100644 Resources/Textures/Structures/Machines/flatpacker.rsi/packing.png
create mode 100644 Resources/Textures/Structures/Machines/flatpacker.rsi/panel.png
create mode 100644 Resources/Textures/Structures/Machines/flatpacker.rsi/screen.png
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 0000000000000000000000000000000000000000..628808adc7cf33292e6b2e88b763ed92908f2f91
GIT binary patch
literal 331
zcmV-R0kr;!P)Px$1xZ9fR9J=Wl|M@ZK@dluVs+U%S4je?_8{blkTwYl{wb`)4=GkQV&NB9I7tw0
ztAJg+)O*{;DuqS%<|3lSzH*tJ-TS@(169;dobff5}u~r*1=V^
zLGmvskdKGk$>fZrqp+o=sy2IT)3l4uG`@BiQ!vU$e6H6&6WC2)ON&woO7(-yY-#EB
zPDPW81OSXF7!3M6K0GxxySu-|n4*=yvAFV=hyqdI{|n6KQ=IdnX3lwL^Jy!A#ytR_
zGtJe_1xh8saGmLIZ=ua87Vxzr=_mkJtB<{PyXE6+2f%W%Y&=9DxQ3V4=LcQFm*-c}
drHw=)kuTU_NacRkzTE%-002ovPDHLkV1hJ`l+*wK
literal 0
HcmV?d00001
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 0000000000000000000000000000000000000000..da9da035a06495de4b261a65c028eda03c43ae2a
GIT binary patch
literal 175
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}nVv3=ArY;~
z2@qAJn~Q4YWEMZ1shqI`u=BW7X>XbWjNrgQt2|OygXX{Zr(4`9Czl+HRoTQ
qac}(09TI9{a8~)#Gi|mH)$$%jUdj198%_dEW$<+Mb6Mw<&;$Uw#V{=Z
literal 0
HcmV?d00001
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 0000000000000000000000000000000000000000..b92a8de43de6646dc6b770e4307c63eaf11f15fc
GIT binary patch
literal 1046
zcmV+x1nK*UP)Px&&`Cr=R9J=OmrG9@R}{y8t`h3lCc~tD#9>NI+Mq}=vZ$~rMM7Nx*|h3sKuYo@
z`T?qbfvQq=-B<0VKsQw+LU<^Z1tIjX8kYfRG=&z4zgW*>j=19-=io33h$6_P_sgJ*Cw#U)j|BK)MG%?CAMce`O
zVwrgOJpdshySas;7CezAv{<)*={t9u2`wh#_%x?Gl0z#AjSi=S+OQ|`GqDj_KqsCW
zmLqwmh#@U5`oe1PT=KG6V^AArV{O@8z<&MkTxXs0vYT6;*0k@IB%nQx8SSzsXVpqY
zvW;iUuJhwJrX*Vku*{OA%x(K^IOPJsCzF$s{b*&Srw>sF3|%Lz205r5c#5SLJX%?C
zB?pHb*xAmK&}RNMu~$d}a`}R2gP{<9pW_+{mFoVRsq27LiY>Dwwf_ZN&w^LgvfJ>v
z<6a}*3Q
z7@}#5ip1flPAQ)g^CHnG^abFkE;(OVTw-By$yETrc;>7YbbST4Zl?&VLCP-*G~SQl
z=QZ>Nh-ir}M~#d9_+Au9jV1w@o1GDf+zSaU)_Vu+3AucMP%y;deg*%fi>0+gV{-meq>6z1>H3tdb#aLhbnAdLNZy
zR%)I4+9B;g*{rd%oh5$l8XIfNjAy23D6O|I6^mkCUw`Ym<+qNds=2i>O?!a*OSnOq3aZ%ulgL1V>Nmcv$!WRdTw^c
zk>rm&A^``rVSZhI=!twRJ&mF3K#%)?S=J!H$STaPslnsa6xJ`>HZl4Bt|AMi+kjDR{?l8Bp=T$
za$C$ZMm}$S5xYMq06lHx>R1<-<5^%6Cq^bv2Cle}_a^IQdlH}wT#<616K{(gdW4{H
z5p8X#X52gEQvUNxyiI1jDf)F=
fu2vCHecc;Yzq>}8rhj;q0MhR1>gTe~DWM4f);C-w
literal 0
HcmV?d00001
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 0000000000000000000000000000000000000000..2b07b99831d6e86a17770397afee80ade472d944
GIT binary patch
literal 1161
zcmV;41a|w0P)Px(LrFwIRCt{2n%_@TR~*2GChC)+TXKMXXA;C=Lr$k%GrPS88um?PFcEi1t
z-gB#q=abyz=6-L^=X<_6J#BNo02B&^Liv9XuytBUkGbW2i8sr;98RY_mw>I)60mhX
z{eX%<6iG5K{dTs+_sewP3I5Fa$FC&M)i-Serb~xhlP(PMKNWsL8EF
z?~w-gTe@6cJjO80>WUQV&q(&gcqFVt_jb3(FO$jOMp!Zm0Qo`^NX4HaS3D&(F7Mai
z#HcS}Iiok?a+Hj9V91Z}3t})a>Pwuf(i_(ELl+)TRh$g*NLYs^&-PneOeG}zoCTPF
zHFE?ck`R)2v^5l=*lH0ZlFBd8UUcltg0pH
z-&B7LZworrAZ0r4iscIYJ<_Q23;P>2
zxs_agQvCtEn_!b!Rg3Q9k+2S3*7^g6!^K6qMon&|5IG;;*PBY~)!DmRje8AjKS>Yg
z>2S}*ukHIjQx}wJvlO>(k7eVrfoIPXTD6+#@!WmF~Ya{5NHJd2g%(Lw@$8
z(aCUhVQyKdpK}FtINW32$HDRT9*=juxZUmXrz%YrhxfsM9Afkd@O}g=T`n)zl0#h@
zV!|-Y>Jj_}Q=R79irw2$5lHzr><>Wq=xqM{{?Cioi;Dq1ez$Y*^-ceiK(}-7HA#Qc
z{Q&>~tqt`%%$54Q)gq+1KfvK~&jPIU2axjfc8Nt6Lm*eI?SCi%hs!;?RVGVIe*o?W
zSnE}gN~KHf_CE#hLmU1VV0UMrBCwx}1>0lmbxjQpkI((s?VKSoKJ5>nP$(1%g+ifF
bK2H7xMdqFEfti(o00000NkvXXu0mjf*?&C$
literal 0
HcmV?d00001
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 0000000000000000000000000000000000000000..b21c6df3a8011e35c074ef49e26bbe382101bd3e
GIT binary patch
literal 292
zcmV+<0o(qGP)Px#-bqA3R9J=W(=m#|P!NFOUkS)!)C*Wzq_D6|XQAK)USXdLgtg2ff}{^v=LU9G
z!Zxo|DyZ({~~2QC620
zvgw;p7O@TCuk3vYy%f#VLgONdU$B)B+uCY9B&2?
zpCI&nx~mQVjWM)(F0G!+HuyjOZmf!UfF#dua|ArY;~
z2@lbfsom)k)NhyhK31`ik*KE`p+Nnp^P1+;*{)78&qol`;+0CvnQ&j0`b
literal 0
HcmV?d00001
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