Animals obey conservation of matter unless they are undead (#21922)
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Gives ability to produce eggs, produces endless if the
|
||||
/// owner has no HungerComponent
|
||||
/// </summary>
|
||||
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<HungerComponent>(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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Gives ability to produce milkable reagents, produces endless if the
|
||||
/// owner has no HungerComponent
|
||||
/// </summary>
|
||||
internal sealed class UdderSystem : EntitySystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gives ability to living beings with acceptable hunger level to produce milkable reagents.
|
||||
/// </summary>
|
||||
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<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
|
||||
SubscribeLocalEvent<UdderComponent, MilkingDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<UdderComponent>();
|
||||
var now = _timing.CurTime;
|
||||
while (query.MoveNext(out var uid, out var udder))
|
||||
{
|
||||
base.Initialize();
|
||||
if (now < udder.NextGrowth)
|
||||
continue;
|
||||
|
||||
SubscribeLocalEvent<UdderComponent, GetVerbsEvent<AlternativeVerb>>(AddMilkVerb);
|
||||
SubscribeLocalEvent<UdderComponent, MilkingDoAfterEvent>(OnDoAfter);
|
||||
}
|
||||
udder.NextGrowth = now + udder.GrowthDelay;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<UdderComponent>();
|
||||
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<AlternativeVerb> args)
|
||||
{
|
||||
if (args.Using == null ||
|
||||
!args.CanInteract ||
|
||||
!EntityManager.HasComponent<RefillableSolutionComponent>(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<UdderComponent?> 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<AlternativeVerb> args)
|
||||
{
|
||||
if (args.Using == null ||
|
||||
!args.CanInteract ||
|
||||
!EntityManager.HasComponent<RefillableSolutionComponent>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <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.
|
||||
/// Gives ability to produce fiber reagents, produces endless if the
|
||||
/// owner has no HungerComponent
|
||||
/// </summary>
|
||||
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<WoolyComponent, HungerComponent>();
|
||||
var query = EntityQueryEnumerator<WoolyComponent>();
|
||||
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 _);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user