diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.Footprints.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.Footprints.cs
new file mode 100644
index 00000000000..50b5b7a6601
--- /dev/null
+++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.Footprints.cs
@@ -0,0 +1,41 @@
+using System.Linq;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Fluids;
+using Content.Shared.FootPrint;
+
+namespace Content.Server.Fluids.EntitySystems;
+
+public sealed partial class AbsorbentSystem
+{
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
+
+ ///
+ /// Tries to clean a number of footprints in a range determined by the component. Returns the number of cleaned footprints.
+ ///
+ private int TryCleanNearbyFootprints(EntityUid user, EntityUid used, Entity target, Entity absorbentSoln)
+ {
+ var footprintQuery = GetEntityQuery();
+ var targetCoords = Transform(target).Coordinates;
+ var entities = _lookup.GetEntitiesInRange(targetCoords, target.Comp.FootprintCleaningRange, LookupFlags.Uncontained);
+
+ // Take up to [MaxCleanedFootprints] footprints closest to the target
+ var cleaned = entities.AsEnumerable()
+ .Select(uid => (uid, dst: Transform(uid).Coordinates.TryDistance(EntityManager, _transform, targetCoords, out var dst) ? dst : 0f))
+ .Where(ent => ent.dst > 0f)
+ .OrderBy(ent => ent.dst)
+ .Select(ent => (ent.uid, comp: footprintQuery.GetComponent(ent.uid)));
+
+ // And try to interact with each one of them, ignoring useDelay
+ var processed = 0;
+ foreach (var (uid, footprintComp) in cleaned)
+ {
+ if (TryPuddleInteract(user, used, uid, target.Comp, useDelay: null, absorbentSoln))
+ processed++;
+
+ if (processed >= target.Comp.MaxCleanedFootprints)
+ break;
+ }
+
+ return processed;
+ }
+}
diff --git a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
index 52afdcf8b49..1f8c44a2409 100644
--- a/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
+++ b/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
@@ -1,5 +1,4 @@
using System.Numerics;
-using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
@@ -18,7 +17,7 @@
namespace Content.Server.Fluids.EntitySystems;
///
-public sealed class AbsorbentSystem : SharedAbsorbentSystem
+public sealed partial class AbsorbentSystem : SharedAbsorbentSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly AudioSystem _audio = default!;
@@ -26,7 +25,7 @@ public sealed class AbsorbentSystem : SharedAbsorbentSystem
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
[Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
+ [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
@@ -119,6 +118,8 @@ public void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentCompo
if (!TryRefillableInteract(user, used, target, component, useDelay, absorberSoln.Value))
return;
}
+
+ TryCleanNearbyFootprints(user, used, (target, component), absorberSoln.Value);
}
///
diff --git a/Content.Server/FootPrint/FootPrintsSystem.cs b/Content.Server/FootPrint/FootPrintsSystem.cs
index 0e45acff5cc..524fcd1cec0 100644
--- a/Content.Server/FootPrint/FootPrintsSystem.cs
+++ b/Content.Server/FootPrint/FootPrintsSystem.cs
@@ -1,39 +1,39 @@
+using System.Linq;
using Content.Server.Atmos.Components;
using Content.Shared.Inventory;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
using Content.Shared.FootPrint;
using Content.Shared.Standing;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.Forensics;
using Robust.Shared.Map;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.FootPrint;
+
public sealed class FootPrintsSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly IMapManager _map = default!;
+ [Dependency] private readonly IPrototypeManager _protoMan = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
- private EntityQuery _transformQuery;
- private EntityQuery _mobThresholdQuery;
private EntityQuery _appearanceQuery;
- private EntityQuery _layingQuery;
+ private EntityQuery _standingStateQuery;
public override void Initialize()
{
base.Initialize();
- _transformQuery = GetEntityQuery();
- _mobThresholdQuery = GetEntityQuery();
_appearanceQuery = GetEntityQuery();
- _layingQuery = GetEntityQuery();
+ _standingStateQuery = GetEntityQuery();
SubscribeLocalEvent(OnStartupComponent);
SubscribeLocalEvent(OnMove);
@@ -46,62 +46,72 @@ private void OnStartupComponent(EntityUid uid, FootPrintsComponent component, Co
private void OnMove(EntityUid uid, FootPrintsComponent component, ref MoveEvent args)
{
- if (component.PrintsColor.A <= 0f
- || !_transformQuery.TryComp(uid, out var transform)
- || !_mobThresholdQuery.TryComp(uid, out var mobThreshHolds)
- || !_map.TryFindGridAt(_transform.GetMapCoordinates((uid, transform)), out var gridUid, out _))
+ if (component.ContainedSolution.Volume <= 0
+ || TryComp(uid, out var physics) && physics.BodyStatus != BodyStatus.OnGround
+ || args.Entity.Comp1.GridUid is not {} gridUid)
return;
- var dragging = mobThreshHolds.CurrentThresholdState is MobState.Critical or MobState.Dead
- || _layingQuery.TryComp(uid, out var laying) && laying.IsCrawlingUnder;
- var distance = (transform.LocalPosition - component.StepPos).Length();
+ var newPos = _transform.ToMapCoordinates(args.NewPosition).Position;
+ var dragging = _standingStateQuery.TryComp(uid, out var standing) && standing.CurrentState == StandingState.Lying;
+ var distance = (newPos - component.LastStepPos).Length();
var stepSize = dragging ? component.DragSize : component.StepSize;
- if (!(distance > stepSize))
+ if (distance < stepSize)
return;
- component.RightStep = !component.RightStep;
+ // are we on a puddle? we exit, ideally we would exchange liquid and DNA with the puddle but meh, too lazy to do that now.
+ var entities = _lookup.GetEntitiesIntersecting(uid, LookupFlags.All);
+ if (entities.Any(HasComp))
+ return;
+
+ // Spawn the footprint
+ var footprintUid = Spawn(component.StepProtoId, CalcCoords(gridUid, component, args.Component, dragging));
+ var stepTransform = Transform(footprintUid);
+ var footPrintComponent = EnsureComp(footprintUid);
- var entity = Spawn(component.StepProtoId, CalcCoords(gridUid, component, transform, dragging));
- var footPrintComponent = EnsureComp(entity);
+ // transfer owner DNA into the footsteps
+ var forensics = EntityManager.EnsureComponent(footprintUid);
+ if (TryComp(uid, out var ownerForensics))
+ forensics.DNAs.UnionWith(ownerForensics.DNAs);
footPrintComponent.PrintOwner = uid;
- Dirty(entity, footPrintComponent);
+ Dirty(footprintUid, footPrintComponent);
- if (_appearanceQuery.TryComp(entity, out var appearance))
+ if (_appearanceQuery.TryComp(footprintUid, out var appearance))
{
- _appearance.SetData(entity, FootPrintVisualState.State, PickState(uid, dragging), appearance);
- _appearance.SetData(entity, FootPrintVisualState.Color, component.PrintsColor, appearance);
- }
+ var color = component.ContainedSolution.GetColor(_protoMan);
+ color.A = Math.Max(0.3f, component.ContainedSolution.FillFraction);
- if (!_transformQuery.TryComp(entity, out var stepTransform))
- return;
+ _appearance.SetData(footprintUid, FootPrintVisualState.State, PickState(uid, dragging), appearance);
+ _appearance.SetData(footprintUid, FootPrintVisualState.Color, color, appearance);
+ }
stepTransform.LocalRotation = dragging
- ? (transform.LocalPosition - component.StepPos).ToAngle() + Angle.FromDegrees(-90f)
- : transform.LocalRotation + Angle.FromDegrees(180f);
+ ? (newPos - component.LastStepPos).ToAngle() + Angle.FromDegrees(-90f)
+ : args.Component.LocalRotation + Angle.FromDegrees(180f);
- component.PrintsColor = component.PrintsColor.WithAlpha(Math.Max(0f, component.PrintsColor.A - component.ColorReduceAlpha));
- component.StepPos = transform.LocalPosition;
-
- if (!TryComp(entity, out var solutionContainer)
- || !_solution.ResolveSolution((entity, solutionContainer), footPrintComponent.SolutionName, ref footPrintComponent.Solution, out var solution)
- || string.IsNullOrWhiteSpace(component.ReagentToTransfer) || solution.Volume >= 1)
+ if (!TryComp(footprintUid, out var solutionContainer)
+ || !_solution.ResolveSolution((footprintUid, solutionContainer), footPrintComponent.SolutionName, ref footPrintComponent.Solution, out var solution))
return;
- _solution.TryAddReagent(footPrintComponent.Solution.Value, component.ReagentToTransfer, 1, out _);
+ // Transfer from the component to the footprint
+ var removedReagents = component.ContainedSolution.SplitSolution(component.FootprintVolume);
+ _solution.ForceAddSolution(footPrintComponent.Solution.Value, removedReagents);
+
+ component.RightStep = !component.RightStep;
+ component.LastStepPos = newPos;
}
private EntityCoordinates CalcCoords(EntityUid uid, FootPrintsComponent component, TransformComponent transform, bool state)
{
if (state)
- return new EntityCoordinates(uid, transform.LocalPosition);
+ return new(uid, transform.LocalPosition);
var offset = component.RightStep
? new Angle(Angle.FromDegrees(180f) + transform.LocalRotation).RotateVec(component.OffsetPrint)
: new Angle(transform.LocalRotation).RotateVec(component.OffsetPrint);
- return new EntityCoordinates(uid, transform.LocalPosition + offset);
+ return new(uid, transform.LocalPosition + offset);
}
private FootPrintVisuals PickState(EntityUid uid, bool dragging)
diff --git a/Content.Server/FootPrint/PuddleFootPrintsSystem.cs b/Content.Server/FootPrint/PuddleFootPrintsSystem.cs
index 706ba25359d..a778bcf2c7d 100644
--- a/Content.Server/FootPrint/PuddleFootPrintsSystem.cs
+++ b/Content.Server/FootPrint/PuddleFootPrintsSystem.cs
@@ -1,16 +1,17 @@
-using System.Linq;
using Content.Shared.FootPrint;
-using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
-using Content.Shared.Fluids;
+using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
+using Content.Shared.Forensics;
using Robust.Shared.Physics.Events;
+using Robust.Shared.Prototypes;
+
namespace Content.Server.FootPrint;
public sealed class PuddleFootPrintsSystem : EntitySystem
{
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
public override void Initialize()
@@ -21,32 +22,27 @@ public override void Initialize()
private void OnStepTrigger(EntityUid uid, PuddleFootPrintsComponent component, ref EndCollideEvent args)
{
- if (!TryComp(uid, out var appearance)
- || !TryComp(uid, out var puddle)
- || !TryComp(args.OtherEntity, out var tripper)
- || !TryComp(uid, out var solutionManager)
- || !_solutionContainer.ResolveSolution((uid, solutionManager), puddle.SolutionName, ref puddle.Solution, out var solutions))
- return;
-
- var totalSolutionQuantity = solutions.Contents.Sum(sol => (float) sol.Quantity);
- var waterQuantity = (from sol in solutions.Contents where sol.Reagent.Prototype == "Water" select (float) sol.Quantity).FirstOrDefault();
-
- if (waterQuantity / (totalSolutionQuantity / 100f) > component.OffPercent || solutions.Contents.Count <= 0)
+ if (!TryComp(uid, out var puddle) || !TryComp(args.OtherEntity, out var tripper))
return;
- tripper.ReagentToTransfer =
- solutions.Contents.Aggregate((l, r) => l.Quantity > r.Quantity ? l : r).Reagent.Prototype;
+ // Transfer DNAs from the puddle to the tripper
+ if (TryComp(uid, out var puddleForensics))
+ {
+ tripper.DNAs.UnionWith(puddleForensics.DNAs);
+ if(TryComp(args.OtherEntity, out var tripperForensics))
+ tripperForensics.DNAs.UnionWith(puddleForensics.DNAs);
+ }
- if (_appearance.TryGetData(uid, PuddleVisuals.SolutionColor, out var color, appearance)
- && _appearance.TryGetData(uid, PuddleVisuals.CurrentVolume, out var volume, appearance))
- AddColor((Color) color, (float) volume * component.SizeRatio, tripper);
+ // Transfer reagents from the puddle to the tripper.
+ // Ideally it should be a two-way process, but that is too hard to simulate and will have very little effect outside of potassium-water spills.
+ var quantity = puddle.Solution?.Comp?.Solution?.Volume ?? 0;
+ var footprintsCapacity = tripper.ContainedSolution.AvailableVolume;
- _solutionContainer.RemoveEachReagent(puddle.Solution.Value, 1);
- }
+ if (quantity <= 0 || footprintsCapacity <= 0)
+ return;
- private void AddColor(Color col, float quantity, FootPrintsComponent component)
- {
- component.PrintsColor = component.ColorQuantity == 0f ? col : Color.InterpolateBetween(component.PrintsColor, col, component.ColorInterpolationFactor);
- component.ColorQuantity += quantity;
+ var transferAmount = FixedPoint2.Min(footprintsCapacity, quantity * component.SizeRatio);
+ var transferred = _solutionContainer.SplitSolution(puddle.Solution!.Value, transferAmount);
+ tripper.ContainedSolution.AddSolution(transferred, _protoMan);
}
}
diff --git a/Content.Shared/Fluids/AbsorbentComponent.cs b/Content.Shared/Fluids/AbsorbentComponent.cs
index 450ecc0df6d..6cda88a72d6 100644
--- a/Content.Shared/Fluids/AbsorbentComponent.cs
+++ b/Content.Shared/Fluids/AbsorbentComponent.cs
@@ -38,4 +38,13 @@ public sealed partial class AbsorbentComponent : Component
{
Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(-3f),
};
+
+ [DataField]
+ public float FootprintCleaningRange = 0.2f;
+
+ ///
+ /// How many footprints within can be cleaned at once.
+ ///
+ [DataField]
+ public int MaxCleanedFootprints = 5;
}
diff --git a/Content.Shared/Footprint/FootPrintsComponent.cs b/Content.Shared/Footprint/FootPrintsComponent.cs
index 2b2c4ed66ed..99aa2106cf6 100644
--- a/Content.Shared/Footprint/FootPrintsComponent.cs
+++ b/Content.Shared/Footprint/FootPrintsComponent.cs
@@ -1,4 +1,6 @@
using System.Numerics;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -7,23 +9,17 @@ namespace Content.Shared.FootPrint;
[RegisterComponent]
public sealed partial class FootPrintsComponent : Component
{
- [ViewVariables(VVAccess.ReadOnly), DataField]
+ [DataField]
public ResPath RsiPath = new("/Textures/Effects/footprints.rsi");
- // all of those are set as a layer
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public string LeftBarePrint = "footprint-left-bare-human";
-
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public string RightBarePrint = "footprint-right-bare-human";
-
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public string ShoesPrint = "footprint-shoes";
-
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public string SuitPrint = "footprint-suit";
+ [DataField]
+ public string
+ LeftBarePrint = "footprint-left-bare-human",
+ RightBarePrint = "footprint-right-bare-human",
+ ShoesPrint = "footprint-shoes",
+ SuitPrint = "footprint-suit";
- [ViewVariables(VVAccess.ReadOnly), DataField]
+ [DataField]
public string[] DraggingPrint =
[
"dragging-1",
@@ -32,14 +28,10 @@ public sealed partial class FootPrintsComponent : Component
"dragging-4",
"dragging-5",
];
- // yea, those
- [ViewVariables(VVAccess.ReadOnly), DataField]
+ [DataField]
public EntProtoId StepProtoId = "Footstep";
- [ViewVariables(VVAccess.ReadOnly), DataField]
- public Color PrintsColor = Color.FromHex("#00000000");
-
///
/// The size scaling factor for footprint steps. Must be positive.
///
@@ -53,20 +45,8 @@ public sealed partial class FootPrintsComponent : Component
public float DragSize = 0.5f;
///
- /// The amount of color to transfer from the source (e.g., puddle) to the footprint.
- ///
- [DataField]
- public float ColorQuantity;
-
- ///
- /// The factor by which the alpha channel is reduced in subsequent footprints.
+ /// Horizontal offset of the created footprints relative to the center.
///
- [DataField]
- public float ColorReduceAlpha = 0.1f;
-
- [DataField]
- public string? ReagentToTransfer;
-
[DataField]
public Vector2 OffsetPrint = new(0.1f, 0f);
@@ -78,11 +58,20 @@ public sealed partial class FootPrintsComponent : Component
///
/// The position of the last footprint in world coordinates.
///
- public Vector2 StepPos = Vector2.Zero;
+ public Vector2 LastStepPos = Vector2.Zero;
+
+ [DataField]
+ public HashSet DNAs = new();
///
- /// Controls how quickly the footprint color transitions between steps.
- /// Value between 0 and 1, where higher values mean faster color changes.
+ /// Reagent volume used for footprints.
///
- public float ColorInterpolationFactor = 0.2f;
+ [DataField]
+ public Solution ContainedSolution = new(3) { CanReact = true, MaxVolume = 5f, };
+
+ ///
+ /// Amount of reagents used per footprint.
+ ///
+ [DataField]
+ public FixedPoint2 FootprintVolume = 1f;
}
diff --git a/Content.Shared/Footprint/PuddleFootPrintsComponent.cs b/Content.Shared/Footprint/PuddleFootPrintsComponent.cs
index 0e2ddfe3836..bea2b6b7a15 100644
--- a/Content.Shared/Footprint/PuddleFootPrintsComponent.cs
+++ b/Content.Shared/Footprint/PuddleFootPrintsComponent.cs
@@ -1,11 +1,14 @@
+using Content.Shared.FixedPoint;
+
+
namespace Content.Shared.FootPrint;
[RegisterComponent]
public sealed partial class PuddleFootPrintsComponent : Component
{
+ ///
+ /// Ratio between puddle volume and the amount of reagents that can be transferred from it.
+ ///
[ViewVariables(VVAccess.ReadWrite)]
- public float SizeRatio = 0.2f;
-
- [ViewVariables(VVAccess.ReadWrite)]
- public float OffPercent = 80f;
+ public FixedPoint2 SizeRatio = 0.15f;
}
diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml
index 7f6125e73d3..988b06de226 100644
--- a/Resources/Prototypes/Entities/Effects/puddle.yml
+++ b/Resources/Prototypes/Entities/Effects/puddle.yml
@@ -205,3 +205,13 @@
- type: Puddle
solution: step
- type: Appearance
+ - type: Drink
+ delay: 3
+ transferAmount: 1
+ solution: step
+ examinable: false
+ - type: ExaminableSolution
+ solution: step
+ - type: DrawableSolution
+ solution: step
+ - type: BadDrink
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml
index cb556c7d4d9..468acc38517 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml
@@ -37,6 +37,8 @@
size: Large
sprite: Objects/Specific/Janitorial/mop.rsi
- type: Absorbent
+ footprintCleaningRange: 0.45
+ maxCleanedFootprints: 25
- type: SolutionContainerManager
solutions:
absorbed:
@@ -94,6 +96,8 @@
sprite: Objects/Specific/Janitorial/advmop.rsi
- type: Absorbent
pickupAmount: 100
+ footprintCleaningRange: 0.75
+ maxCleanedFootprints: 25
- type: UseDelay
delay: 1.0
- type: SolutionRegeneration