diff --git a/Content.Server/Animals/Components/EggLayerComponent.cs b/Content.Server/Animals/Components/EggLayerComponent.cs index 33d14c8e36..a0f7de676e 100644 --- a/Content.Server/Animals/Components/EggLayerComponent.cs +++ b/Content.Server/Animals/Components/EggLayerComponent.cs @@ -1,7 +1,6 @@ using Content.Shared.Storage; using Robust.Shared.Audio; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Animals.Components; @@ -9,28 +8,29 @@ namespace Content.Server.Animals.Components; /// This component handles animals which lay eggs (or some other item) on a timer, using up hunger to do so. /// It also grants an action to players who are controlling these entities, allowing them to do it manually. /// + [RegisterComponent] public sealed partial class EggLayerComponent : Component { - [DataField("eggLayAction", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string EggLayAction = "ActionAnimalLayEgg"; + [DataField] + public EntProtoId EggLayAction = "ActionAnimalLayEgg"; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("hungerUsage")] + /// + /// The amount of nutrient consumed on update. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] public float HungerUsage = 60f; /// /// Minimum cooldown used for the automatic egg laying. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("eggLayCooldownMin")] + [DataField, ViewVariables(VVAccess.ReadWrite)] public float EggLayCooldownMin = 60f; /// /// Maximum cooldown used for the automatic egg laying. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("eggLayCooldownMax")] + [DataField, ViewVariables(VVAccess.ReadWrite)] public float EggLayCooldownMax = 120f; /// @@ -39,14 +39,13 @@ public sealed partial class EggLayerComponent : Component [ViewVariables(VVAccess.ReadWrite)] public float CurrentEggLayCooldown; - [ViewVariables(VVAccess.ReadWrite)] - [DataField("eggSpawn", required: true)] + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] public List EggSpawn = default!; - [DataField("eggLaySound")] + [DataField] public SoundSpecifier EggLaySound = new SoundPathSpecifier("/Audio/Effects/pop.ogg"); - [DataField("accumulatedFrametime")] + [DataField] public float AccumulatedFrametime; [DataField] public EntityUid? Action; diff --git a/Content.Server/Animals/Components/UdderComponent.cs b/Content.Server/Animals/Components/UdderComponent.cs index 1c71fcb7c5..3895a8ba24 100644 --- a/Content.Server/Animals/Components/UdderComponent.cs +++ b/Content.Server/Animals/Components/UdderComponent.cs @@ -1,9 +1,15 @@ using Content.Server.Animals.Systems; using Content.Shared.Chemistry.Reagent; using Content.Shared.FixedPoint; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + namespace Content.Server.Animals.Components + +/// +/// Lets an entity produce milk. Uses hunger if present. +/// { [RegisterComponent, Access(typeof(UdderSystem))] internal sealed partial class UdderComponent : Component @@ -11,31 +17,37 @@ namespace Content.Server.Animals.Components /// /// The reagent to produce. /// - [ViewVariables(VVAccess.ReadOnly)] - [DataField("reagentId", customTypeSerializer:typeof(PrototypeIdSerializer))] - public string ReagentId = "Milk"; + [DataField, ViewVariables(VVAccess.ReadOnly)] + public ProtoId ReagentId = "Milk"; /// /// The solution to add reagent to. /// - [ViewVariables(VVAccess.ReadOnly)] - [DataField("targetSolution")] - public string TargetSolutionName = "udder"; + [DataField, ViewVariables(VVAccess.ReadOnly)] + public string Solution = "udder"; /// /// The amount of reagent to be generated on update. /// - [ViewVariables(VVAccess.ReadOnly)] - [DataField("quantity")] - public FixedPoint2 QuantityPerUpdate = 1; + [DataField, ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 QuantityPerUpdate = 25; /// - /// The time between updates (in seconds). + /// The amount of nutrient consumed on update. /// - [ViewVariables(VVAccess.ReadOnly)] - [DataField("updateRate")] - public float UpdateRate = 5; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float HungerUsage = 10f; - public float AccumulatedFrameTime; + /// + /// How long to wait before producing. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1); + + /// + /// When to next try to produce. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] + public TimeSpan NextGrowth = TimeSpan.FromSeconds(0); } } diff --git a/Content.Server/Animals/Components/WoolyComponent.cs b/Content.Server/Animals/Components/WoolyComponent.cs index 8db44973e5..e700fd76f6 100644 --- a/Content.Server/Animals/Components/WoolyComponent.cs +++ b/Content.Server/Animals/Components/WoolyComponent.cs @@ -4,38 +4,47 @@ using Content.Shared.FixedPoint; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; +namespace Content.Server.Animals.Components; + /// -/// Lets an animal grow a wool solution when not hungry. +/// Lets an entity produce wool fibers. Uses hunger if present. /// + [RegisterComponent, Access(typeof(WoolySystem))] public sealed partial class WoolyComponent : Component { /// - /// What reagent to grow. + /// The reagent to grow. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadOnly)] public ProtoId ReagentId = "Fiber"; /// - /// How much wool to grow at every growth cycle. + /// The solution to add reagent to. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 Quantity = 25; - - /// - /// What solution to add the wool reagent to. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadOnly)] public string Solution = "wool"; /// - /// How long to wait before growing wool. + /// The amount of reagent to be generated on update. + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 Quantity = 25; + + /// + /// The amount of nutrient consumed on update. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float HungerUsage = 10f; + + /// + /// How long to wait before growing wool. /// [DataField, ViewVariables(VVAccess.ReadWrite)] public TimeSpan GrowthDelay = TimeSpan.FromMinutes(1); /// - /// When to next try growing wool. + /// When to next try growing wool. /// [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] public TimeSpan NextGrowth = TimeSpan.FromSeconds(0); diff --git a/Content.Server/Animals/Systems/EggLayerSystem.cs b/Content.Server/Animals/Systems/EggLayerSystem.cs index 35c3bab01d..55d63808a4 100644 --- a/Content.Server/Animals/Systems/EggLayerSystem.cs +++ b/Content.Server/Animals/Systems/EggLayerSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Actions; using Content.Server.Animals.Components; using Content.Server.Popups; using Content.Shared.Actions.Events; +using Content.Shared.Mobs.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Storage; @@ -12,6 +13,10 @@ using Robust.Shared.Random; namespace Content.Server.Animals.Systems; +/// +/// Gives ability to produce eggs, produces endless if the +/// owner has no HungerComponent +/// public sealed class EggLayerSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; @@ -19,6 +24,7 @@ public sealed class EggLayerSystem : EntitySystem [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly HungerSystem _hunger = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { @@ -57,35 +63,38 @@ public sealed class EggLayerSystem : EntitySystem component.CurrentEggLayCooldown = _random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax); } - private void OnEggLayAction(EntityUid uid, EggLayerComponent component, EggLayInstantActionEvent args) + private void OnEggLayAction(EntityUid uid, EggLayerComponent egglayer, EggLayInstantActionEvent args) { - args.Handled = TryLayEgg(uid, component); + args.Handled = TryLayEgg(uid, egglayer); } - public bool TryLayEgg(EntityUid uid, EggLayerComponent? component) + public bool TryLayEgg(EntityUid uid, EggLayerComponent? egglayer) { - if (!Resolve(uid, ref component)) + if (!Resolve(uid, ref egglayer)) + return false; + + if (_mobState.IsDead(uid)) return false; // Allow infinitely laying eggs if they can't get hungry if (TryComp(uid, out var hunger)) { - if (hunger.CurrentHunger < component.HungerUsage) + if (hunger.CurrentHunger < egglayer.HungerUsage) { _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-too-hungry"), uid, uid); return false; } - _hunger.ModifyHunger(uid, -component.HungerUsage, hunger); + _hunger.ModifyHunger(uid, -egglayer.HungerUsage, hunger); } - foreach (var ent in EntitySpawnCollection.GetSpawns(component.EggSpawn, _random)) + foreach (var ent in EntitySpawnCollection.GetSpawns(egglayer.EggSpawn, _random)) { Spawn(ent, Transform(uid).Coordinates); } // Sound + popups - _audio.PlayPvs(component.EggLaySound, uid); + _audio.PlayPvs(egglayer.EggLaySound, uid); _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-user"), uid, uid); _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-others", ("entity", uid)), uid, Filter.PvsExcept(uid), true); diff --git a/Content.Server/Animals/Systems/UdderSystem.cs b/Content.Server/Animals/Systems/UdderSystem.cs index 4164aedfd1..c07ba12dd5 100644 --- a/Content.Server/Animals/Systems/UdderSystem.cs +++ b/Content.Server/Animals/Systems/UdderSystem.cs @@ -4,124 +4,132 @@ using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; +using Content.Shared.Mobs.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Popups; using Content.Shared.Udder; using Content.Shared.Verbs; +using Robust.Shared.Timing; -namespace Content.Server.Animals.Systems +namespace Content.Server.Animals.Systems; + +/// +/// Gives ability to produce milkable reagents, produces endless if the +/// owner has no HungerComponent +/// +internal sealed class UdderSystem : EntitySystem { - /// - /// Gives ability to living beings with acceptable hunger level to produce milkable reagents. - /// - internal sealed class UdderSystem : EntitySystem + [Dependency] private readonly HungerSystem _hunger = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; + + public override void Initialize() { - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly HungerSystem _hunger = default!; - [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; - [Dependency] private readonly PopupSystem _popupSystem = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent>(AddMilkVerb); + SubscribeLocalEvent(OnDoAfter); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + var now = _timing.CurTime; + while (query.MoveNext(out var uid, out var udder)) { - base.Initialize(); + if (now < udder.NextGrowth) + continue; - SubscribeLocalEvent>(AddMilkVerb); - SubscribeLocalEvent(OnDoAfter); - } + udder.NextGrowth = now + udder.GrowthDelay; - public override void Update(float frameTime) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var udder)) + if (_mobState.IsDead(uid)) + continue; + + // Actually there is food digestion so no problem with instant reagent generation "OnFeed" + if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger)) { - udder.AccumulatedFrameTime += frameTime; + // Is there enough nutrition to produce reagent? + if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay) + continue; - while (udder.AccumulatedFrameTime > udder.UpdateRate) - { - udder.AccumulatedFrameTime -= udder.UpdateRate; - - // Actually there is food digestion so no problem with instant reagent generation "OnFeed" - if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger)) - { - // Is there enough nutrition to produce reagent? - if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Peckish) - continue; - } - - if (!_solutionContainerSystem.TryGetSolution(uid, udder.TargetSolutionName, - out var solution)) - continue; - - //TODO: toxins from bloodstream !? - _solutionContainerSystem.TryAddReagent(uid, solution, udder.ReagentId, - udder.QuantityPerUpdate, out var accepted); - } - } - } - - private void AttemptMilk(EntityUid uid, EntityUid userUid, EntityUid containerUid, UdderComponent? udder = null) - { - if (!Resolve(uid, ref udder)) - return; - - var doargs = new DoAfterArgs(EntityManager, userUid, 5, new MilkingDoAfterEvent(), uid, uid, used: containerUid) - { - BreakOnUserMove = true, - BreakOnDamage = true, - BreakOnTargetMove = true, - MovementThreshold = 1.0f, - }; - - _doAfterSystem.TryStartDoAfter(doargs); - } - - private void OnDoAfter(EntityUid uid, UdderComponent component, MilkingDoAfterEvent args) - { - if (args.Cancelled || args.Handled || args.Args.Used == null) - return; - - if (!_solutionContainerSystem.TryGetSolution(uid, component.TargetSolutionName, out var solution)) - return; - - if (!_solutionContainerSystem.TryGetRefillableSolution(args.Args.Used.Value, out var targetSolution)) - return; - - args.Handled = true; - var quantity = solution.Volume; - if(quantity == 0) - { - _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, args.Args.User); - return; + _hunger.ModifyHunger(uid, -udder.HungerUsage, hunger); } - if (quantity > targetSolution.AvailableVolume) - quantity = targetSolution.AvailableVolume; + if (!_solutionContainerSystem.TryGetSolution(uid, udder.Solution, out var solution)) + continue; - var split = _solutionContainerSystem.SplitSolution(uid, solution, quantity); - _solutionContainerSystem.TryAddSolution(args.Args.Used.Value, targetSolution, split); - - _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), uid, - args.Args.User, PopupType.Medium); - } - - private void AddMilkVerb(EntityUid uid, UdderComponent component, GetVerbsEvent args) - { - if (args.Using == null || - !args.CanInteract || - !EntityManager.HasComponent(args.Using.Value)) - return; - - AlternativeVerb verb = new() - { - Act = () => - { - AttemptMilk(uid, args.User, args.Using.Value, component); - }, - Text = Loc.GetString("udder-system-verb-milk"), - Priority = 2 - }; - args.Verbs.Add(verb); + //TODO: toxins from bloodstream !? + _solutionContainerSystem.TryAddReagent(uid, solution, udder.ReagentId, udder.QuantityPerUpdate, out _); } } + + private void AttemptMilk(Entity udder, EntityUid userUid, EntityUid containerUid) + { + if (!Resolve(udder, ref udder.Comp)) + return; + + var doargs = new DoAfterArgs(EntityManager, userUid, 5, new MilkingDoAfterEvent(), udder, udder, used: containerUid) + { + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnTargetMove = true, + MovementThreshold = 1.0f, + }; + + _doAfterSystem.TryStartDoAfter(doargs); + } + + private void OnDoAfter(EntityUid uid, UdderComponent component, MilkingDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Args.Used == null) + return; + + if (!_solutionContainerSystem.TryGetSolution(uid, component.Solution, out var solution)) + return; + + if (!_solutionContainerSystem.TryGetRefillableSolution(args.Args.Used.Value, out var targetSolution)) + return; + + args.Handled = true; + var quantity = solution.Volume; + if (quantity == 0) + { + _popupSystem.PopupEntity(Loc.GetString("udder-system-dry"), uid, args.Args.User); + return; + } + + if (quantity > targetSolution.AvailableVolume) + quantity = targetSolution.AvailableVolume; + + var split = _solutionContainerSystem.SplitSolution(uid, solution, quantity); + _solutionContainerSystem.TryAddSolution(args.Args.Used.Value, targetSolution, split); + + _popupSystem.PopupEntity(Loc.GetString("udder-system-success", ("amount", quantity), ("target", Identity.Entity(args.Args.Used.Value, EntityManager))), uid, + args.Args.User, PopupType.Medium); + } + + private void AddMilkVerb(EntityUid uid, UdderComponent component, GetVerbsEvent args) + { + if (args.Using == null || + !args.CanInteract || + !EntityManager.HasComponent(args.Using.Value)) + return; + + AlternativeVerb verb = new() + { + Act = () => + { + AttemptMilk(uid, args.User, args.Using.Value); + }, + Text = Loc.GetString("udder-system-verb-milk"), + Priority = 2 + }; + args.Verbs.Add(verb); + } } diff --git a/Content.Server/Animals/Systems/WoolySystem.cs b/Content.Server/Animals/Systems/WoolySystem.cs index e63718c395..6cf8d8c88f 100644 --- a/Content.Server/Animals/Systems/WoolySystem.cs +++ b/Content.Server/Animals/Systems/WoolySystem.cs @@ -1,6 +1,7 @@ using Content.Server.Animals.Components; using Content.Server.Nutrition; using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Mobs.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; using Robust.Shared.Timing; @@ -8,13 +9,14 @@ using Robust.Shared.Timing; namespace Content.Server.Animals.Systems; /// -/// 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. +/// Gives ability to produce fiber reagents, produces endless if the +/// owner has no HungerComponent /// public sealed class WoolySystem : EntitySystem { [Dependency] private readonly HungerSystem _hunger = default!; [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; public override void Initialize() @@ -28,23 +30,32 @@ public sealed class WoolySystem : EntitySystem { base.Update(frameTime); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); var now = _timing.CurTime; - while (query.MoveNext(out var uid, out var comp, out var hunger)) + while (query.MoveNext(out var uid, out var wooly)) { - if (now < comp.NextGrowth) + if (now < wooly.NextGrowth) continue; - comp.NextGrowth = now + comp.GrowthDelay; + wooly.NextGrowth = now + wooly.GrowthDelay; - // Is there enough nutrition to produce reagent? - if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Peckish) + if (_mobState.IsDead(uid)) continue; - if (!_solutionContainer.TryGetSolution(uid, comp.Solution, out var solution)) + // Actually there is food digestion so no problem with instant reagent generation "OnFeed" + if (EntityManager.TryGetComponent(uid, out HungerComponent? hunger)) + { + // Is there enough nutrition to produce reagent? + if (_hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay) + continue; + + _hunger.ModifyHunger(uid, -wooly.HungerUsage, hunger); + } + + if (!_solutionContainer.TryGetSolution(uid, wooly.Solution, out var solution)) continue; - _solutionContainer.TryAddReagent(uid, solution, comp.ReagentId, comp.Quantity, out _); + _solutionContainer.TryAddReagent(uid, solution, wooly.ReagentId, wooly.Quantity, out _); } } diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 77cf121102..cac516407b 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -25,6 +25,7 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Systems; +using Content.Shared.Nutrition.AnimalHusbandry; using Content.Shared.Nutrition.Components; using Content.Shared.Popups; using Content.Shared.Roles; @@ -96,11 +97,13 @@ namespace Content.Server.Zombies var zombiecomp = AddComp(target); //we need to basically remove all of these because zombies shouldn't - //get diseases, breath, be thirst, be hungry, or die in space + //get diseases, breath, be thirst, be hungry, die in space or have offspring RemComp(target); RemComp(target); RemComp(target); RemComp(target); + RemComp(target); + RemComp(target); //funny voice var accentType = "zombie";