id: {AbsorbentDummyId}
components:
- type: Absorbent
+ useAbsorberSolution: true
- type: SolutionContainerManager
solutions:
absorbed:
refillable = entityManager.SpawnEntity(RefillableDummyId, coordinates);
entityManager.TryGetComponent(absorbent, out component);
- solutionContainerSystem.TryGetSolution(absorbent, AbsorbentComponent.SolutionName, out var absorbentSoln, out var absorbentSolution);
+ solutionContainerSystem.TryGetSolution(absorbent, component.SolutionName, out var absorbentSoln, out var absorbentSolution);
solutionContainerSystem.TryGetRefillableSolution(refillable, out var refillableSoln, out var refillableSolution);
// Arrange
refillable = entityManager.SpawnEntity(SmallRefillableDummyId, coordinates);
entityManager.TryGetComponent(absorbent, out component);
- solutionContainerSystem.TryGetSolution(absorbent, AbsorbentComponent.SolutionName, out var absorbentSoln, out var absorbentSolution);
+ solutionContainerSystem.TryGetSolution(absorbent, component.SolutionName, out var absorbentSoln, out var absorbentSolution);
solutionContainerSystem.TryGetRefillableSolution(refillable, out var refillableSoln, out var refillableSolution);
// Arrange
// Is the target a mob? If yes, use a do-after to give them time to respond.
if (HasComp<MobStateComponent>(target) || HasComp<BloodstreamComponent>(target))
{
- // Are use using an injector capible of targeting a mob?
+ // Are use using an injector capable of targeting a mob?
if (entity.Comp.IgnoreMobs)
return;
/// <inheritdoc/>
public sealed class AbsorbentSystem : SharedAbsorbentSystem
{
+ private static readonly EntProtoId Sparkles = "PuddleSparkle";
+
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly PopupSystem _popups = default!;
private void UpdateAbsorbent(EntityUid uid, AbsorbentComponent component)
{
- if (!_solutionContainerSystem.TryGetSolution(uid, AbsorbentComponent.SolutionName, out _, out var solution))
+ if (!_solutionContainerSystem.TryGetSolution(uid, component.SolutionName, out _, out var solution))
return;
var oldProgress = component.Progress.ShallowClone();
public void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentComponent component)
{
- if (!_solutionContainerSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorberSoln))
+ if (!_solutionContainerSystem.TryGetSolution(used, component.SolutionName, out var absorberSoln))
return;
if (TryComp<UseDelayComponent>(used, out var useDelay)
return;
// If it's a puddle try to grab from
- if (!TryPuddleInteract(user, used, target, component, useDelay, absorberSoln.Value))
+ if (!TryPuddleInteract(user, used, target, component, useDelay, absorberSoln.Value) && component.UseAbsorberSolution)
{
// If it's refillable try to transfer
if (!TryRefillableInteract(user, used, target, component, useDelay, absorberSoln.Value))
return true;
}
- // Check if we have any evaporative reagents on our absorber to transfer
- var absorberSolution = absorberSoln.Comp.Solution;
- var available = absorberSolution.GetTotalPrototypeQuantity(_puddleSystem.GetAbsorbentReagents(absorberSolution));
-
- // No material
- if (available == FixedPoint2.Zero)
+ Solution puddleSplit;
+ var isRemoved = false;
+ if (absorber.UseAbsorberSolution)
{
- _popups.PopupEntity(Loc.GetString("mopping-system-no-water", ("used", used)), user, user);
- return true;
- }
+ // Check if we have any evaporative reagents on our absorber to transfer
+ var absorberSolution = absorberSoln.Comp.Solution;
+ var available = absorberSolution.GetTotalPrototypeQuantity(_puddleSystem.GetAbsorbentReagents(absorberSolution));
+
+ // No material
+ if (available == FixedPoint2.Zero)
+ {
+ _popups.PopupEntity(Loc.GetString("mopping-system-no-water", ("used", used)), user, user);
+ return true;
+ }
- var transferMax = absorber.PickupAmount;
- var transferAmount = available > transferMax ? transferMax : available;
+ var transferMax = absorber.PickupAmount;
+ var transferAmount = available > transferMax ? transferMax : available;
- var puddleSplit = puddleSolution.SplitSolutionWithout(transferAmount, _puddleSystem.GetAbsorbentReagents(puddleSolution));
- var absorberSplit = absorberSolution.SplitSolutionWithOnly(puddleSplit.Volume, _puddleSystem.GetAbsorbentReagents(absorberSolution));
+ puddleSplit = puddleSolution.SplitSolutionWithout(transferAmount, _puddleSystem.GetAbsorbentReagents(puddleSolution));
+ var absorberSplit = absorberSolution.SplitSolutionWithOnly(puddleSplit.Volume, _puddleSystem.GetAbsorbentReagents(absorberSolution));
- // Do tile reactions first
- var transform = Transform(target);
- var gridUid = transform.GridUid;
- if (TryComp(gridUid, out MapGridComponent? mapGrid))
+ // Do tile reactions first
+ var transform = Transform(target);
+ var gridUid = transform.GridUid;
+ if (TryComp(gridUid, out MapGridComponent? mapGrid))
+ {
+ var tileRef = _mapSystem.GetTileRef(gridUid.Value, mapGrid, transform.Coordinates);
+ _puddleSystem.DoTileReactions(tileRef, absorberSplit);
+ }
+ _solutionContainerSystem.AddSolution(puddle.Solution.Value, absorberSplit);
+ }
+ else
{
- var tileRef = _mapSystem.GetTileRef(gridUid.Value, mapGrid, transform.Coordinates);
- _puddleSystem.DoTileReactions(tileRef, absorberSplit);
+ puddleSplit = puddleSolution.SplitSolutionWithout(absorber.PickupAmount, _puddleSystem.GetAbsorbentReagents(puddleSolution));
+ // Despawn if we're done
+ if (puddleSolution.Volume == FixedPoint2.Zero)
+ {
+ // Spawn a *sparkle*
+ Spawn(Sparkles, GetEntityQuery<TransformComponent>().GetComponent(target).Coordinates);
+ QueueDel(target);
+ isRemoved = true;
+ }
}
- _solutionContainerSystem.AddSolution(puddle.Solution.Value, absorberSplit);
_solutionContainerSystem.AddSolution(absorberSoln, puddleSplit);
- _audio.PlayPvs(absorber.PickupSound, target);
+ _audio.PlayPvs(absorber.PickupSound, isRemoved ? used : target);
if (useDelay != null)
_useDelay.TryResetDelay((used, useDelay));
--- /dev/null
+using Content.Shared.Containers;
+using Content.Shared.Inventory;
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Chemistry.Components;
+
+/// <summary>
+/// Component for marking linked container in character slot, to which entity is bound.
+/// </summary>
+[RegisterComponent, Access(typeof(SlotBasedConnectedContainerSystem)), NetworkedComponent]
+public sealed partial class SlotBasedConnectedContainerComponent : Component
+{
+ /// <summary>
+ /// The slot in which target container should be.
+ /// </summary>
+ [DataField(required: true)]
+ public SlotFlags TargetSlot;
+
+ /// <summary>
+ /// A whitelist for determining whether container is valid or not .
+ /// </summary>
+ [DataField]
+ public EntityWhitelist? ContainerWhitelist;
+}
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
+using Content.Shared.Containers;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Robust.Shared.Map;
[NotNullWhen(true)] out Entity<SolutionComponent>? entity,
bool errorOnMissing = false)
{
+ // use connected container instead of entity from arguments, if it exists.
+ var ev = new GetConnectedContainerEvent();
+ RaiseLocalEvent(container, ref ev);
+ if (ev.ContainerEntity.HasValue)
+ container = ev.ContainerEntity.Value;
+
EntityUid uid;
if (name is null)
uid = container;
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Containers;
+
+/// <summary>
+/// System for getting container that is linked to subject entity. Container is supposed to be present in certain character slot.
+/// Can be used for linking ammo feeder, solution source for spray nozzle, etc.
+/// </summary>
+public sealed class SlotBasedConnectedContainerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedContainerSystem _containers = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
+
+ /// <inheritdoc />
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<SlotBasedConnectedContainerComponent, GetConnectedContainerEvent>(OnGettingConnectedContainer);
+ }
+
+ /// <summary>
+ /// Try get connected container entity in character slots for <see cref="uid"/>.
+ /// </summary>
+ /// <param name="uid">
+ /// Entity for which connected container is required. If <see cref="SlotBasedConnectedContainerComponent"/>
+ /// is used - tries to find container in slot, returns false and null <see cref="slotEntity"/> otherwise.
+ /// </param>
+ /// <param name="slotEntity">Found connected container entity or null.</param>
+ /// <returns>True if connected container was found, false otherwise.</returns>
+ public bool TryGetConnectedContainer(EntityUid uid, [NotNullWhen(true)] out EntityUid? slotEntity)
+ {
+ if (!TryComp<SlotBasedConnectedContainerComponent>(uid, out var component))
+ {
+ slotEntity = null;
+ return false;
+ }
+
+ return TryGetConnectedContainer(uid, component.TargetSlot, component.ContainerWhitelist, out slotEntity);
+ }
+
+ private void OnGettingConnectedContainer(Entity<SlotBasedConnectedContainerComponent> ent, ref GetConnectedContainerEvent args)
+ {
+ if (TryGetConnectedContainer(ent, ent.Comp.TargetSlot, ent.Comp.ContainerWhitelist, out var val))
+ args.ContainerEntity = val;
+ }
+
+ private bool TryGetConnectedContainer(EntityUid uid, SlotFlags slotFlag, EntityWhitelist? providerWhitelist, [NotNullWhen(true)] out EntityUid? slotEntity)
+ {
+ slotEntity = null;
+
+ if (!_containers.TryGetContainingContainer((uid, null, null), out var container))
+ return false;
+
+ var user = container.Owner;
+ if (!_inventory.TryGetContainerSlotEnumerator(user, out var enumerator, slotFlag))
+ return false;
+
+ while (enumerator.NextItem(out var item))
+ {
+ if (_whitelistSystem.IsWhitelistFailOrNull(providerWhitelist, item))
+ continue;
+
+ slotEntity = item;
+ return true;
+ }
+
+ return false;
+ }
+}
+
+/// <summary>
+/// Event for an attempt of getting container, connected to entity on which event was raised.
+/// Fills <see cref="ContainerEntity"/> if connected container exists.
+/// </summary>
+[ByRefEvent]
+public struct GetConnectedContainerEvent
+{
+ /// <summary>
+ /// Container entity, if it exists, or null.
+ /// </summary>
+ public EntityUid? ContainerEntity;
+}
[RegisterComponent, NetworkedComponent]
public sealed partial class AbsorbentComponent : Component
{
- public const string SolutionName = "absorbed";
-
public Dictionary<Color, float> Progress = new();
+ /// <summary>
+ /// Name for solution container, that should be used for absorbed solution storage and as source of absorber solution.
+ /// Default is 'absorbed'.
+ /// </summary>
+ [DataField]
+ public string SolutionName = "absorbed";
+
/// <summary>
/// How much solution we can transfer in one interaction.
/// </summary>
- [DataField("pickupAmount")]
+ [DataField]
public FixedPoint2 PickupAmount = FixedPoint2.New(100);
- [DataField("pickupSound")]
+ [DataField]
public SoundSpecifier PickupSound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg")
{
Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation),
};
- [DataField("transferSound")] public SoundSpecifier TransferSound =
+ [DataField] public SoundSpecifier TransferSound =
new SoundPathSpecifier("/Audio/Effects/Fluids/slosh.ogg")
{
Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(-3f),
{
Params = AudioParams.Default.WithVariation(SharedContentAudioSystem.DefaultVariation).WithVolume(-3f),
};
+
+ /// <summary>
+ /// Marker that absorbent component owner should try to use 'absorber solution' to replace solution to be absorbed.
+ /// Target solution will be simply consumed into container if set to false.
+ /// </summary>
+ [DataField]
+ public bool UseAbsorberSolution = true;
}
namespace Content.Shared.Weapons.Ranged.Components;
[NetworkedComponent]
-public abstract partial class AmmoProviderComponent : Component {}
+public abstract partial class AmmoProviderComponent : Component;
-using Content.Shared.Inventory;
using Content.Shared.Weapons.Ranged.Systems;
-using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Weapons.Ranged.Components;
/// to an entity in the user's clothing slot.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedGunSystem))]
-public sealed partial class ClothingSlotAmmoProviderComponent : AmmoProviderComponent
-{
- /// <summary>
- /// The slot that the ammo provider should be located in.
- /// </summary>
- [DataField("targetSlot", required: true)]
- public SlotFlags TargetSlot;
-
- /// <summary>
- /// A whitelist for determining whether or not an ammo provider is valid.
- /// </summary>
- [DataField("providerWhitelist")]
- public EntityWhitelist? ProviderWhitelist;
-}
+public sealed partial class ClothingSlotAmmoProviderComponent : AmmoProviderComponent;
-using System.Diagnostics.CodeAnalysis;
-using Content.Shared.Inventory;
+using Content.Shared.Containers;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
public partial class SharedGunSystem
{
- [Dependency] private readonly InventorySystem _inventory = default!;
-
private void InitializeClothing()
{
SubscribeLocalEvent<ClothingSlotAmmoProviderComponent, TakeAmmoEvent>(OnClothingTakeAmmo);
private void OnClothingTakeAmmo(EntityUid uid, ClothingSlotAmmoProviderComponent component, TakeAmmoEvent args)
{
- if (!TryGetClothingSlotEntity(uid, component, out var entity))
+ var getConnectedContainerEvent = new GetConnectedContainerEvent();
+ RaiseLocalEvent(uid, ref getConnectedContainerEvent);
+ if(!getConnectedContainerEvent.ContainerEntity.HasValue)
return;
- RaiseLocalEvent(entity.Value, args);
+
+ RaiseLocalEvent(getConnectedContainerEvent.ContainerEntity.Value, args);
}
private void OnClothingAmmoCount(EntityUid uid, ClothingSlotAmmoProviderComponent component, ref GetAmmoCountEvent args)
{
- if (!TryGetClothingSlotEntity(uid, component, out var entity))
+ var getConnectedContainerEvent = new GetConnectedContainerEvent();
+ RaiseLocalEvent(uid, ref getConnectedContainerEvent);
+ if (!getConnectedContainerEvent.ContainerEntity.HasValue)
return;
- RaiseLocalEvent(entity.Value, ref args);
- }
-
- private bool TryGetClothingSlotEntity(EntityUid uid, ClothingSlotAmmoProviderComponent component, [NotNullWhen(true)] out EntityUid? slotEntity)
- {
- slotEntity = null;
-
- if (!Containers.TryGetContainingContainer((uid, null, null), out var container))
- return false;
- var user = container.Owner;
-
- if (!_inventory.TryGetContainerSlotEnumerator(user, out var enumerator, component.TargetSlot))
- return false;
-
- while (enumerator.NextItem(out var item))
- {
- if (_whitelistSystem.IsWhitelistFailOrNull(component.ProviderWhitelist, item))
- continue;
-
- slotEntity = item;
- return true;
- }
- return false;
+ RaiseLocalEvent(getConnectedContainerEvent.ContainerEntity.Value, ref args);
}
}
license: "CC-BY-SA-3.0"
copyright: "Created by the_toilet_guy"
source: "https://freesound.org/people/the_toilet_guy/sounds/98770/"
+
+- files: ["vacuum-cleaner-fast.ogg"]
+ license: "CC0-1.0"
+ copyright: "Created by kyles"
+ source: "https://freesound.org/people/kyles/sounds/637927/"
+
+- files: ["vacuum-cleaner-fast.ogg"]
+ license: "CC0-1.0"
+ copyright: "Created by BrendanSound12 mixed by Fildrance"
+ source: "https://freesound.org/people/BrendanSound12/sounds/445165/"
size: Large
sprite: Objects/Specific/Janitorial/mop.rsi
- type: Absorbent
+ useAbsorberSolution: true
- type: SolutionContainerManager
solutions:
absorbed:
sprite: Objects/Specific/Janitorial/advmop.rsi
- type: Absorbent
pickupAmount: 100
+ useAbsorberSolution: true
- type: UseDelay
delay: 1.0
- type: SolutionRegeneration
sprite: Objects/Specific/Janitorial/rag.rsi
- type: Absorbent
pickupAmount: 15
+ useAbsorberSolution: true
- type: Construction
graph: Rag
node: rag
- FullAuto
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/water_spray.ogg
+ - type: Absorbent
+ pickupAmount: 35
+ solutionName: tank
+ useAbsorberSolution: false
+ pickupSound:
+ path: /Audio/Effects/Fluids/vacuum-cleaner-fast.ogg
- type: Appearance
- type: ClothingSlotAmmoProvider
+ - type: SlotBasedConnectedContainer
targetSlot: BACK
- providerWhitelist:
+ containerWhitelist:
tags:
- NozzleBackTank
+ - type: UseDelay
+ delay: 1.0