+using System.Diagnostics.CodeAnalysis;
using Content.Shared.Administration.Logs;
-using Content.Shared.Body.Components;
-using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Hypospray.Events;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Forensics;
using Content.Shared.IdentityManagement;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Audio.Systems;
+using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry.EntitySystems;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainers = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
public override void Initialize()
{
SubscribeLocalEvent<HyposprayComponent, MeleeHitEvent>(OnAttack);
SubscribeLocalEvent<HyposprayComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<HyposprayComponent, GetVerbsEvent<AlternativeVerb>>(AddToggleModeVerb);
+ SubscribeLocalEvent<HyposprayComponent, HyposprayDrawDoAfterEvent>(OnDrawDoAfter);
}
#region Ref events
TryDoInject(entity, args.HitEntities[0], args.User);
}
+ private void OnDrawDoAfter(Entity<HyposprayComponent> entity, ref HyposprayDrawDoAfterEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (entity.Comp.CanContainerDraw
+ && args.Target.HasValue
+ && !EligibleEntity(args.Target.Value, entity)
+ && _solutionContainers.TryGetDrawableSolution(args.Target.Value, out var drawableSolution, out _))
+ {
+ TryDraw(entity, args.Target.Value, drawableSolution.Value, args.User);
+ }
+ }
+
#endregion
#region Draw/Inject
&& !EligibleEntity(target, entity)
&& _solutionContainers.TryGetDrawableSolution(target, out var drawableSolution, out _))
{
- return TryDraw(entity, target, drawableSolution.Value, user);
+ return TryStartDraw(entity, target, drawableSolution.Value, user);
}
return TryDoInject(entity, target, user);
return true;
}
- private bool TryDraw(Entity<HyposprayComponent> entity, EntityUid target, Entity<SolutionComponent> targetSolution, EntityUid user)
+ public bool TryStartDraw(Entity<HyposprayComponent> entity, EntityUid target, Entity<SolutionComponent> targetSolution, EntityUid user)
+ {
+ if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln))
+ return false;
+
+ if (!TryGetDrawAmount(entity, target, targetSolution, user, soln.Value, out _))
+ return false;
+
+ var doAfterArgs = new DoAfterArgs(EntityManager, user, entity.Comp.DrawTime, new HyposprayDrawDoAfterEvent(), entity, target)
+ {
+ BreakOnDamage = true,
+ BreakOnMove = true,
+ NeedHand = true,
+ Hidden = true,
+ };
+
+ return _doAfter.TryStartDoAfter(doAfterArgs, out _);
+ }
+
+ private bool TryGetDrawAmount(Entity<HyposprayComponent> entity, EntityUid target, Entity<SolutionComponent> targetSolution, EntityUid user, Entity<SolutionComponent> solutionEntity, [NotNullWhen(true)] out FixedPoint2? amount)
{
- if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln,
- out var solution) || solution.AvailableVolume == 0)
+ amount = null;
+
+ if (solutionEntity.Comp.Solution.AvailableVolume == 0)
{
return false;
}
// Get transfer amount. May be smaller than _transferAmount if not enough room, also make sure there's room in the injector
var realTransferAmount = FixedPoint2.Min(entity.Comp.TransferAmount, targetSolution.Comp.Solution.Volume,
- solution.AvailableVolume);
+ solutionEntity.Comp.Solution.AvailableVolume);
if (realTransferAmount <= 0)
{
return false;
}
- var removedSolution = _solutionContainers.Draw(target, targetSolution, realTransferAmount);
+ amount = realTransferAmount;
+ return true;
+ }
+
+ private bool TryDraw(Entity<HyposprayComponent> entity, EntityUid target, Entity<SolutionComponent> targetSolution, EntityUid user)
+ {
+ if (!_solutionContainers.TryGetSolution(entity.Owner, entity.Comp.SolutionName, out var soln))
+ return false;
+
+ if (!TryGetDrawAmount(entity, target, targetSolution, user, soln.Value, out var amount))
+ return false;
+
+ var removedSolution = _solutionContainers.Draw(target, targetSolution, amount.Value);
if (!_solutionContainers.TryAddSolution(soln.Value, removedSolution))
{
#endregion
}
+
+[Serializable, NetSerializable]
+public sealed partial class HyposprayDrawDoAfterEvent : SimpleDoAfterEvent {}
+using System.Diagnostics.CodeAnalysis;
using Content.Shared.Administration.Logs;
-using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Database;
+using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Verbs;
-using Robust.Shared.Network;
-using Robust.Shared.Player;
+using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry.EntitySystems;
/// <summary>
-/// Allows an entity to transfer solutions with a customizable amount per click.
-/// Also provides <see cref="Transfer"/> API for other systems.
+/// Allows an entity to transfer solutions with a customizable amount -per click-.
+/// Also provides <see cref="Transfer"/>, <see cref="RefillTransfer"/> and <see cref="DrainTransfer"/> API for other systems.
/// </summary>
public sealed class SolutionTransferSystem : EntitySystem
{
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
+ [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+
+ private EntityQuery<RefillableSolutionComponent> _refillableQuery;
+ private EntityQuery<DrainableSolutionComponent> _drainableQuery;
/// <summary>
/// Default transfer amounts for the set-transfer verb.
base.Initialize();
SubscribeLocalEvent<SolutionTransferComponent, GetVerbsEvent<AlternativeVerb>>(AddSetTransferVerbs);
- SubscribeLocalEvent<SolutionTransferComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<SolutionTransferComponent, TransferAmountSetValueMessage>(OnTransferAmountSetValueMessage);
- }
-
- private void OnTransferAmountSetValueMessage(Entity<SolutionTransferComponent> ent, ref TransferAmountSetValueMessage message)
- {
- var (uid, comp) = ent;
-
- var newTransferAmount = FixedPoint2.Clamp(message.Value, comp.MinimumTransferAmount, comp.MaximumTransferAmount);
- comp.TransferAmount = newTransferAmount;
-
- if (message.Actor is { Valid: true } user)
- _popup.PopupEntity(Loc.GetString("comp-solution-transfer-set-amount", ("amount", newTransferAmount)), uid, user);
+ SubscribeLocalEvent<SolutionTransferComponent, AfterInteractEvent>(OnAfterInteract);
+ SubscribeLocalEvent<SolutionTransferComponent, SolutionDrainTransferDoAfterEvent>(OnSolutionDrainTransferDoAfter);
+ SubscribeLocalEvent<SolutionTransferComponent, SolutionRefillTransferDoAfterEvent>(OnSolutionFillTransferDoAfter);
- Dirty(uid, comp);
+ _refillableQuery = GetEntityQuery<RefillableSolutionComponent>();
+ _drainableQuery = GetEntityQuery<DrainableSolutionComponent>();
}
private void AddSetTransferVerbs(Entity<SolutionTransferComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
- var (uid, comp) = ent;
-
- if (!args.CanAccess || !args.CanInteract || !comp.CanChangeTransferAmount || args.Hands == null)
+ if (!args.CanAccess || !args.CanInteract || !ent.Comp.CanChangeTransferAmount || args.Hands == null)
return;
// Custom transfer verb
// TODO: remove server check when bui prediction is a thing
Act = () =>
{
- _ui.OpenUi(uid, TransferAmountUiKey.Key, @event.User);
+ _ui.OpenUi(ent.Owner, TransferAmountUiKey.Key, @event.User);
},
Priority = 1
});
var user = args.User;
foreach (var amount in DefaultTransferAmounts)
{
- if (amount < comp.MinimumTransferAmount || amount > comp.MaximumTransferAmount)
+ if (amount < ent.Comp.MinimumTransferAmount || amount > ent.Comp.MaximumTransferAmount)
continue;
AlternativeVerb verb = new();
verb.Category = VerbCategory.SetTransferAmount;
verb.Act = () =>
{
- comp.TransferAmount = amount;
+ ent.Comp.TransferAmount = amount;
- _popup.PopupClient(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)), uid, user);
+ _popup.PopupClient(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)), ent.Owner, user);
- Dirty(uid, comp);
+ Dirty(ent.Owner, ent.Comp);
};
// we want to sort by size, not alphabetically by the verb text.
}
}
+ private void OnTransferAmountSetValueMessage(Entity<SolutionTransferComponent> ent, ref TransferAmountSetValueMessage message)
+ {
+ var newTransferAmount = FixedPoint2.Clamp(message.Value, ent.Comp.MinimumTransferAmount, ent.Comp.MaximumTransferAmount);
+ ent.Comp.TransferAmount = newTransferAmount;
+
+ if (message.Actor is { Valid: true } user)
+ _popup.PopupEntity(Loc.GetString("comp-solution-transfer-set-amount", ("amount", newTransferAmount)), ent.Owner, user);
+
+ Dirty(ent.Owner, ent.Comp);
+ }
+
private void OnAfterInteract(Entity<SolutionTransferComponent> ent, ref AfterInteractEvent args)
{
if (!args.CanReach || args.Target is not {} target)
return;
- var (uid, comp) = ent;
+ // We have two cases for interaction:
+ // Held Drainable --> Target Refillable
+ // Held Refillable <-- Target Drainable
+
+ // In the case where the target has both Refillable and Drainable, Held --> Target takes priority.
- //Special case for reagent tanks, because normally clicking another container will give solution, not take it.
- if (comp.CanReceive
- && !HasComp<RefillableSolutionComponent>(target) // target must not be refillable (e.g. Reagent Tanks)
- && _solution.TryGetDrainableSolution(target, out var targetSoln, out _) // target must be drainable
- && TryComp<RefillableSolutionComponent>(uid, out var refill)
- && _solution.TryGetRefillableSolution((uid, refill, null), out var ownerSoln, out var ownerRefill))
+ if (ent.Comp.CanSend
+ && _drainableQuery.TryComp(ent.Owner, out var heldDrainable)
+ && _refillableQuery.TryComp(target, out var targetRefillable)
+ && TryGetTransferrableSolutions((ent.Owner, heldDrainable),
+ (target, targetRefillable),
+ out var ownerSoln,
+ out var targetSoln,
+ out _))
{
- var transferAmount = comp.TransferAmount; // This is the player-configurable transfer amount of "uid," not the target reagent tank.
+ args.Handled = true; //If we reach this point, the interaction counts as handled.
- // if the receiver has a smaller transfer limit, use that instead
- if (refill?.MaxRefill is {} maxRefill)
+ var transferAmount = ent.Comp.TransferAmount;
+ if (targetRefillable.MaxRefill is {} maxRefill)
transferAmount = FixedPoint2.Min(transferAmount, maxRefill);
- var transferred = Transfer(args.User, target, targetSoln.Value, uid, ownerSoln.Value, transferAmount);
- args.Handled = true;
- if (transferred > 0)
- {
- var toTheBrim = ownerRefill.AvailableVolume == 0;
- var msg = toTheBrim
- ? "comp-solution-transfer-fill-fully"
- : "comp-solution-transfer-fill-normal";
+ var transferData = new SolutionTransferData(args.User, ent.Owner, ownerSoln.Value, target, targetSoln.Value, transferAmount);
+ var transferTime = targetRefillable.RefillTime + heldDrainable.DrainTime;
- _popup.PopupClient(Loc.GetString(msg, ("owner", args.Target), ("amount", transferred), ("target", uid)), uid, args.User);
- return;
+ if (transferTime > TimeSpan.Zero)
+ {
+ if (!CanTransfer(transferData))
+ return;
+
+ var doAfterArgs = new DoAfterArgs(EntityManager, args.User, transferTime, new SolutionDrainTransferDoAfterEvent(transferAmount), ent.Owner, target)
+ {
+ BreakOnDamage = true,
+ BreakOnMove = true,
+ NeedHand = true,
+ Hidden = true,
+ };
+ _doAfter.TryStartDoAfter(doAfterArgs);
+ }
+ else
+ {
+ DrainTransfer(transferData);
}
+
+ return;
}
- // if target is refillable, and owner is drainable
- if (comp.CanSend
- && TryComp<RefillableSolutionComponent>(target, out var targetRefill)
- && _solution.TryGetRefillableSolution((target, targetRefill, null), out targetSoln, out _)
- && _solution.TryGetDrainableSolution(uid, out ownerSoln, out _))
+ if (ent.Comp.CanReceive
+ && _refillableQuery.TryComp(ent.Owner, out var heldRefillable)
+ && _drainableQuery.TryComp(target, out var targetDrainable)
+ && TryGetTransferrableSolutions((target, targetDrainable),
+ (ent.Owner, heldRefillable),
+ out targetSoln,
+ out ownerSoln,
+ out var solution))
{
- var transferAmount = comp.TransferAmount;
+ args.Handled = true; //If we reach this point, the interaction counts as handled.
- if (targetRefill?.MaxRefill is {} maxRefill)
+ var transferAmount = ent.Comp.TransferAmount; // This is the player-configurable transfer amount of "uid," not the target drainable.
+ if (heldRefillable.MaxRefill is {} maxRefill) // if the receiver has a smaller transfer limit, use that instead
transferAmount = FixedPoint2.Min(transferAmount, maxRefill);
- var transferred = Transfer(args.User, uid, ownerSoln.Value, target, targetSoln.Value, transferAmount);
- args.Handled = true;
- if (transferred > 0)
+ var transferData = new SolutionTransferData(args.User, target, targetSoln.Value, ent.Owner, ownerSoln.Value, transferAmount);
+ var transferTime = heldRefillable.RefillTime + targetDrainable.DrainTime;
+
+ if (transferTime > TimeSpan.Zero)
{
- var message = Loc.GetString("comp-solution-transfer-transfer-solution", ("amount", transferred), ("target", target));
- _popup.PopupClient(message, uid, args.User);
+ if (!CanTransfer(transferData))
+ return;
+
+ var doAfterArgs = new DoAfterArgs(EntityManager, args.User, transferTime, new SolutionRefillTransferDoAfterEvent(transferAmount), ent.Owner, target)
+ {
+ BreakOnDamage = true,
+ BreakOnMove = true,
+ NeedHand = true,
+ Hidden = true,
+ };
+ _doAfter.TryStartDoAfter(doAfterArgs);
+ }
+ else
+ {
+ RefillTransfer(transferData, solution);
}
}
}
+ private void OnSolutionDrainTransferDoAfter(Entity<SolutionTransferComponent> ent, ref SolutionDrainTransferDoAfterEvent args)
+ {
+ if (args.Cancelled || args.Target is not { } target)
+ return;
+
+ // Have to check again, in case something has changed.
+ if (CanSend(ent, target, out var ownerSoln, out var targetSoln))
+ {
+ DrainTransfer(new SolutionTransferData(args.User, ent.Owner, ownerSoln.Value, args.Target.Value, targetSoln.Value, args.Amount));
+ }
+ }
+
+ private void OnSolutionFillTransferDoAfter(Entity<SolutionTransferComponent> ent, ref SolutionRefillTransferDoAfterEvent args)
+ {
+ if (args.Cancelled || args.Target is not { } target)
+ return;
+
+ // Have to check again, in case something has changed.
+ if (!CanRecieve(ent, target, out var ownerSoln, out var targetSoln, out var solution))
+ return;
+
+ RefillTransfer(new SolutionTransferData(args.User, target, targetSoln.Value, ent.Owner, ownerSoln.Value, args.Amount), solution);
+ }
+
+ private bool CanSend(Entity<SolutionTransferComponent, DrainableSolutionComponent?> ent,
+ Entity<RefillableSolutionComponent?> target,
+ [NotNullWhen(true)] out Entity<SolutionComponent>? drainable,
+ [NotNullWhen(true)] out Entity<SolutionComponent>? refillable)
+ {
+ drainable = null;
+ refillable = null;
+
+ return ent.Comp1.CanReceive && TryGetTransferrableSolutions(ent.Owner, target, out drainable, out refillable, out _);
+ }
+
+ private bool CanRecieve(Entity<SolutionTransferComponent> ent,
+ EntityUid source,
+ [NotNullWhen(true)] out Entity<SolutionComponent>? drainable,
+ [NotNullWhen(true)] out Entity<SolutionComponent>? refillable,
+ [NotNullWhen(true)] out Solution? solution)
+ {
+ drainable = null;
+ refillable = null;
+ solution = null;
+
+ return ent.Comp.CanReceive && TryGetTransferrableSolutions(source, ent.Owner, out drainable, out refillable, out solution);
+ }
+
+ private bool TryGetTransferrableSolutions(Entity<DrainableSolutionComponent?> source,
+ Entity<RefillableSolutionComponent?> target,
+ [NotNullWhen(true)] out Entity<SolutionComponent>? drainable,
+ [NotNullWhen(true)] out Entity<SolutionComponent>? refillable,
+ [NotNullWhen(true)] out Solution? solution)
+ {
+ drainable = null;
+ refillable = null;
+ solution = null;
+
+ if (!_drainableQuery.Resolve(source, ref source.Comp) || !_refillableQuery.Resolve(target, ref target.Comp))
+ return false;
+
+ if (!_solution.TryGetDrainableSolution(source, out drainable, out _))
+ return false;
+
+ if (!_solution.TryGetRefillableSolution(target, out refillable, out solution))
+ return false;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Attempt to drain a solution into another, such as pouring a bottle into a glass.
+ /// Includes a pop-up if the transfer failed or succeeded
+ /// </summary>
+ /// <param name="data">The transfer data making up the transfer.</param>
+ /// <returns>The actual amount transferred.</returns>
+ private void DrainTransfer(SolutionTransferData data)
+ {
+ var transferred = Transfer(data);
+ if (transferred <= 0)
+ return;
+
+ var message = Loc.GetString("comp-solution-transfer-transfer-solution", ("amount", transferred), ("target", data.TargetEntity));
+ _popup.PopupClient(message, data.SourceEntity, data.User);
+ }
+
+ /// <summary>
+ /// Attempt to fill a solution from another container, such as tapping from a water tank.
+ /// Includes a pop-up if the transfer failed or succeeded.
+ /// </summary>
+ /// <param name="data">The transfer data making up the transfer.</param>
+ /// <param name="targetSolution">The target solution,included for LoC pop-up purposes.</param>
+ /// <returns>The actual amount transferred.</returns>
+ private void RefillTransfer(SolutionTransferData data, Solution targetSolution)
+ {
+ var transferred = Transfer(data);
+ if (transferred <= 0)
+ return;
+
+ var toTheBrim = targetSolution.AvailableVolume == 0;
+ var msg = toTheBrim
+ ? "comp-solution-transfer-fill-fully"
+ : "comp-solution-transfer-fill-normal";
+
+ _popup.PopupClient(Loc.GetString(msg, ("owner", data.SourceEntity), ("amount", transferred), ("target", data.TargetEntity)), data.TargetEntity, data.User);
+ }
+
/// <summary>
- /// Transfer from a solution to another, allowing either entity to cancel it and show a popup.
+ /// Transfer from a solution to another, allowing either entity to cancel.
+ /// Includes a pop-up if the transfer failed.
/// </summary>
/// <returns>The actual amount transferred.</returns>
- public FixedPoint2 Transfer(EntityUid user,
- EntityUid sourceEntity,
- Entity<SolutionComponent> source,
- EntityUid targetEntity,
- Entity<SolutionComponent> target,
- FixedPoint2 amount)
+ public FixedPoint2 Transfer(SolutionTransferData data)
{
- var transferAttempt = new SolutionTransferAttemptEvent(sourceEntity, targetEntity);
+ var sourceSolution = data.Source.Comp.Solution;
+ var targetSolution = data.Target.Comp.Solution;
+
+ if (!CanTransfer(data))
+ return FixedPoint2.Zero;
+
+ var actualAmount = FixedPoint2.Min(data.Amount, FixedPoint2.Min(sourceSolution.Volume, targetSolution.AvailableVolume));
+
+ var solution = _solution.SplitSolution(data.Source, actualAmount);
+ _solution.AddSolution(data.Target, solution);
+
+ var ev = new SolutionTransferredEvent(data.SourceEntity, data.TargetEntity, data.User, actualAmount);
+ RaiseLocalEvent(data.TargetEntity, ref ev);
+
+ _adminLogger.Add(LogType.Action,
+ LogImpact.Medium,
+ $"{ToPrettyString(data.User):player} transferred {SharedSolutionContainerSystem.ToPrettyString(solution)} to {ToPrettyString(data.TargetEntity):target}, which now contains {SharedSolutionContainerSystem.ToPrettyString(targetSolution)}");
+
+ return actualAmount;
+ }
+
+ /// <summary>
+ /// Check if the source solution can transfer the amount to the target solution, and display a pop-up if it fails.
+ /// </summary>
+ private bool CanTransfer(SolutionTransferData data)
+ {
+ var transferAttempt = new SolutionTransferAttemptEvent(data.SourceEntity, data.TargetEntity);
// Check if the source is cancelling the transfer
- RaiseLocalEvent(sourceEntity, ref transferAttempt);
+ RaiseLocalEvent(data.SourceEntity, ref transferAttempt);
if (transferAttempt.CancelReason is {} reason)
{
- _popup.PopupClient(reason, sourceEntity, user);
- return FixedPoint2.Zero;
+ _popup.PopupClient(reason, data.SourceEntity, data.User);
+ return false;
}
- var sourceSolution = source.Comp.Solution;
+ var sourceSolution = data.Source.Comp.Solution;
if (sourceSolution.Volume == 0)
{
- _popup.PopupClient(Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity)), sourceEntity, user);
- return FixedPoint2.Zero;
+ _popup.PopupClient(Loc.GetString("comp-solution-transfer-is-empty", ("target", data.SourceEntity)), data.SourceEntity, data.User);
+ return false;
}
// Check if the target is cancelling the transfer
- RaiseLocalEvent(targetEntity, ref transferAttempt);
+ RaiseLocalEvent(data.TargetEntity, ref transferAttempt);
if (transferAttempt.CancelReason is {} targetReason)
{
- _popup.PopupClient(targetReason, targetEntity, user);
- return FixedPoint2.Zero;
+ _popup.PopupClient(targetReason, data.TargetEntity, data.User);
+ return false;
}
- var targetSolution = target.Comp.Solution;
+ var targetSolution = data.Target.Comp.Solution;
if (targetSolution.AvailableVolume == 0)
{
- _popup.PopupClient(Loc.GetString("comp-solution-transfer-is-full", ("target", targetEntity)), targetEntity, user);
- return FixedPoint2.Zero;
+ _popup.PopupClient(Loc.GetString("comp-solution-transfer-is-full", ("target", data.TargetEntity)), data.TargetEntity, data.User);
+ return false;
}
- var actualAmount = FixedPoint2.Min(amount, FixedPoint2.Min(sourceSolution.Volume, targetSolution.AvailableVolume));
-
- var solution = _solution.SplitSolution(source, actualAmount);
- _solution.AddSolution(target, solution);
-
- var ev = new SolutionTransferredEvent(sourceEntity, targetEntity, user, actualAmount);
- RaiseLocalEvent(targetEntity, ref ev);
+ return true;
+ }
+}
- _adminLogger.Add(LogType.Action, LogImpact.Medium,
- $"{ToPrettyString(user):player} transferred {SharedSolutionContainerSystem.ToPrettyString(solution)} to {ToPrettyString(targetEntity):target}, which now contains {SharedSolutionContainerSystem.ToPrettyString(targetSolution)}");
- return actualAmount;
- }
+/// <summary>
+/// A collection of data containing relevant entities and values for transferring reagents.
+/// </summary>
+/// <param name="user">The user performing the transfer.</param>
+/// <param name="sourceEntity">The entity holding the solution container which reagents are being moved from.</param>
+/// <param name="source">The entity holding the solution from which reagents are being moved away from.</param>
+/// <param name="targetEntity">The entity holding the solution container which reagents are being moved to.</param>
+/// <param name="target">The entity holding the solution which reagents are being moved to</param>
+/// <param name="amount">The amount being moved.</param>
+public struct SolutionTransferData(EntityUid user, EntityUid sourceEntity, Entity<SolutionComponent> source, EntityUid targetEntity, Entity<SolutionComponent> target, FixedPoint2 amount)
+{
+ public EntityUid User = user;
+ public EntityUid SourceEntity = sourceEntity;
+ public Entity<SolutionComponent> Source = source;
+ public EntityUid TargetEntity = targetEntity;
+ public Entity<SolutionComponent> Target = target;
+ public FixedPoint2 Amount = amount;
}
/// <summary>
/// </summary>
[ByRefEvent]
public record struct SolutionTransferredEvent(EntityUid From, EntityUid To, EntityUid User, FixedPoint2 Amount);
+
+/// <summary>
+/// Doafter event for solution transfers where the held item is drained into the target. Checks for validity both when initiating and when finishing the event.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed partial class SolutionDrainTransferDoAfterEvent : DoAfterEvent
+{
+ public FixedPoint2 Amount;
+
+ public SolutionDrainTransferDoAfterEvent(FixedPoint2 amount)
+ {
+ Amount = amount;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}
+
+/// <summary>
+/// Doafter event for solution transfers where the held item is filled from the target. Checks for validity both when initiating and when finishing the event.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed partial class SolutionRefillTransferDoAfterEvent : DoAfterEvent
+{
+ public FixedPoint2 Amount;
+
+ public SolutionRefillTransferDoAfterEvent(FixedPoint2 amount)
+ {
+ Amount = amount;
+ }
+
+ public override DoAfterEvent Clone() => this;
+}