CatStation/Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
Dmitry 25c5e59248
Upstream (#3172)
Signed-off-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Co-authored-by: Samuka-C <47865393+Samuka-C@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: Partmedia <kevinz5000@gmail.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: themias <89101928+themias@users.noreply.github.com>
Co-authored-by: Victor Shen <71985089+Vexerot@users.noreply.github.com>
Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Milon <milonpl.git@proton.me>
Co-authored-by: Kirus59 <145689588+Kirus59@users.noreply.github.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: Stomf <5dorkydorks@gmail.com>
Co-authored-by: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com>
Co-authored-by: PraxisMapper <praxismapper@gmail.com>
Co-authored-by: EmoGarbage404 <retron404@gmail.com>
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com>
Co-authored-by: abadaba695 <spacestation13thingy@gmail.com>
Co-authored-by: kosticia <kosticia46@gmail.com>
Co-authored-by: Thinbug <101073555+Thinbug0@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: ActiveMammmoth <140334666+ActiveMammmoth@users.noreply.github.com>
Co-authored-by: Myra <vasilis@pikachu.systems>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com>
Co-authored-by: Gentleman-Bird <dcgreen406@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: BIGZi0348 <svalker0348@gmail.com>
Co-authored-by: LaCumbiaDelCoronavirus <90893484+LaCumbiaDelCoronavirus@users.noreply.github.com>
Co-authored-by: imatsoup <93290208+imatsoup@users.noreply.github.com>
Co-authored-by: Matthew Herber <32679887+happyrobot33@users.noreply.github.com>
Co-authored-by: Ertanic <36124833+Ertanic@users.noreply.github.com>
Co-authored-by: MissKay1994 <15877268+MissKay1994@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
Co-authored-by: eoineoineoin <helloworld@eoinrul.es>
Co-authored-by: Tiniest Shark <head.rebel@yahoo.com>
Co-authored-by: nikitosych <boriszyn@gmail.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com>
Co-authored-by: YoungThug <ramialanbagy@gmail.com>
Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>
Co-authored-by: Vladislav Suchkov <20380250+murolem@users.noreply.github.com>
Co-authored-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
Co-authored-by: 3nderall <101940324+3nderall@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: J <billsmith116@gmail.com>
Co-authored-by: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com>
Co-authored-by: Minemoder5000 <minemoder50000@gmail.com>
Co-authored-by: Spanky <scott@wearejacob.com>
Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com>
Co-authored-by: Deerstop <edainturner@gmail.com>
Co-authored-by: B_Kirill <153602297+B-Kirill@users.noreply.github.com>
Co-authored-by: archee1 <archee3@hotmail.co.uk>
Co-authored-by: Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>
Co-authored-by: poklj <compgeek223@gmail.com>
Co-authored-by: Krunklehorn <42424291+Krunklehorn@users.noreply.github.com>
Co-authored-by: OnyxTheBrave <131422822+OnyxTheBrave@users.noreply.github.com>
Co-authored-by: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: Zalycon <84675130+Zalycon@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Dmitry <57028746+dimm00n@users.noreply.github.com>
2025-05-24 11:00:17 +03:00

327 lines
13 KiB
C#

using System.Numerics;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
using Content.Shared.Fluids.Components;
using Content.Shared.Interaction;
using Content.Shared.Timing;
using Content.Shared.Weapons.Melee;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Fluids.EntitySystems;
/// <inheritdoc/>
public sealed class AbsorbentSystem : SharedAbsorbentSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly PopupSystem _popups = default!;
[Dependency] private readonly PuddleSystem _puddleSystem = default!;
[Dependency] private readonly SharedMeleeWeaponSystem _melee = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AbsorbentComponent, ComponentInit>(OnAbsorbentInit);
SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<AbsorbentComponent, UserActivateInWorldEvent>(OnActivateInWorld);
SubscribeLocalEvent<AbsorbentComponent, SolutionContainerChangedEvent>(OnAbsorbentSolutionChange);
}
private void OnAbsorbentInit(EntityUid uid, AbsorbentComponent component, ComponentInit args)
{
// TODO: I know dirty on init but no prediction moment.
UpdateAbsorbent(uid, component);
}
private void OnAbsorbentSolutionChange(EntityUid uid, AbsorbentComponent component, ref SolutionContainerChangedEvent args)
{
UpdateAbsorbent(uid, component);
}
private void UpdateAbsorbent(EntityUid uid, AbsorbentComponent component)
{
if (!_solutionContainerSystem.TryGetSolution(uid, AbsorbentComponent.SolutionName, out _, out var solution))
return;
var oldProgress = component.Progress.ShallowClone();
component.Progress.Clear();
var mopReagent = solution.GetTotalPrototypeQuantity(_puddleSystem.GetAbsorbentReagents(solution));
if (mopReagent > FixedPoint2.Zero)
{
component.Progress[solution.GetColorWithOnly(_prototype, _puddleSystem.GetAbsorbentReagents(solution))] = mopReagent.Float();
}
var otherColor = solution.GetColorWithout(_prototype, _puddleSystem.GetAbsorbentReagents(solution));
var other = (solution.Volume - mopReagent).Float();
if (other > 0f)
{
component.Progress[otherColor] = other;
}
var remainder = solution.AvailableVolume;
if (remainder > FixedPoint2.Zero)
{
component.Progress[Color.DarkGray] = remainder.Float();
}
if (component.Progress.Equals(oldProgress))
return;
Dirty(uid, component);
}
private void OnActivateInWorld(EntityUid uid, AbsorbentComponent component, UserActivateInWorldEvent args)
{
if (args.Handled)
return;
Mop(uid, args.Target, uid, component);
args.Handled = true;
}
private void OnAfterInteract(EntityUid uid, AbsorbentComponent component, AfterInteractEvent args)
{
if (!args.CanReach || args.Handled || args.Target == null)
return;
Mop(args.User, args.Target.Value, args.Used, component);
args.Handled = true;
}
public void Mop(EntityUid user, EntityUid target, EntityUid used, AbsorbentComponent component)
{
if (!_solutionContainerSystem.TryGetSolution(used, AbsorbentComponent.SolutionName, out var absorberSoln))
return;
if (TryComp<UseDelayComponent>(used, out var useDelay)
&& _useDelay.IsDelayed((used, useDelay)))
return;
// If it's a puddle try to grab from
if (!TryPuddleInteract(user, used, target, component, useDelay, absorberSoln.Value))
{
// If it's refillable try to transfer
if (!TryRefillableInteract(user, used, target, component, useDelay, absorberSoln.Value))
return;
}
}
/// <summary>
/// Logic for an absorbing entity interacting with a refillable.
/// </summary>
private bool TryRefillableInteract(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, UseDelayComponent? useDelay, Entity<SolutionComponent> absorbentSoln)
{
if (!TryComp(target, out RefillableSolutionComponent? refillable))
return false;
if (!_solutionContainerSystem.TryGetRefillableSolution((target, refillable, null), out var refillableSoln, out var refillableSolution))
return false;
if (refillableSolution.Volume <= 0)
{
// Target empty - only transfer absorbent contents into refillable
if (!TryTransferFromAbsorbentToRefillable(user, used, target, component, absorbentSoln, refillableSoln.Value))
return false;
}
else
{
// Target non-empty - do a two-way transfer
if (!TryTwoWayAbsorbentRefillableTransfer(user, used, target, component, absorbentSoln, refillableSoln.Value))
return false;
}
_audio.PlayPvs(component.TransferSound, target);
if (useDelay != null)
_useDelay.TryResetDelay((used, useDelay));
return true;
}
/// <summary>
/// Logic for an transferring solution from absorber to an empty refillable.
/// </summary>
private bool TryTransferFromAbsorbentToRefillable(
EntityUid user,
EntityUid used,
EntityUid target,
AbsorbentComponent component,
Entity<SolutionComponent> absorbentSoln,
Entity<SolutionComponent> refillableSoln)
{
var absorbentSolution = absorbentSoln.Comp.Solution;
if (absorbentSolution.Volume <= 0)
{
_popups.PopupEntity(Loc.GetString("mopping-system-target-container-empty", ("target", target)), user, user);
return false;
}
var refillableSolution = refillableSoln.Comp.Solution;
var transferAmount = component.PickupAmount < refillableSolution.AvailableVolume ?
component.PickupAmount :
refillableSolution.AvailableVolume;
if (transferAmount <= 0)
{
_popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", used)), used, user);
return false;
}
// Prioritize transferring non-evaporatives if absorbent has any
var contaminants = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, transferAmount, _puddleSystem.GetAbsorbentReagents(absorbentSoln.Comp.Solution));
if (contaminants.Volume > 0)
{
_solutionContainerSystem.TryAddSolution(refillableSoln, contaminants);
}
else
{
var evaporatives = _solutionContainerSystem.SplitSolution(absorbentSoln, transferAmount);
_solutionContainerSystem.TryAddSolution(refillableSoln, evaporatives);
}
return true;
}
/// <summary>
/// Logic for an transferring contaminants to a non-empty refillable & reabsorbing water if any available.
/// </summary>
private bool TryTwoWayAbsorbentRefillableTransfer(
EntityUid user,
EntityUid used,
EntityUid target,
AbsorbentComponent component,
Entity<SolutionComponent> absorbentSoln,
Entity<SolutionComponent> refillableSoln)
{
var contaminantsFromAbsorbent = _solutionContainerSystem.SplitSolutionWithout(absorbentSoln, component.PickupAmount, _puddleSystem.GetAbsorbentReagents(absorbentSoln.Comp.Solution));
var absorbentSolution = absorbentSoln.Comp.Solution;
if (contaminantsFromAbsorbent.Volume == FixedPoint2.Zero && absorbentSolution.AvailableVolume == FixedPoint2.Zero)
{
// Nothing to transfer to refillable and no room to absorb anything extra
_popups.PopupEntity(Loc.GetString("mopping-system-puddle-space", ("used", used)), user, user);
// We can return cleanly because nothing was split from absorbent solution
return false;
}
var waterPulled = component.PickupAmount < absorbentSolution.AvailableVolume ?
component.PickupAmount :
absorbentSolution.AvailableVolume;
var refillableSolution = refillableSoln.Comp.Solution;
var waterFromRefillable = refillableSolution.SplitSolutionWithOnly(waterPulled, _puddleSystem.GetAbsorbentReagents(refillableSoln.Comp.Solution));
_solutionContainerSystem.UpdateChemicals(refillableSoln);
if (waterFromRefillable.Volume == FixedPoint2.Zero && contaminantsFromAbsorbent.Volume == FixedPoint2.Zero)
{
// Nothing to transfer in either direction
_popups.PopupEntity(Loc.GetString("mopping-system-target-container-empty-water", ("target", target)), user, user);
// We can return cleanly because nothing was split from refillable solution
return false;
}
var anyTransferOccurred = false;
if (waterFromRefillable.Volume > FixedPoint2.Zero)
{
// transfer water to absorbent
_solutionContainerSystem.TryAddSolution(absorbentSoln, waterFromRefillable);
anyTransferOccurred = true;
}
if (contaminantsFromAbsorbent.Volume > 0)
{
if (refillableSolution.AvailableVolume <= 0)
{
_popups.PopupEntity(Loc.GetString("mopping-system-full", ("used", target)), user, user);
}
else
{
// transfer as much contaminants to refillable as will fit
var contaminantsForRefillable = contaminantsFromAbsorbent.SplitSolution(refillableSolution.AvailableVolume);
_solutionContainerSystem.TryAddSolution(refillableSoln, contaminantsForRefillable);
anyTransferOccurred = true;
}
// absorb everything that did not fit in the refillable back by the absorbent
_solutionContainerSystem.TryAddSolution(absorbentSoln, contaminantsFromAbsorbent);
}
return anyTransferOccurred;
}
/// <summary>
/// Logic for an absorbing entity interacting with a puddle.
/// </summary>
private bool TryPuddleInteract(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent absorber, UseDelayComponent? useDelay, Entity<SolutionComponent> absorberSoln)
{
if (!TryComp(target, out PuddleComponent? puddle))
return false;
if (!_solutionContainerSystem.ResolveSolution(target, puddle.SolutionName, ref puddle.Solution, out var puddleSolution) || puddleSolution.Volume <= 0)
return false;
// Check if the puddle has any non-evaporative reagents
if (_puddleSystem.CanFullyEvaporate(puddleSolution))
{
_popups.PopupEntity(Loc.GetString("mopping-system-puddle-evaporate", ("target", target)), 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 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))
{
var tileRef = _mapSystem.GetTileRef(gridUid.Value, mapGrid, transform.Coordinates);
_puddleSystem.DoTileReactions(tileRef, absorberSplit);
}
_solutionContainerSystem.AddSolution(puddle.Solution.Value, absorberSplit);
_solutionContainerSystem.AddSolution(absorberSoln, puddleSplit);
_audio.PlayPvs(absorber.PickupSound, target);
if (useDelay != null)
_useDelay.TryResetDelay((used, useDelay));
var userXform = Transform(user);
var targetPos = _transform.GetWorldPosition(target);
var localPos = Vector2.Transform(targetPos, _transform.GetInvWorldMatrix(userXform));
localPos = userXform.LocalRotation.RotateVec(localPos);
_melee.DoLunge(user, used, Angle.Zero, localPos, null, false);
return true;
}
}