| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- using Content.Shared.Administration.Logs;
- using Content.Shared.Chemistry;
- using Content.Shared.Chemistry.Components;
- using Content.Shared.Database;
- using Content.Shared.FixedPoint;
- using Content.Shared.Interaction;
- using Content.Shared.Popups;
- using Content.Shared.Verbs;
- using Robust.Shared.Network;
- using Robust.Shared.Player;
- 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.
- /// </summary>
- public sealed class SolutionTransferSystem : EntitySystem
- {
- [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
- [Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
- /// <summary>
- /// Default transfer amounts for the set-transfer verb.
- /// </summary>
- public static readonly FixedPoint2[] DefaultTransferAmounts = new FixedPoint2[] { 1, 5, 10, 25, 50, 100, 250, 500, 1000 };
- public override void Initialize()
- {
- 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);
- Dirty(uid, comp);
- }
- private void AddSetTransferVerbs(Entity<SolutionTransferComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
- {
- var (uid, comp) = ent;
- if (!args.CanAccess || !args.CanInteract || !comp.CanChangeTransferAmount || args.Hands == null)
- return;
- // Custom transfer verb
- var @event = args;
- args.Verbs.Add(new AlternativeVerb()
- {
- Text = Loc.GetString("comp-solution-transfer-verb-custom-amount"),
- Category = VerbCategory.SetTransferAmount,
- // TODO: remove server check when bui prediction is a thing
- Act = () =>
- {
- _ui.OpenUi(uid, TransferAmountUiKey.Key, @event.User);
- },
- Priority = 1
- });
- // Add specific transfer verbs according to the container's size
- var priority = 0;
- var user = args.User;
- foreach (var amount in DefaultTransferAmounts)
- {
- if (amount < comp.MinimumTransferAmount || amount > comp.MaximumTransferAmount)
- continue;
- AlternativeVerb verb = new();
- verb.Text = Loc.GetString("comp-solution-transfer-verb-amount", ("amount", amount));
- verb.Category = VerbCategory.SetTransferAmount;
- verb.Act = () =>
- {
- comp.TransferAmount = amount;
- _popup.PopupClient(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount)), uid, user);
- Dirty(uid, comp);
- };
- // we want to sort by size, not alphabetically by the verb text.
- verb.Priority = priority;
- priority--;
- args.Verbs.Add(verb);
- }
- }
- private void OnAfterInteract(Entity<SolutionTransferComponent> ent, ref AfterInteractEvent args)
- {
- if (!args.CanReach || args.Target is not {} target)
- return;
- var (uid, comp) = ent;
- //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))
- {
- var transferAmount = comp.TransferAmount; // This is the player-configurable transfer amount of "uid," not the target reagent tank.
- // if the receiver has a smaller transfer limit, use that instead
- if (refill?.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";
- _popup.PopupClient(Loc.GetString(msg, ("owner", args.Target), ("amount", transferred), ("target", uid)), uid, args.User);
- 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 _))
- {
- var transferAmount = comp.TransferAmount;
- if (targetRefill?.MaxRefill is {} maxRefill)
- transferAmount = FixedPoint2.Min(transferAmount, maxRefill);
- var transferred = Transfer(args.User, uid, ownerSoln.Value, target, targetSoln.Value, transferAmount);
- args.Handled = true;
- if (transferred > 0)
- {
- var message = Loc.GetString("comp-solution-transfer-transfer-solution", ("amount", transferred), ("target", target));
- _popup.PopupClient(message, uid, args.User);
- }
- }
- }
- /// <summary>
- /// Transfer from a solution to another, allowing either entity to cancel it and show a popup.
- /// </summary>
- /// <returns>The actual amount transferred.</returns>
- public FixedPoint2 Transfer(EntityUid user,
- EntityUid sourceEntity,
- Entity<SolutionComponent> source,
- EntityUid targetEntity,
- Entity<SolutionComponent> target,
- FixedPoint2 amount)
- {
- var transferAttempt = new SolutionTransferAttemptEvent(sourceEntity, targetEntity);
- // Check if the source is cancelling the transfer
- RaiseLocalEvent(sourceEntity, ref transferAttempt);
- if (transferAttempt.CancelReason is {} reason)
- {
- _popup.PopupClient(reason, sourceEntity, user);
- return FixedPoint2.Zero;
- }
- var sourceSolution = source.Comp.Solution;
- if (sourceSolution.Volume == 0)
- {
- _popup.PopupClient(Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity)), sourceEntity, user);
- return FixedPoint2.Zero;
- }
- // Check if the target is cancelling the transfer
- RaiseLocalEvent(targetEntity, ref transferAttempt);
- if (transferAttempt.CancelReason is {} targetReason)
- {
- _popup.PopupClient(targetReason, targetEntity, user);
- return FixedPoint2.Zero;
- }
- var targetSolution = target.Comp.Solution;
- if (targetSolution.AvailableVolume == 0)
- {
- _popup.PopupClient(Loc.GetString("comp-solution-transfer-is-full", ("target", targetEntity)), targetEntity, user);
- return FixedPoint2.Zero;
- }
- 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);
- _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>
- /// Raised when attempting to transfer from one solution to another.
- /// Raised on both the source and target entities so either can cancel the transfer.
- /// To not mispredict this should always be cancelled in shared code and not server or client.
- /// </summary>
- [ByRefEvent]
- public record struct SolutionTransferAttemptEvent(EntityUid From, EntityUid To, string? CancelReason = null)
- {
- /// <summary>
- /// Cancels the transfer.
- /// </summary>
- public void Cancel(string reason)
- {
- CancelReason = reason;
- }
- }
- /// <summary>
- /// Raised on the target entity when a non-zero amount of solution gets transferred.
- /// </summary>
- [ByRefEvent]
- public record struct SolutionTransferredEvent(EntityUid From, EntityUid To, EntityUid User, FixedPoint2 Amount);
|