--- /dev/null
+using Content.Server.Animals.Systems;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+/// <summary>
+/// Lets an animal grow a wool solution when not hungry.
+/// </summary>
+[RegisterComponent, Access(typeof(WoolySystem))]
+public sealed partial class WoolyComponent : Component
+{
+ /// <summary>
+ /// What reagent to grow.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId<ReagentPrototype> ReagentId = "Fiber";
+
+ /// <summary>
+ /// How much wool to grow at every growth cycle.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public FixedPoint2 Quantity = 25;
+
+ /// <summary>
+ /// What solution to add the wool reagent to.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string Solution = "wool";
+
+ /// <summary>
+ /// How long to wait before growing wool.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1);
+
+ /// <summary>
+ /// When to next try growing wool.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextGrowth = TimeSpan.FromSeconds(0);
+}
--- /dev/null
+using Content.Server.Animals.Components;
+using Content.Server.Nutrition;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Nutrition.Components;
+using Content.Shared.Nutrition.EntitySystems;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Animals.Systems;
+
+/// <summary>
+/// Handles regeneration of an animal's wool solution when not hungry.
+/// Shearing is not currently possible so the only use is for moths to eat.
+/// </summary>
+public sealed class WoolySystem : EntitySystem
+{
+ [Dependency] private readonly HungerSystem _hunger = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<WoolyComponent, BeforeFullyEatenEvent>(OnBeforeFullyEaten);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator<WoolyComponent, HungerComponent>();
+ var now = _timing.CurTime;
+ while (query.MoveNext(out var uid, out var comp, out var hunger))
+ {
+ if (now < comp.NextGrowth)
+ continue;
+
+ comp.NextGrowth = now + comp.GrowthDelay;
+
+ // Is there enough nutrition to produce reagent?
+ if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Peckish)
+ continue;
+
+ if (!_solutionContainer.TryGetSolution(uid, comp.Solution, out var solution))
+ continue;
+
+ _solutionContainer.TryAddReagent(uid, solution, comp.ReagentId, comp.Quantity, out _);
+ }
+ }
+
+ private void OnBeforeFullyEaten(Entity<WoolyComponent> ent, ref BeforeFullyEatenEvent args)
+ {
+ // don't want moths to delete goats after eating them
+ args.Cancel();
+ }
+}
/// </summary>
[DataField]
public float ForceFeedDelay = 3;
+
+ /// <summary>
+ /// For mobs that are food, requires killing them before eating.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool RequireDead = true;
}
public (bool Success, bool Handled) TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp)
{
//Suppresses eating yourself and alive mobs
- if (food == user || _mobState.IsAlive(food))
+ if (food == user || (_mobState.IsAlive(food) && foodComp.RequireDead))
return (false, false);
// Target can't be fed or they're already eating
return;
}
+ // don't try to repeat if its being deleted
+ args.Repeat = false;
+ DeleteAndSpawnTrash(component, uid, args.User);
+ }
+
+ public void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityUid user)
+ {
var ev = new BeforeFullyEatenEvent
{
- User = args.User
+ User = user
};
- RaiseLocalEvent(uid, ev);
+ RaiseLocalEvent(food, ev);
if (ev.Cancelled)
return;
if (string.IsNullOrEmpty(component.Trash))
- QueueDel(uid);
- else
- DeleteAndSpawnTrash(component, uid, args.User);
- }
+ {
+ QueueDel(food);
+ return;
+ }
- public void DeleteAndSpawnTrash(FoodComponent component, EntityUid food, EntityUid? user = null)
- {
//We're empty. Become trash.
var position = Transform(food).MapPosition;
var finisher = Spawn(component.Trash, position);
// If the user is holding the item
- if (user != null && _hands.IsHolding(user.Value, food, out var hand))
+ if (_hands.IsHolding(user, food, out var hand))
{
Del(food);
// Put the trash in the user's hand
- _hands.TryPickup(user.Value, finisher, hand);
+ _hands.TryPickup(user, finisher, hand);
return;
}
return;
// have to kill mouse before eating it
- if (_mobState.IsAlive(uid))
+ if (_mobState.IsAlive(uid) && component.RequireDead)
return;
// only give moths eat verb for clothes since it would just fail otherwise
- MobLayer
- type: Tag
tags:
+ # let moths eat wool directly
+ - ClothMade
- DoorBumpOpener
- Goat
- type: Reproductive
reagents:
- ReagentId: MilkGoat
Quantity: 30
+ wool:
+ maxVol: 250
- type: Udder
reagentId: MilkGoat
targetSolution: udder
quantity: 25
updateRate: 20
+ - type: Wooly
+ - type: Food
+ solution: wool
+ requiresSpecialDigestion: true
+ # Wooly prevents eating wool deleting the goat so its fine
+ requireDead: false
- type: Butcherable
spawned:
- id: FoodMeat