diff --git a/Content.Client/Sericulture/SericultureSystem.cs b/Content.Client/Sericulture/SericultureSystem.cs
new file mode 100644
index 0000000000..6b2ad2fd17
--- /dev/null
+++ b/Content.Client/Sericulture/SericultureSystem.cs
@@ -0,0 +1,8 @@
+using Content.Shared.Sericulture;
+
+namespace Content.Client.Sericulture;
+
+///
+///
+///
+public sealed partial class SericultureSystem : SharedSericultureSystem { }
diff --git a/Content.Server/Sericulture/SericultureComponent.cs b/Content.Server/Sericulture/SericultureComponent.cs
deleted file mode 100644
index 2d258accc8..0000000000
--- a/Content.Server/Sericulture/SericultureComponent.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Sericulture;
-
-[RegisterComponent]
-public sealed partial class SericultureComponent : Component
-{
- [DataField("popupText")]
- public string PopupText = "sericulture-failure-hunger";
-
- ///
- /// What will be produced at the end of the action.
- ///
- [DataField("entityProduced", required: true)]
- public string EntityProduced = "";
-
- [DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string Action = "ActionSericulture";
-
- [DataField("actionEntity")] public EntityUid? ActionEntity;
-
- ///
- /// How long will it take to make.
- ///
- [DataField("productionLength", required: true), ViewVariables(VVAccess.ReadWrite)]
- public float ProductionLength = 0;
-
- [DataField("hungerCost"), ViewVariables(VVAccess.ReadWrite)]
- public float HungerCost = 0f;
-}
diff --git a/Content.Server/Sericulture/SericultureSystem.cs b/Content.Server/Sericulture/SericultureSystem.cs
index c100d3d4a5..672c0b914b 100644
--- a/Content.Server/Sericulture/SericultureSystem.cs
+++ b/Content.Server/Sericulture/SericultureSystem.cs
@@ -1,89 +1,7 @@
-using Content.Server.Actions;
-using Content.Server.DoAfter;
-using Content.Server.Popups;
-using Content.Server.Stack;
-using Content.Shared.DoAfter;
-using Content.Shared.Nutrition.Components;
-using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Sericulture;
namespace Content.Server.Sericulture;
-public sealed partial class SericultureSystem : EntitySystem
+public sealed partial class SericultureSystem : SharedSericultureSystem
{
- [Dependency] private readonly ActionsSystem _actionsSystem = default!;
- [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
- [Dependency] private readonly HungerSystem _hungerSystem = default!;
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly StackSystem _stackSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnCompMapInit);
- SubscribeLocalEvent(OnCompRemove);
- SubscribeLocalEvent(OnSericultureStart);
- SubscribeLocalEvent(OnSericultureDoAfter);
- }
-
- private void OnCompMapInit(EntityUid uid, SericultureComponent comp, MapInitEvent args)
- {
- _actionsSystem.AddAction(uid, ref comp.ActionEntity, comp.Action, uid);
- }
-
- private void OnCompRemove(EntityUid uid, SericultureComponent comp, ComponentShutdown args)
- {
- _actionsSystem.RemoveAction(uid, comp.ActionEntity);
- }
-
- private void OnSericultureStart(EntityUid uid, SericultureComponent comp, SericultureActionEvent args)
- {
- if (IsHungry(uid))
- {
- _popupSystem.PopupEntity(Loc.GetString(comp.PopupText), uid, uid);
- return;
- }
-
- var doAfter = new DoAfterArgs(EntityManager, uid, comp.ProductionLength, new SericultureDoAfterEvent(), uid)
- {
- BreakOnUserMove = true,
- BlockDuplicate = true,
- BreakOnDamage = true,
- CancelDuplicate = true,
- };
-
- _doAfterSystem.TryStartDoAfter(doAfter);
- }
-
- private void OnSericultureDoAfter(EntityUid uid, SericultureComponent comp, SericultureDoAfterEvent args)
- {
- if (args.Cancelled || args.Handled || comp.Deleted)
- return;
-
- if (IsHungry(uid))
- {
- _popupSystem.PopupEntity(Loc.GetString(comp.PopupText), uid, uid);
- return;
- }
-
- _hungerSystem.ModifyHunger(uid, -comp.HungerCost);
-
- var newEntity = Spawn(comp.EntityProduced, Transform(uid).Coordinates);
-
- _stackSystem.TryMergeToHands(newEntity, uid);
-
- args.Repeat = true;
- }
-
- private bool IsHungry(EntityUid uid, HungerComponent? comp = null)
- {
- if (!Resolve(uid, ref comp))
- return false;
-
- if (_hungerSystem.GetHungerThreshold(comp) <= HungerThreshold.Peckish)
- return true;
-
- return false;
- }
}
diff --git a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs
index 8baee3e379..9c09603510 100644
--- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs
+++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs
@@ -173,6 +173,17 @@ public sealed class HungerSystem : EntitySystem
return result;
}
+ ///
+ /// A check that returns if the entity is below a hunger threshold.
+ ///
+ public bool IsHungerBelowState(EntityUid uid, HungerThreshold threshold, float? food = null, HungerComponent? comp = null)
+ {
+ if (!Resolve(uid, ref comp))
+ return false; // It's never going to go hungry, so it's probably fine to assume that it's not... you know, hungry.
+
+ return GetHungerThreshold(comp, food) < threshold;
+ }
+
private bool GetMovementThreshold(HungerThreshold threshold)
{
switch (threshold)
diff --git a/Content.Shared/Sericulture/SericultureComponent.cs b/Content.Shared/Sericulture/SericultureComponent.cs
new file mode 100644
index 0000000000..23143f5ac4
--- /dev/null
+++ b/Content.Shared/Sericulture/SericultureComponent.cs
@@ -0,0 +1,66 @@
+using Content.Shared.Nutrition.Components;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Sericulture;
+
+///
+/// Should be applied to any mob that you want to be able to produce any material with an action and the cost of hunger.
+/// TODO: Probably adjust this to utilize organs?
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedSericultureSystem))]
+public sealed partial class SericultureComponent : Component
+{
+ ///
+ /// The text that pops up whenever sericulture fails for not having enough hunger.
+ ///
+ [DataField("popupText")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public string PopupText = "sericulture-failure-hunger";
+
+ ///
+ /// What will be produced at the end of the action.
+ ///
+ [DataField("entityProduced", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public string EntityProduced = string.Empty;
+
+ ///
+ /// The entity needed to actually preform sericulture. This will be granted (and removed) upon the entity's creation.
+ ///
+ [DataField("action", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public string Action = string.Empty;
+
+ [AutoNetworkedField]
+ [DataField("actionEntity")]
+ public EntityUid? ActionEntity;
+
+ ///
+ /// How long will it take to make.
+ ///
+ [DataField("productionLength")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public float ProductionLength = 3f;
+
+ ///
+ /// This will subtract (not add, don't get this mixed up) from the current hunger of the mob doing sericulture.
+ ///
+ [DataField("hungerCost")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public float HungerCost = 5f;
+
+ ///
+ /// The lowest hunger threshold that this mob can be in before it's allowed to spin silk.
+ ///
+ [DataField("minHungerThreshold")]
+ [ViewVariables(VVAccess.ReadWrite)]
+ [AutoNetworkedField]
+ public HungerThreshold MinHungerThreshold = HungerThreshold.Okay;
+}
diff --git a/Content.Shared/Sericulture/SericultureEvents.cs b/Content.Shared/Sericulture/SericultureEvents.cs
deleted file mode 100644
index 9a497d16f4..0000000000
--- a/Content.Shared/Sericulture/SericultureEvents.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.DoAfter;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Sericulture;
-
-[Serializable, NetSerializable]
-public sealed partial class SericultureDoAfterEvent : SimpleDoAfterEvent { }
-
-public sealed partial class SericultureActionEvent : InstantActionEvent { }
diff --git a/Content.Shared/Sericulture/SericultureSystem.cs b/Content.Shared/Sericulture/SericultureSystem.cs
new file mode 100644
index 0000000000..514ec79f68
--- /dev/null
+++ b/Content.Shared/Sericulture/SericultureSystem.cs
@@ -0,0 +1,109 @@
+using Content.Shared.Actions;
+using Content.Shared.DoAfter;
+using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Serialization;
+using Content.Shared.Popups;
+using Robust.Shared.Network;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Stacks;
+
+namespace Content.Shared.Sericulture;
+
+///
+/// Allows mobs to produce materials with .
+///
+public abstract partial class SharedSericultureSystem : EntitySystem
+{
+ // Managers
+ [Dependency] private readonly INetManager _netManager = default!;
+
+ // Systems
+ [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly HungerSystem _hungerSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+ [Dependency] private readonly SharedStackSystem _stackSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnCompRemove);
+ SubscribeLocalEvent(OnSericultureStart);
+ SubscribeLocalEvent(OnSericultureDoAfter);
+ }
+
+ ///
+ /// Giveths the action to preform sericulture on the entity
+ ///
+ private void OnMapInit(EntityUid uid, SericultureComponent comp, MapInitEvent args)
+ {
+ _actionsSystem.AddAction(uid, ref comp.ActionEntity, comp.Action);
+ }
+
+ ///
+ /// Takeths away the action to preform sericulture from the entity.
+ ///
+ private void OnCompRemove(EntityUid uid, SericultureComponent comp, ComponentShutdown args)
+ {
+ _actionsSystem.RemoveAction(uid, comp.ActionEntity);
+ }
+
+ private void OnSericultureStart(EntityUid uid, SericultureComponent comp, SericultureActionEvent args)
+ {
+ if (TryComp(uid, out var hungerComp)
+ && _hungerSystem.IsHungerBelowState(uid, comp.MinHungerThreshold, hungerComp.CurrentHunger - comp.HungerCost, hungerComp))
+ {
+ _popupSystem.PopupClient(Loc.GetString(comp.PopupText), uid, uid);
+ return;
+ }
+
+ var doAfter = new DoAfterArgs(EntityManager, uid, comp.ProductionLength, new SericultureDoAfterEvent(), uid)
+ { // I'm not sure if more things should be put here, but imo ideally it should probably be set in the component/YAML. Not sure if this is currently possible.
+ BreakOnUserMove = true,
+ BlockDuplicate = true,
+ BreakOnDamage = true,
+ CancelDuplicate = true,
+ };
+
+ _doAfterSystem.TryStartDoAfter(doAfter);
+ }
+
+
+ private void OnSericultureDoAfter(EntityUid uid, SericultureComponent comp, SericultureDoAfterEvent args)
+ {
+ if (args.Cancelled || args.Handled || comp.Deleted)
+ return;
+
+ if (TryComp(uid, out var hungerComp) // A check, just incase the doafter is somehow performed when the entity is not in the right hunger state.
+ && _hungerSystem.IsHungerBelowState(uid, comp.MinHungerThreshold, hungerComp.CurrentHunger - comp.HungerCost, hungerComp))
+ {
+ _popupSystem.PopupClient(Loc.GetString(comp.PopupText), uid, uid);
+ return;
+ }
+
+ _hungerSystem.ModifyHunger(uid, -comp.HungerCost);
+
+ if (!_netManager.IsClient) // Have to do this because spawning stuff in shared is CBT.
+ {
+ var newEntity = Spawn(comp.EntityProduced, Transform(uid).Coordinates);
+
+ _stackSystem.TryMergeToHands(newEntity, uid);
+ }
+
+ args.Repeat = true;
+ }
+}
+
+///
+/// Should be relayed upon using the action.
+///
+public sealed partial class SericultureActionEvent : InstantActionEvent { }
+
+///
+/// Is relayed at the end of the sericulturing doafter.
+///
+[Serializable, NetSerializable]
+public sealed partial class SericultureDoAfterEvent : SimpleDoAfterEvent { }
+