diff --git a/Content.Server/GameObjects/Components/Chemistry/RehydratableComponent.cs b/Content.Server/GameObjects/Components/Chemistry/RehydratableComponent.cs
new file mode 100644
index 0000000000..f727d00616
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Chemistry/RehydratableComponent.cs
@@ -0,0 +1,92 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using Content.Server.GameObjects.Components.Body.Digestive;
+using Content.Server.GameObjects.Components.Chemistry;
+using Content.Server.GameObjects.Components.GUI;
+using Content.Server.GameObjects.Components.Items.Storage;
+using Content.Server.GameObjects.EntitySystems;
+using Content.Server.Utility;
+using Content.Shared.Chemistry;
+using Content.Shared.Interfaces;
+using Content.Shared.Interfaces.GameObjects.Components;
+using Content.Shared.Utility;
+using Robust.Server.GameObjects.EntitySystems;
+using Robust.Server.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.Interfaces.GameObjects.Components;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Serialization;
+using Robust.Shared.ViewVariables;
+using Robust.Shared.Log;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Server.GameObjects.Components.Chemistry
+{
+ ///
+ /// Basically, monkey cubes.
+ /// But specifically, this component deletes the entity and spawns in a new entity when the entity is exposed to a given reagent.
+ ///
+ [RegisterComponent]
+ [ComponentReference(typeof(IReagentReaction))]
+ [ComponentReference(typeof(ISolutionChange))]
+ public class RehydratableComponent : Component, IReagentReaction, ISolutionChange
+ {
+ public override string Name => "Rehydratable";
+
+ [ViewVariables]
+ private string _catalystPrototype = "";
+ [ViewVariables]
+ private string? _targetPrototype;
+
+ private bool _expanding;
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+ serializer.DataField(ref _catalystPrototype, "catalyst", "chem.H2O");
+ serializer.DataField(ref _targetPrototype, "target", null);
+ }
+
+ ReagentUnit IReagentReaction.ReagentReactTouch(ReagentPrototype reagent, ReagentUnit volume) => Reaction(reagent, volume);
+ ReagentUnit IReagentReaction.ReagentReactInjection(ReagentPrototype reagent, ReagentUnit volume) => Reaction(reagent, volume);
+
+ private ReagentUnit Reaction(ReagentPrototype reagent, ReagentUnit volume)
+ {
+ if ((volume > ReagentUnit.Zero) && (reagent.ID == _catalystPrototype))
+ {
+ Expand();
+ }
+ return ReagentUnit.Zero;
+ }
+
+ void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
+ {
+ var solution = eventArgs.Owner.GetComponent();
+ if (solution.Solution.GetReagentQuantity(_catalystPrototype) > ReagentUnit.Zero)
+ {
+ Expand();
+ }
+ }
+
+ // Try not to make this public if you can help it.
+ private void Expand()
+ {
+ if (_expanding)
+ {
+ return;
+ }
+ _expanding = true;
+ Owner.PopupMessageEveryone(Loc.GetString("{0:TheName} expands!", Owner));
+ if (!string.IsNullOrEmpty(_targetPrototype))
+ {
+ Owner.EntityManager.SpawnEntity(_targetPrototype, Owner.Transform.Coordinates);
+ }
+ Owner.Delete();
+ }
+ }
+}
diff --git a/Content.Server/GameObjects/Components/Kitchen/ButcherableComponent.cs b/Content.Server/GameObjects/Components/Kitchen/ButcherableComponent.cs
new file mode 100644
index 0000000000..0e7d7b3dbc
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Kitchen/ButcherableComponent.cs
@@ -0,0 +1,30 @@
+#nullable enable
+using System;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Server.GameObjects.Components.Kitchen
+{
+ ///
+ /// Indicates that the entity can be thrown on a kitchen spike for butchering.
+ ///
+ [RegisterComponent]
+ public class ButcherableComponent : Component
+ {
+ public override string Name => "Butcherable";
+
+ [ViewVariables]
+ public string? MeatPrototype => _meatPrototype;
+
+ [ViewVariables]
+ private string? _meatPrototype;
+
+ public override void ExposeData(ObjectSerializer serializer)
+ {
+ base.ExposeData(serializer);
+ serializer.DataField(ref _meatPrototype, "meat", null);
+ }
+ }
+}
+
diff --git a/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs b/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs
new file mode 100644
index 0000000000..35bfe18dc8
--- /dev/null
+++ b/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs
@@ -0,0 +1,114 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Content.Server.GameObjects.Components.Body;
+using Content.Server.GameObjects.Components.Chemistry;
+using Content.Server.GameObjects.Components.GUI;
+using Content.Server.GameObjects.Components.Items.Storage;
+using Content.Server.GameObjects.Components.Power.ApcNetComponents;
+using Content.Server.GameObjects.EntitySystems;
+using Content.Server.Interfaces.Chat;
+using Content.Server.Interfaces.GameObjects;
+using Content.Server.Utility;
+using Content.Shared.Chemistry;
+using Content.Shared.GameObjects.Components.Body;
+using Content.Shared.GameObjects.Components.Power;
+using Content.Shared.Interfaces;
+using Content.Shared.Interfaces.GameObjects.Components;
+using Content.Shared.Kitchen;
+using Content.Shared.Prototypes.Kitchen;
+using Robust.Server.GameObjects;
+using Robust.Server.GameObjects.Components.Container;
+using Robust.Server.GameObjects.Components.UserInterface;
+using Robust.Server.GameObjects.EntitySystems;
+using Robust.Server.Interfaces.GameObjects;
+using Robust.Shared.Audio;
+using Robust.Shared.GameObjects;
+using Robust.Shared.GameObjects.Systems;
+using Robust.Shared.Interfaces.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+using Robust.Shared.Serialization;
+using Robust.Shared.Timers;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Server.GameObjects.Components.Kitchen
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(IActivate))]
+ public class KitchenSpikeComponent : Component, IActivate, ISuicideAct
+ {
+ public override string Name => "KitchenSpike";
+
+ private int _meatParts;
+ private string? _meatPrototype;
+ private string _meatSource1p = "?";
+ private string _meatSource0 = "?";
+
+ void IActivate.Activate(ActivateEventArgs eventArgs)
+ {
+ var victim = eventArgs.User.GetComponent().PulledObject?.Owner;
+
+ var sprite = Owner.GetComponent();
+
+ if (victim == null)
+ {
+ if (_meatParts == 0)
+ {
+ return;
+ }
+ _meatParts--;
+
+ if (!string.IsNullOrEmpty(_meatPrototype))
+ {
+ Owner.EntityManager.SpawnEntity(_meatPrototype, Owner.Transform.Coordinates);
+ }
+
+ if (_meatParts != 0)
+ {
+ eventArgs.User.PopupMessage(_meatSource1p);
+ }
+ else
+ {
+ sprite.LayerSetState(0, "spike");
+ eventArgs.User.PopupMessage(_meatSource0);
+ }
+ return;
+ }
+ else if (_meatParts > 0)
+ {
+ Owner.PopupMessage(eventArgs.User, Loc.GetString("The spike already has something on it, finish collecting its meat first!"));
+ return;
+ }
+
+ if (!victim.TryGetComponent(out var food))
+ {
+ Owner.PopupMessage(eventArgs.User, Loc.GetString("{0:theName} can't be butchered on the spike.", victim));
+ return;
+ }
+
+ _meatPrototype = food.MeatPrototype;
+ _meatParts = 5;
+ _meatSource1p = Loc.GetString("You remove some meat from {0:theName}.", victim);
+ _meatSource0 = Loc.GetString("You remove the last piece of meat from {0:theName}!", victim);
+
+ sprite.LayerSetState(0, "spikebloody");
+
+ Owner.PopupMessageEveryone(Loc.GetString("{0:theName} has forced {1:theName} onto the spike, killing them instantly!", eventArgs.User, victim));
+ victim.Delete();
+ }
+
+ public SuicideKind Suicide(IEntity victim, IChatManager chat)
+ {
+ var othersMessage = Loc.GetString("{0:theName} has thrown themselves on a meat spike!", victim);
+ victim.PopupMessageOtherClients(othersMessage);
+
+ var selfMessage = Loc.GetString("You throw yourself on a meat spike!");
+ victim.PopupMessage(selfMessage);
+
+ return SuicideKind.Piercing;
+ }
+ }
+}
diff --git a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs
index 02919bcf8e..9e16860294 100644
--- a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs
+++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs
@@ -15,7 +15,7 @@ namespace Content.Shared.GameObjects.Components.Items
public sealed override uint? NetID => ContentNetIDs.HANDS;
[ViewVariables]
- protected ICollidableComponent? PulledObject;
+ public ICollidableComponent? PulledObject { get; protected set; }
[ViewVariables]
protected bool IsPulling => PulledObject != null;
diff --git a/Resources/Prototypes/Entities/Constructible/Ground/kitchen.yml b/Resources/Prototypes/Entities/Constructible/Ground/kitchen.yml
new file mode 100644
index 0000000000..ab76b8d1c2
--- /dev/null
+++ b/Resources/Prototypes/Entities/Constructible/Ground/kitchen.yml
@@ -0,0 +1,23 @@
+- type: entity
+ id: KitchenSpike
+ name: meat spike
+ description: A spike for collecting meat from animals.
+ components:
+ - type: Clickable
+ - type: InteractionOutline
+ - type: Collidable
+ shapes:
+ - !type:PhysShapeAabb
+ mask:
+ - Impassable
+ layer:
+ - Impassable
+ - type: Sprite
+ # temp to make clickmask work
+ sprite: Constructible/Misc/kitchen.rsi
+ state: spike
+ - type: Anchorable
+ - type: Pullable
+ - type: Destructible
+ deadThreshold: 50
+ - type: KitchenSpike
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index a4dd7a47b5..317de00e00 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -60,6 +60,8 @@
normal: monkey
dead: dead
- type: Pullable
+ - type: Butcherable
+ meat: FoodMeat
- type: entity
save: false
diff --git a/Resources/Prototypes/Entities/Objects/Consumable/food.yml b/Resources/Prototypes/Entities/Objects/Consumable/food.yml
index a22396e796..9c35a42c1b 100644
--- a/Resources/Prototypes/Entities/Objects/Consumable/food.yml
+++ b/Resources/Prototypes/Entities/Objects/Consumable/food.yml
@@ -1705,9 +1705,12 @@
reagents:
- ReagentId: chem.Nutriment
Quantity: 10
+ maxVol: 11 # needs room for water
+ caps: AddTo, RemoveFrom, FitsInDispenser
- type: Sprite
sprite: Objects/Consumable/Food/monkeycube.rsi
-
+ - type: Rehydratable
+ target: MonkeyMob_Content
- type: entity
parent: FoodBase
diff --git a/Resources/Prototypes/Recipes/Construction/kitchen.yml b/Resources/Prototypes/Recipes/Construction/kitchen.yml
new file mode 100644
index 0000000000..75a4504c10
--- /dev/null
+++ b/Resources/Prototypes/Recipes/Construction/kitchen.yml
@@ -0,0 +1,18 @@
+- type: construction
+ name: meat spike
+ id: KitchenSpike
+ category: Items/Kitchen
+ keywords: [kitchen]
+ description:
+ icon: Constructible/Misc/kitchen.rsi/spike.png
+ result: KitchenSpike
+ objectType: Structure
+ steps:
+ # Replace with Metal Rods whenever
+ # also this should be done with a Welding tool if that is specifiable
+ - material: Metal
+ amount: 3
+ reverse:
+ # logic here: BOLT_TURNING -> Wrench -> Anchoring
+ tool: Anchoring
+
diff --git a/Resources/Textures/Constructible/Misc/kitchen.rsi/meta.json b/Resources/Textures/Constructible/Misc/kitchen.rsi/meta.json
new file mode 100644
index 0000000000..7bf3d1be3c
--- /dev/null
+++ b/Resources/Textures/Constructible/Misc/kitchen.rsi/meta.json
@@ -0,0 +1,12 @@
+{
+ "version":1,
+ "size":{"x":32,"y":32},
+ "license":"CC-BY-SA-3.0",
+ "copyright":"Taken from https://github.com/discordia-space/CEV-Eris/blob/2b969adc2dfd3e9621bf3597c5cbffeb3ac8c9f0/icons/obj/kitchen.dmi",
+ "states":[
+ {"name":"spike","directions":1,"delays":[[1.0]]},
+ {"name":"spikebloody","directions":1,"delays":[[1.0]]},
+ {"name":"spikebloodygreen","directions":1,"delays":[[1.0]]}
+ ]
+}
+
diff --git a/Resources/Textures/Constructible/Misc/kitchen.rsi/spike.png b/Resources/Textures/Constructible/Misc/kitchen.rsi/spike.png
new file mode 100644
index 0000000000..f39fafcf20
Binary files /dev/null and b/Resources/Textures/Constructible/Misc/kitchen.rsi/spike.png differ
diff --git a/Resources/Textures/Constructible/Misc/kitchen.rsi/spikebloody.png b/Resources/Textures/Constructible/Misc/kitchen.rsi/spikebloody.png
new file mode 100644
index 0000000000..e914c0225c
Binary files /dev/null and b/Resources/Textures/Constructible/Misc/kitchen.rsi/spikebloody.png differ
diff --git a/Resources/Textures/Constructible/Misc/kitchen.rsi/spikebloodygreen.png b/Resources/Textures/Constructible/Misc/kitchen.rsi/spikebloodygreen.png
new file mode 100644
index 0000000000..213c7247ba
Binary files /dev/null and b/Resources/Textures/Constructible/Misc/kitchen.rsi/spikebloodygreen.png differ